signoz/pkg/querybuilder/never_true.go

940 lines
27 KiB
Go
Raw Normal View History

package querybuilder
import (
"fmt"
"strconv"
"strings"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/antlr4-go/antlr/v4"
)
// FieldConstraint represents a constraint on a field
type FieldConstraint struct {
Field string
Operator qbtypes.FilterOperator
Value interface{}
Values []interface{} // For IN, NOT IN operations
}
// ConstraintSet represents a set of constraints that must all be true (AND)
type ConstraintSet struct {
Constraints map[string][]FieldConstraint // field -> constraints
}
// LogicalContradictionDetector implements the visitor pattern to detect logical contradictions
type LogicalContradictionDetector struct {
grammar.BaseFilterQueryVisitor
constraintStack []*ConstraintSet // Stack of constraint sets for nested expressions
contradictions []string
notContextStack []bool // Stack to track NOT contexts
}
// DetectContradictions analyzes a query string and returns any contradictions found
func DetectContradictions(query string) ([]string, error) {
// Setup ANTLR parsing pipeline
input := antlr.NewInputStream(query)
lexer := grammar.NewFilterQueryLexer(input)
// Error handling
lexerErrorListener := NewErrorListener()
lexer.RemoveErrorListeners()
lexer.AddErrorListener(lexerErrorListener)
tokens := antlr.NewCommonTokenStream(lexer, 0)
parser := grammar.NewFilterQueryParser(tokens)
parserErrorListener := NewErrorListener()
parser.RemoveErrorListeners()
parser.AddErrorListener(parserErrorListener)
// Parse the query
tree := parser.Query()
// Check for syntax errors
if len(parserErrorListener.SyntaxErrors) > 0 {
return nil, fmt.Errorf("syntax errors: %v", parserErrorListener.SyntaxErrors)
}
// Create detector and visit tree
detector := &LogicalContradictionDetector{
constraintStack: []*ConstraintSet{
{Constraints: make(map[string][]FieldConstraint)},
},
contradictions: []string{},
notContextStack: []bool{false},
}
detector.Visit(tree)
// Deduplicate contradictions
seen := make(map[string]bool)
unique := []string{}
for _, c := range detector.contradictions {
if !seen[c] {
seen[c] = true
unique = append(unique, c)
}
}
return unique, nil
}
// Helper methods for constraint stack
func (d *LogicalContradictionDetector) currentConstraints() *ConstraintSet {
return d.constraintStack[len(d.constraintStack)-1]
}
// Helper methods for NOT context
func (d *LogicalContradictionDetector) inNotContext() bool {
return d.notContextStack[len(d.notContextStack)-1]
}
func (d *LogicalContradictionDetector) pushNotContext(value bool) {
d.notContextStack = append(d.notContextStack, value)
}
func (d *LogicalContradictionDetector) popNotContext() {
if len(d.notContextStack) > 1 {
d.notContextStack = d.notContextStack[:len(d.notContextStack)-1]
}
}
// Visit dispatches to the appropriate visit method
func (d *LogicalContradictionDetector) Visit(tree antlr.ParseTree) interface{} {
if tree == nil {
return nil
}
return tree.Accept(d)
}
// VisitQuery is the entry point
func (d *LogicalContradictionDetector) VisitQuery(ctx *grammar.QueryContext) interface{} {
d.Visit(ctx.Expression())
// Check final constraints
d.checkContradictions(d.currentConstraints())
return nil
}
// VisitExpression just passes through to OrExpression
func (d *LogicalContradictionDetector) VisitExpression(ctx *grammar.ExpressionContext) interface{} {
return d.Visit(ctx.OrExpression())
}
// VisitOrExpression handles OR logic
func (d *LogicalContradictionDetector) VisitOrExpression(ctx *grammar.OrExpressionContext) interface{} {
andExpressions := ctx.AllAndExpression()
if len(andExpressions) == 1 {
// Single AND expression, just visit it
return d.Visit(andExpressions[0])
}
// Multiple AND expressions connected by OR
// Visit each branch to find contradictions within branches
for _, andExpr := range andExpressions {
// Save current constraints
savedConstraints := d.cloneConstraintSet(d.currentConstraints())
// Visit the AND expression
d.Visit(andExpr)
// Restore constraints for next branch
d.constraintStack[len(d.constraintStack)-1] = savedConstraints
}
return nil
}
// VisitAndExpression handles AND logic (including implicit AND)
func (d *LogicalContradictionDetector) VisitAndExpression(ctx *grammar.AndExpressionContext) interface{} {
unaryExpressions := ctx.AllUnaryExpression()
// Visit each unary expression, accumulating constraints
for _, unaryExpr := range unaryExpressions {
d.Visit(unaryExpr)
}
return nil
}
// VisitUnaryExpression handles NOT operator
func (d *LogicalContradictionDetector) VisitUnaryExpression(ctx *grammar.UnaryExpressionContext) interface{} {
hasNot := ctx.NOT() != nil
if hasNot {
// Push new NOT context (toggle current value)
d.pushNotContext(!d.inNotContext())
}
result := d.Visit(ctx.Primary())
if hasNot {
// Pop NOT context
d.popNotContext()
}
return result
}
// VisitPrimary handles different primary expressions
func (d *LogicalContradictionDetector) VisitPrimary(ctx *grammar.PrimaryContext) interface{} {
if ctx.OrExpression() != nil {
// Parenthesized expression
// If we're in an AND context, we continue with the same constraint set
// Otherwise, we need to handle it specially
return d.Visit(ctx.OrExpression())
} else if ctx.Comparison() != nil {
return d.Visit(ctx.Comparison())
} else if ctx.FunctionCall() != nil {
// Handle function calls if needed
return nil
} else if ctx.FullText() != nil {
// Handle full text search if needed
return nil
}
return nil
}
// VisitComparison extracts constraints from comparisons
func (d *LogicalContradictionDetector) VisitComparison(ctx *grammar.ComparisonContext) interface{} {
if ctx.Key() == nil {
return nil
}
field := ctx.Key().GetText()
notContext := d.inNotContext()
// Handle EXISTS
if ctx.EXISTS() != nil {
operator := qbtypes.FilterOperatorExists
if ctx.NOT() != nil {
operator = qbtypes.FilterOperatorNotExists
}
// Apply NOT context
if notContext {
operator = negateOperator(operator)
}
constraint := FieldConstraint{
Field: field,
Operator: operator,
}
d.addConstraint(constraint)
return nil
}
// Handle IN/NOT IN
if ctx.InClause() != nil {
values := d.extractValueList(ctx.InClause().(*grammar.InClauseContext).ValueList())
operator := qbtypes.FilterOperatorIn
if notContext {
operator = negateOperator(operator)
}
constraint := FieldConstraint{
Field: field,
Operator: operator,
Values: values,
}
d.addConstraint(constraint)
return nil
}
if ctx.NotInClause() != nil {
values := d.extractValueList(ctx.NotInClause().(*grammar.NotInClauseContext).ValueList())
operator := qbtypes.FilterOperatorNotIn
if notContext {
operator = negateOperator(operator)
}
constraint := FieldConstraint{
Field: field,
Operator: operator,
Values: values,
}
d.addConstraint(constraint)
return nil
}
// Handle BETWEEN
if ctx.BETWEEN() != nil {
values := ctx.AllValue()
if len(values) == 2 {
val1 := d.extractValue(values[0])
val2 := d.extractValue(values[1])
operator := qbtypes.FilterOperatorBetween
if ctx.NOT() != nil {
operator = qbtypes.FilterOperatorNotBetween
}
// Apply NOT context
if notContext {
operator = negateOperator(operator)
}
constraint := FieldConstraint{
Field: field,
Operator: operator,
Values: []interface{}{val1, val2},
}
d.addConstraint(constraint)
}
return nil
}
// Handle regular comparisons
values := ctx.AllValue()
if len(values) > 0 {
value := d.extractValue(values[0])
var operator qbtypes.FilterOperator
if ctx.EQUALS() != nil {
operator = qbtypes.FilterOperatorEqual
} else if ctx.NOT_EQUALS() != nil || ctx.NEQ() != nil {
operator = qbtypes.FilterOperatorNotEqual
} else if ctx.LT() != nil {
operator = qbtypes.FilterOperatorLessThan
} else if ctx.LE() != nil {
operator = qbtypes.FilterOperatorLessThanOrEq
} else if ctx.GT() != nil {
operator = qbtypes.FilterOperatorGreaterThan
} else if ctx.GE() != nil {
operator = qbtypes.FilterOperatorGreaterThanOrEq
} else if ctx.LIKE() != nil {
operator = qbtypes.FilterOperatorLike
if ctx.NOT() != nil {
operator = qbtypes.FilterOperatorNotLike
}
} else if ctx.ILIKE() != nil {
operator = qbtypes.FilterOperatorILike
if ctx.NOT() != nil {
operator = qbtypes.FilterOperatorNotILike
}
} else if ctx.REGEXP() != nil {
operator = qbtypes.FilterOperatorRegexp
if ctx.NOT() != nil {
operator = qbtypes.FilterOperatorNotRegexp
}
} else if ctx.CONTAINS() != nil {
operator = qbtypes.FilterOperatorContains
if ctx.NOT() != nil {
operator = qbtypes.FilterOperatorNotContains
}
}
if operator != qbtypes.FilterOperatorUnknown {
// Apply NOT context if needed
if notContext {
operator = negateOperator(operator)
}
constraint := FieldConstraint{
Field: field,
Operator: operator,
Value: value,
}
d.addConstraint(constraint)
}
}
// Check for contradictions after adding this constraint
d.checkContradictions(d.currentConstraints())
return nil
}
// extractValue extracts the actual value from a ValueContext
func (d *LogicalContradictionDetector) extractValue(ctx grammar.IValueContext) interface{} {
if ctx.QUOTED_TEXT() != nil {
text := ctx.QUOTED_TEXT().GetText()
// Remove quotes
if len(text) >= 2 {
return text[1 : len(text)-1]
}
return text
} else if ctx.NUMBER() != nil {
return ctx.NUMBER().GetText()
} else if ctx.BOOL() != nil {
return ctx.BOOL().GetText()
} else if ctx.KEY() != nil {
return ctx.KEY().GetText()
}
return ""
}
// extractValueList extracts values from a ValueListContext
func (d *LogicalContradictionDetector) extractValueList(ctx grammar.IValueListContext) []interface{} {
if ctx == nil {
return nil
}
values := []interface{}{}
for _, val := range ctx.AllValue() {
values = append(values, d.extractValue(val))
}
return values
}
// addConstraint adds a constraint to the current set
func (d *LogicalContradictionDetector) addConstraint(constraint FieldConstraint) {
constraints := d.currentConstraints()
// For positive operators that imply existence, add an implicit EXISTS constraint
// This mirrors the behavior of AddDefaultExistsFilter in the FilterOperator type
if constraint.Operator.AddDefaultExistsFilter() && !isNegativeOperator(constraint.Operator) {
// The field must exist for positive predicates
// This helps detect contradictions like: field = "value" AND field NOT EXISTS
existsConstraint := FieldConstraint{
Field: constraint.Field,
Operator: qbtypes.FilterOperatorExists,
}
constraints.Constraints[constraint.Field] = append(
constraints.Constraints[constraint.Field],
existsConstraint,
)
}
constraints.Constraints[constraint.Field] = append(
constraints.Constraints[constraint.Field],
constraint,
)
}
// checkContradictions checks the given constraint set for contradictions
func (d *LogicalContradictionDetector) checkContradictions(constraintSet *ConstraintSet) {
for field, constraints := range constraintSet.Constraints {
if len(constraints) < 2 {
continue
}
// Check for contradictions in this field's constraints
contradictions := d.findContradictionsInConstraints(field, constraints)
d.contradictions = append(d.contradictions, contradictions...)
}
}
// findContradictionsInConstraints checks if a set of constraints on the same field contradict
func (d *LogicalContradictionDetector) findContradictionsInConstraints(field string, constraints []FieldConstraint) []string {
contradictions := []string{}
// Group constraints by type for easier checking
var equalConstraints []FieldConstraint
var notEqualConstraints []FieldConstraint
var rangeConstraints []FieldConstraint
var inConstraints []FieldConstraint
var notInConstraints []FieldConstraint
var existsConstraints []FieldConstraint
var notExistsConstraints []FieldConstraint
var betweenConstraints []FieldConstraint
var notBetweenConstraints []FieldConstraint
var likeConstraints []FieldConstraint
for _, c := range constraints {
switch c.Operator {
case qbtypes.FilterOperatorEqual:
equalConstraints = append(equalConstraints, c)
case qbtypes.FilterOperatorNotEqual:
notEqualConstraints = append(notEqualConstraints, c)
case qbtypes.FilterOperatorIn:
inConstraints = append(inConstraints, c)
case qbtypes.FilterOperatorNotIn:
notInConstraints = append(notInConstraints, c)
case qbtypes.FilterOperatorExists:
existsConstraints = append(existsConstraints, c)
case qbtypes.FilterOperatorNotExists:
notExistsConstraints = append(notExistsConstraints, c)
case qbtypes.FilterOperatorBetween:
betweenConstraints = append(betweenConstraints, c)
case qbtypes.FilterOperatorNotBetween:
notBetweenConstraints = append(notBetweenConstraints, c)
case qbtypes.FilterOperatorLike, qbtypes.FilterOperatorILike,
qbtypes.FilterOperatorNotLike, qbtypes.FilterOperatorNotILike:
likeConstraints = append(likeConstraints, c)
default:
// Handle range operators
if isRangeOperator(c.Operator) {
rangeConstraints = append(rangeConstraints, c)
}
}
}
// Check for multiple different equality constraints
if len(equalConstraints) > 1 {
values := make(map[string]bool)
for _, c := range equalConstraints {
values[fmt.Sprintf("%v", c.Value)] = true
}
if len(values) > 1 {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' cannot equal multiple different values", field))
}
}
// Check equality vs not-equality
for _, eq := range equalConstraints {
for _, neq := range notEqualConstraints {
if fmt.Sprintf("%v", eq.Value) == fmt.Sprintf("%v", neq.Value) {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' cannot both equal and not equal '%v'", field, eq.Value))
}
}
}
// Check equality vs IN/NOT IN
for _, eq := range equalConstraints {
// Check against NOT IN
for _, notIn := range notInConstraints {
for _, v := range notIn.Values {
if fmt.Sprintf("%v", eq.Value) == fmt.Sprintf("%v", v) {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' equals '%v' but is in NOT IN list", field, eq.Value))
}
}
}
// Check against IN
for _, in := range inConstraints {
found := false
for _, v := range in.Values {
if fmt.Sprintf("%v", eq.Value) == fmt.Sprintf("%v", v) {
found = true
break
}
}
if !found {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' equals '%v' but is not in IN list", field, eq.Value))
}
}
}
// Check IN vs NOT IN overlap
for _, in := range inConstraints {
for _, notIn := range notInConstraints {
overlap := []string{}
for _, inVal := range in.Values {
for _, notInVal := range notIn.Values {
if fmt.Sprintf("%v", inVal) == fmt.Sprintf("%v", notInVal) {
overlap = append(overlap, fmt.Sprintf("%v", inVal))
}
}
}
if len(overlap) > 0 {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' has overlapping IN and NOT IN values: %v", field, overlap))
}
}
}
// Check range contradictions
if len(rangeConstraints) > 0 {
if impossible := d.checkRangeContradictions(rangeConstraints); impossible {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' has contradictory range constraints", field))
}
}
// Check equality vs range
for _, eq := range equalConstraints {
if !d.valuesSatisfyRanges(eq.Value, rangeConstraints) {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' equals '%v' which violates range constraints", field, eq.Value))
}
}
// Check EXISTS contradictions
if len(existsConstraints) > 0 && len(notExistsConstraints) > 0 {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' cannot both exist and not exist", field))
}
// Check if NOT EXISTS contradicts with operators that imply existence
if len(notExistsConstraints) > 0 {
for _, c := range constraints {
if c.Operator.AddDefaultExistsFilter() && !isNegativeOperator(c.Operator) {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' has NOT EXISTS but also has %v which implies existence",
field, c.Operator))
break
}
}
}
// Check BETWEEN contradictions - need to check if ALL ranges have a common intersection
if len(betweenConstraints) >= 2 {
if !d.hasCommonIntersection(betweenConstraints) {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' has non-overlapping BETWEEN ranges", field))
}
}
// Check BETWEEN vs equality
for _, eq := range equalConstraints {
satisfiesAny := false
for _, between := range betweenConstraints {
if d.valueSatisfiesBetween(eq.Value, between) {
satisfiesAny = true
break
}
}
if len(betweenConstraints) > 0 && !satisfiesAny {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' equals '%v' which is outside BETWEEN range(s)", field, eq.Value))
}
}
// Check NOT BETWEEN vs equality
for _, eq := range equalConstraints {
for _, notBetween := range notBetweenConstraints {
if d.valueSatisfiesBetween(eq.Value, notBetween) {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' equals '%v' which is excluded by NOT BETWEEN range", field, eq.Value))
}
}
}
// Check if BETWEEN and NOT BETWEEN ranges make it impossible to have any value
if len(betweenConstraints) > 0 && len(notBetweenConstraints) > 0 {
// Check if the NOT BETWEEN completely covers the BETWEEN range
for _, between := range betweenConstraints {
if len(between.Values) == 2 {
bMin, err1 := parseNumericValue(between.Values[0])
bMax, err2 := parseNumericValue(between.Values[1])
if err1 == nil && err2 == nil {
// Check if this BETWEEN range has any values not excluded by NOT BETWEEN
hasValidValue := false
// Simple check: see if the endpoints or midpoint are valid
testValues := []float64{bMin, bMax, (bMin + bMax) / 2}
for _, testVal := range testValues {
valid := true
for _, notBetween := range notBetweenConstraints {
if d.valueSatisfiesBetween(testVal, notBetween) {
valid = false
break
}
}
if valid {
hasValidValue = true
break
}
}
if !hasValidValue {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' has BETWEEN and NOT BETWEEN ranges that exclude all values", field))
}
}
}
}
}
// Check LIKE pattern contradictions with exact values
for _, eq := range equalConstraints {
for _, like := range likeConstraints {
if like.Operator == qbtypes.FilterOperatorLike || like.Operator == qbtypes.FilterOperatorILike {
pattern := fmt.Sprintf("%v", like.Value)
value := fmt.Sprintf("%v", eq.Value)
if !d.matchesLikePattern(value, pattern) {
contradictions = append(contradictions,
fmt.Sprintf("Field '%s' equals '%v' which doesn't match LIKE pattern '%v'",
field, eq.Value, like.Value))
}
}
}
}
return contradictions
}
// hasCommonIntersection checks if all BETWEEN ranges have a common intersection
func (d *LogicalContradictionDetector) hasCommonIntersection(betweens []FieldConstraint) bool {
if len(betweens) == 0 {
return true
}
// Find the intersection of all ranges
var intersectionMin, intersectionMax float64
initialized := false
for _, b := range betweens {
if len(b.Values) != 2 {
continue
}
min, err1 := parseNumericValue(b.Values[0])
max, err2 := parseNumericValue(b.Values[1])
if err1 != nil || err2 != nil {
continue // Skip non-numeric ranges
}
if !initialized {
intersectionMin = min
intersectionMax = max
initialized = true
} else {
// Update intersection
if min > intersectionMin {
intersectionMin = min
}
if max < intersectionMax {
intersectionMax = max
}
}
}
// If intersection is empty, ranges don't all overlap
return !initialized || intersectionMin <= intersectionMax
}
// checkRangeContradictions checks if range constraints are satisfiable
func (d *LogicalContradictionDetector) checkRangeContradictions(constraints []FieldConstraint) bool {
// We need to find if there's any value that satisfies all constraints
var lowerBounds []struct {
value float64
inclusive bool
}
var upperBounds []struct {
value float64
inclusive bool
}
for _, c := range constraints {
val, err := parseNumericValue(c.Value)
if err != nil {
continue // Skip non-numeric values
}
switch c.Operator {
case qbtypes.FilterOperatorGreaterThan:
lowerBounds = append(lowerBounds, struct {
value float64
inclusive bool
}{val, false})
case qbtypes.FilterOperatorGreaterThanOrEq:
lowerBounds = append(lowerBounds, struct {
value float64
inclusive bool
}{val, true})
case qbtypes.FilterOperatorLessThan:
upperBounds = append(upperBounds, struct {
value float64
inclusive bool
}{val, false})
case qbtypes.FilterOperatorLessThanOrEq:
upperBounds = append(upperBounds, struct {
value float64
inclusive bool
}{val, true})
}
}
// Find the most restrictive lower bound
var effectiveLower *float64
lowerInclusive := false
for _, lb := range lowerBounds {
if effectiveLower == nil || lb.value > *effectiveLower ||
(lb.value == *effectiveLower && !lb.inclusive && lowerInclusive) {
effectiveLower = &lb.value
lowerInclusive = lb.inclusive
}
}
// Find the most restrictive upper bound
var effectiveUpper *float64
upperInclusive := false
for _, ub := range upperBounds {
if effectiveUpper == nil || ub.value < *effectiveUpper ||
(ub.value == *effectiveUpper && !ub.inclusive && upperInclusive) {
effectiveUpper = &ub.value
upperInclusive = ub.inclusive
}
}
// Check if we have both bounds and they're contradictory
if effectiveLower != nil && effectiveUpper != nil {
if *effectiveLower > *effectiveUpper {
return true
}
if *effectiveLower == *effectiveUpper && (!lowerInclusive || !upperInclusive) {
return true
}
}
return false
}
// valuesSatisfyRanges checks if a value satisfies all range constraints
func (d *LogicalContradictionDetector) valuesSatisfyRanges(value interface{}, constraints []FieldConstraint) bool {
val, err := parseNumericValue(value)
if err != nil {
return true // If not numeric, we can't check
}
for _, c := range constraints {
cVal, err := parseNumericValue(c.Value)
if err != nil {
continue
}
switch c.Operator {
case qbtypes.FilterOperatorGreaterThan:
if val <= cVal {
return false
}
case qbtypes.FilterOperatorGreaterThanOrEq:
if val < cVal {
return false
}
case qbtypes.FilterOperatorLessThan:
if val >= cVal {
return false
}
case qbtypes.FilterOperatorLessThanOrEq:
if val > cVal {
return false
}
}
}
return true
}
// valueSatisfiesBetween checks if a value is within a BETWEEN range
func (d *LogicalContradictionDetector) valueSatisfiesBetween(value interface{}, between FieldConstraint) bool {
if len(between.Values) != 2 {
return false
}
val, err := parseNumericValue(value)
if err != nil {
return true // Can't check non-numeric
}
min, err1 := parseNumericValue(between.Values[0])
max, err2 := parseNumericValue(between.Values[1])
if err1 != nil || err2 != nil {
return true
}
return val >= min && val <= max
}
// matchesLikePattern is a simple pattern matcher for LIKE
func (d *LogicalContradictionDetector) matchesLikePattern(value, pattern string) bool {
// Simple implementation - just check prefix/suffix with %
if strings.HasPrefix(pattern, "%") && strings.HasSuffix(pattern, "%") {
return strings.Contains(value, pattern[1:len(pattern)-1])
} else if strings.HasPrefix(pattern, "%") {
return strings.HasSuffix(value, pattern[1:])
} else if strings.HasSuffix(pattern, "%") {
return strings.HasPrefix(value, pattern[:len(pattern)-1])
}
return value == pattern
}
// cloneConstraintSet creates a deep copy of a constraint set
func (d *LogicalContradictionDetector) cloneConstraintSet(set *ConstraintSet) *ConstraintSet {
newSet := &ConstraintSet{
Constraints: make(map[string][]FieldConstraint),
}
for field, constraints := range set.Constraints {
newConstraints := make([]FieldConstraint, len(constraints))
copy(newConstraints, constraints)
newSet.Constraints[field] = newConstraints
}
return newSet
}
// parseNumericValue attempts to parse a value as a number
func parseNumericValue(value interface{}) (float64, error) {
switch v := value.(type) {
case float64:
return v, nil
case int:
return float64(v), nil
case string:
return strconv.ParseFloat(v, 64)
default:
return 0, fmt.Errorf("not a numeric value")
}
}
// negateOperator returns the negated version of an operator
func negateOperator(op qbtypes.FilterOperator) qbtypes.FilterOperator {
switch op {
case qbtypes.FilterOperatorEqual:
return qbtypes.FilterOperatorNotEqual
case qbtypes.FilterOperatorNotEqual:
return qbtypes.FilterOperatorEqual
case qbtypes.FilterOperatorLessThan:
return qbtypes.FilterOperatorGreaterThanOrEq
case qbtypes.FilterOperatorLessThanOrEq:
return qbtypes.FilterOperatorGreaterThan
case qbtypes.FilterOperatorGreaterThan:
return qbtypes.FilterOperatorLessThanOrEq
case qbtypes.FilterOperatorGreaterThanOrEq:
return qbtypes.FilterOperatorLessThan
case qbtypes.FilterOperatorIn:
return qbtypes.FilterOperatorNotIn
case qbtypes.FilterOperatorNotIn:
return qbtypes.FilterOperatorIn
case qbtypes.FilterOperatorExists:
return qbtypes.FilterOperatorNotExists
case qbtypes.FilterOperatorNotExists:
return qbtypes.FilterOperatorExists
case qbtypes.FilterOperatorLike:
return qbtypes.FilterOperatorNotLike
case qbtypes.FilterOperatorNotLike:
return qbtypes.FilterOperatorLike
case qbtypes.FilterOperatorILike:
return qbtypes.FilterOperatorNotILike
case qbtypes.FilterOperatorNotILike:
return qbtypes.FilterOperatorILike
case qbtypes.FilterOperatorBetween:
return qbtypes.FilterOperatorNotBetween
case qbtypes.FilterOperatorNotBetween:
return qbtypes.FilterOperatorBetween
case qbtypes.FilterOperatorRegexp:
return qbtypes.FilterOperatorNotRegexp
case qbtypes.FilterOperatorNotRegexp:
return qbtypes.FilterOperatorRegexp
case qbtypes.FilterOperatorContains:
return qbtypes.FilterOperatorNotContains
case qbtypes.FilterOperatorNotContains:
return qbtypes.FilterOperatorContains
default:
return op
}
}
// isRangeOperator returns true if the operator is a range comparison operator
func isRangeOperator(op qbtypes.FilterOperator) bool {
switch op {
case qbtypes.FilterOperatorLessThan,
qbtypes.FilterOperatorLessThanOrEq,
qbtypes.FilterOperatorGreaterThan,
qbtypes.FilterOperatorGreaterThanOrEq:
return true
default:
return false
}
}
// isNegativeOperator returns true if the operator is a negative/exclusion operator
func isNegativeOperator(op qbtypes.FilterOperator) bool {
switch op {
case qbtypes.FilterOperatorNotEqual,
qbtypes.FilterOperatorNotIn,
qbtypes.FilterOperatorNotExists,
qbtypes.FilterOperatorNotLike,
qbtypes.FilterOperatorNotILike,
qbtypes.FilterOperatorNotBetween,
qbtypes.FilterOperatorNotRegexp,
qbtypes.FilterOperatorNotContains:
return true
default:
return false
}
}