Files
traces/pkg/traceql/enum_operators.go
cc86e98b04 Rewrite conditions on same attribute to equivalent array operation (#6353)
* Add operators IN, NOT IN, MATCH ANY, and MATCH NONE

* Implement Static.append()

* Print string arrays with '`' instead of '"'

Example ["aa", "bb"] -> [`aa`, `bb`]

* Rewrite binary operations to array equivalent

The rewriter handles the following cases:
- { .a =  "a" || .a =  "b" } => { .a IN  ["a", "b"] }
- { .a != "a" && .a != "b" } => { .a NOT IN ["a", "b"] }
- { .a =~ "a" || .a =~ "b" } => { .a MATCH ANY ["a", "b"] }
- { .a !~ "a" && .a !~ "b" } => { .a MATCH NONE ["a", "b"] }

* Add test cases with arrays to TestBinOp

* traceql.Parse() applies AST optimizations by default

* Rename predicate_test.go to predicates_test.go

* Add array predicates to parquetquery

* BREAKING CHANGE: more precise regex validation

BinaryOperation.validate() considered everythin a valid regex where
FieldExpression.EncodeToString(false) happened to return a valid regex.
With this change the RHS of a regex operation is required to be a string
or string array

* vp4: handle predicates with arrays correctly

* vp5: handle predicates with arrays correctly

* vp3: handle predicates with arrays correctly

* Add test cases for optimized queries

* Update query test examples with optimizable queries

* vp3: fix handling of array types

* CHANGELOG.md

* Pass array operators down to fetch layer

* Read to first virtual row after seeking on a low definition level

This helps avoid additional spans/op in queries with only
resource attributes

---------

Co-authored-by: Martin Disibio <[email protected]>
2026-02-22 00:55:23 -08:00

250 lines
4.7 KiB
Go

package traceql
import "fmt"
type Operator int
const (
OpNone Operator = iota
OpAdd
OpSub
OpDiv
OpMod
OpMult
OpEqual
OpNotEqual
OpRegex
OpNotRegex
OpGreater
OpGreaterEqual
OpLess
OpLessEqual
OpPower
OpAnd
OpOr
OpNot
OpSpansetChild
OpSpansetParent
OpSpansetDescendant
OpSpansetAncestor
OpSpansetAnd
OpSpansetUnion
OpSpansetSibling
OpSpansetNotChild
OpSpansetNotParent
OpSpansetNotSibling
OpSpansetNotAncestor
OpSpansetNotDescendant
OpSpansetUnionChild
OpSpansetUnionParent
OpSpansetUnionSibling
OpSpansetUnionAncestor
OpSpansetUnionDescendant
// The following operators are used internally and only exist in the AST. They are not parseable in TraceQL
OpExists
OpNotExists
OpIn
OpNotIn
OpRegexMatchAny
OpRegexMatchNone
)
func (op Operator) isBoolean() bool {
return op == OpOr ||
op == OpAnd ||
op == OpEqual ||
op == OpNotEqual ||
op == OpRegex ||
op == OpNotRegex ||
op == OpGreater ||
op == OpGreaterEqual ||
op == OpLess ||
op == OpLessEqual ||
op == OpNot ||
op == OpExists ||
op == OpNotExists ||
op == OpIn ||
op == OpNotIn ||
op == OpRegexMatchAny ||
op == OpRegexMatchNone
}
func (op Operator) binaryTypesValid(lhsT StaticType, rhsT StaticType) bool {
return binaryTypeValid(op, lhsT) && binaryTypeValid(op, rhsT)
}
func binaryTypeValid(op Operator, t StaticType) bool {
if t == TypeAttribute {
return true
}
switch t {
case TypeBoolean, TypeBooleanArray:
return op == OpAnd ||
op == OpOr ||
op == OpEqual ||
op == OpNotEqual ||
op == OpIn ||
op == OpNotIn
case TypeFloat, TypeFloatArray, TypeInt, TypeIntArray, TypeDuration:
return op == OpAdd ||
op == OpSub ||
op == OpMult ||
op == OpDiv ||
op == OpMod ||
op == OpPower ||
op == OpEqual ||
op == OpNotEqual ||
op == OpGreater ||
op == OpGreaterEqual ||
op == OpLess ||
op == OpLessEqual ||
op == OpIn ||
op == OpNotIn
case TypeString, TypeStringArray:
return op == OpEqual ||
op == OpNotEqual ||
op == OpRegex ||
op == OpNotRegex ||
op == OpGreater ||
op == OpGreaterEqual ||
op == OpLess ||
op == OpLessEqual ||
op == OpIn ||
op == OpNotIn ||
op == OpRegexMatchAny ||
op == OpRegexMatchNone
case TypeNil, TypeStatus, TypeKind:
return op == OpEqual || op == OpNotEqual
}
return false
}
func (op Operator) unaryTypesValid(t StaticType) bool {
if t == TypeAttribute {
return true
}
switch op {
case OpSub:
return t.isNumeric()
case OpNot:
return t == TypeBoolean
case OpExists:
return true
case OpNotExists:
return true
}
return false
}
// isArrayOp returns true if the operator is a dedicated array operator like IN, NOT IN, MATCH ANY, or MATCH NONE. It
// returns false for all other operators, even if those operators can operate on arrays like = or !=.
func (op Operator) isArrayOp() bool {
return op == OpIn || op == OpNotIn || op == OpRegexMatchAny || op == OpRegexMatchNone
}
// toElementOp returns the equivalent element operator for the given array operator
func (op Operator) toElementOp() Operator {
switch op {
case OpIn:
return OpEqual
case OpNotIn:
return OpNotEqual
case OpRegexMatchAny:
return OpRegex
case OpRegexMatchNone:
return OpNotRegex
default:
return op
}
}
func (op Operator) String() string {
switch op {
case OpAdd:
return "+"
case OpSub:
return "-"
case OpDiv:
return "/"
case OpMod:
return "%"
case OpMult:
return "*"
case OpEqual:
return "="
case OpNotEqual:
return "!="
case OpRegex:
return "=~"
case OpNotRegex:
return "!~"
case OpGreater:
return ">"
case OpGreaterEqual:
return ">="
case OpLess:
return "<"
case OpLessEqual:
return "<="
case OpPower:
return "^"
case OpAnd:
return "&&"
case OpOr:
return "||"
case OpNot:
return "!"
case OpSpansetChild:
return ">"
case OpSpansetParent:
return "<"
case OpSpansetDescendant:
return ">>"
case OpSpansetAncestor:
return "<<"
case OpSpansetAnd:
return "&&"
case OpSpansetSibling:
return "~"
case OpSpansetUnion:
return "||"
case OpSpansetNotChild:
return "!>"
case OpSpansetNotParent:
return "!<"
case OpSpansetNotSibling:
return "!~"
case OpSpansetNotAncestor:
return "!<<"
case OpSpansetNotDescendant:
return "!>>"
case OpSpansetUnionChild:
return "&>"
case OpSpansetUnionParent:
return "&<"
case OpSpansetUnionSibling:
return "&~"
case OpSpansetUnionAncestor:
return "&<<"
case OpSpansetUnionDescendant:
return "&>>"
// Operators IN, NOT IN, MATCH ANY, and MATCH NONE do not exist in the TraceQL syntax (only used internally)
case OpIn:
return "IN"
case OpNotIn:
return "NOT IN"
case OpRegexMatchAny:
return "MATCH ANY"
case OpRegexMatchNone:
return "MATCH NONE"
}
return fmt.Sprintf("operator(%d)", op)
}