diff --git a/docs/docs/concepts/selectors.mdx b/docs/docs/concepts/selectors.mdx index 0b8733bfce..8f69462772 100644 --- a/docs/docs/concepts/selectors.mdx +++ b/docs/docs/concepts/selectors.mdx @@ -1381,3 +1381,18 @@ flowchart TD class F parentSpan class G selectedSpan ``` + +## Supported comparisons + +In previous examples, It was always used the equality comparator (`=`), however, the selector language supports a number of comparisons: + +| Operation | Description | +| :------------ | :--------------------------------------------------------------- | +| `=` | both values are equal | +| `!=` | both values are different | +| `<` | value on the left is less than the value on the right | +| `<=` | value on the left is less or equal to the value on the right | +| `>` | value on the left is greater than the value on the right | +| `>=` | value on the left is greater or equal to the value on the right | +| `contains` | string on the left contains the string on the right | +| `not-contains`| string on the left does not contain the string on the right | diff --git a/server/assertions/selectors/builder_test.go b/server/assertions/selectors/builder_test.go index 3b247705af..ee00c69a6b 100644 --- a/server/assertions/selectors/builder_test.go +++ b/server/assertions/selectors/builder_test.go @@ -39,6 +39,11 @@ func TestSimpleSelectorBuilder(t *testing.T) { Expression: "span.tracetest.span.type=\"http\"", ShouldSucceed: false, }, + { + Name: "Selector with invalid syntax", + Expression: "span[attr.bool=true]", + ShouldSucceed: true, + }, } for _, testCase := range testCases { diff --git a/server/assertions/selectors/parser.go b/server/assertions/selectors/parser.go index 1f3f0591cf..16b11ac4a6 100644 --- a/server/assertions/selectors/parser.go +++ b/server/assertions/selectors/parser.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/alecthomas/participle/v2" + "github.com/alecthomas/participle/v2/lexer" ) type ParserSelector struct { @@ -18,14 +19,14 @@ type parserSpanSelector struct { type parserFilter struct { Property string `( @Ident ( @"." @Ident )*)` - Operator string `@("=" | "contains" )` + Operator string `@Comparator` Value *parserValue `@@*` } type parserValue struct { String *string ` @String` - Int *int64 ` | @Int` Float *float64 ` | @Float` + Int *int64 ` | @Int` Boolean *bool ` | @("true" | "false")` } @@ -34,8 +35,21 @@ type parserPseudoClass struct { Value *parserValue `("(" @@* ")")*` } +var languageLexer = lexer.MustStateful(lexer.Rules{ + "Root": { + {Name: "whitespace", Pattern: `\s+`, Action: nil}, + {Name: "Punc", Pattern: `[(),|\[\]:]`, Action: nil}, + + {Name: "Comparator", Pattern: `!=|<=|>=|=|<|>|contains|not-contains`}, + {Name: "Ident", Pattern: `[a-zA-Z][a-zA-Z0-9_\.]*`, Action: nil}, + {Name: "String", Pattern: `"(\\"|[^"])*"`, Action: nil}, + {Name: "Float", Pattern: `[0-9]+\.[0-9]+`}, + {Name: "Int", Pattern: `[0-9]+`}, + }, +}) + func CreateParser() (*participle.Parser, error) { - parser, err := participle.Build(&ParserSelector{}) + parser, err := participle.Build(&ParserSelector{}, participle.Lexer(languageLexer), participle.UseLookahead(2)) if err != nil { return nil, fmt.Errorf("could not create parser: %w", err) } diff --git a/server/assertions/selectors/selector_test.go b/server/assertions/selectors/selector_test.go index c59ac79cdf..da90f6cd53 100644 --- a/server/assertions/selectors/selector_test.go +++ b/server/assertions/selectors/selector_test.go @@ -142,6 +142,16 @@ func TestSelector(t *testing.T) { Expression: `span[kind="db"]`, ExpectedSpanIds: []trace.SpanID{insertPokemonDatabaseSpanID, updatePokemonDatabaseSpanID}, }, + { + Name: "SelectorShouldMatchAllButDB", + Expression: `span[kind!="db"]`, + ExpectedSpanIds: []trace.SpanID{postImportSpanID, getPokemonFromExternalAPISpanID}, + }, + { + Name: "SelectorShouldGetSpansWithAttribute", + Expression: `span[kind!=""]`, + ExpectedSpanIds: []trace.SpanID{postImportSpanID, insertPokemonDatabaseSpanID, getPokemonFromExternalAPISpanID, updatePokemonDatabaseSpanID}, + }, } for _, testCase := range testCases {