Skip to content

Commit

Permalink
Tweak traceql parsing to correctly parse binary operations without sp…
Browse files Browse the repository at this point in the history
…ace (#1941)

* Tweak traceql parsing to correctly parse some binary operations without space

* changelog
  • Loading branch information
mdisibio authored Dec 7, 2022
1 parent a3e84a6 commit e9b4391
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Internal types are updated to use `scope` instead of `instrumentation_library`.
* [BUGFIX] tempo-mixin: tweak dashboards to support metrics without `cluster` label present [#1913](https://github.com/grafana/tempo/pull/1913) (@kvrhdn)
* [ENHANCEMENT] New tenant dashboard [#1901](https://github.com/grafana/tempo/pull/1901) (@mapno)
* [BUGFIX] Fix docker-compose examples not running on Apple M1 hardware [#1920](https://github.com/grafana/tempo/pull/1920) (@stoewer)
* [BUGFIX] Fix traceql parsing of most binary operations to not require spacing [#1939](https://github.com/grafana/tempo/pull/1941) (@mdisibio)

## v1.5.0 / 2022-08-17

Expand Down
16 changes: 10 additions & 6 deletions pkg/traceql/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,16 @@ func isDurationRune(r rune) bool {
}

func isAttributeRune(r rune) bool {
return !unicode.IsSpace(r) &&
r != scanner.EOF &&
r != '(' &&
r != ')' &&
r != '}' &&
r != '{'
if unicode.IsSpace(r) {
return false
}

switch r {
case scanner.EOF, '{', '}', '(', ')', '=', '~', '!', '<', '>', '&', '|', '^':
return false
default:
return true
}
}

func startsAttribute(tok int) bool {
Expand Down
6 changes: 6 additions & 0 deletions pkg/traceql/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,9 @@ func testLexer(t *testing.T, tcs []lexerTestCase) {
})
}
}

func BenchmarkIsAttributeRune(b *testing.B) {
for i := 0; i < b.N; i++ {
isAttributeRune('=')
}
}
49 changes: 31 additions & 18 deletions pkg/traceql/parse_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package traceql

import (
"strings"
"testing"
"time"

Expand Down Expand Up @@ -646,36 +647,48 @@ func TestSpansetFilterStatics(t *testing.T) {

func TestSpansetFilterOperators(t *testing.T) {
tests := []struct {
in string
err error
expected FieldExpression
in string
expected FieldExpression
alsoTestWithoutSpace bool
}{
{in: "{ .a + .b }", expected: newBinaryOperation(OpAdd, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a - .b }", expected: newBinaryOperation(OpSub, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a / .b }", expected: newBinaryOperation(OpDiv, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a % .b }", expected: newBinaryOperation(OpMod, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a * .b }", expected: newBinaryOperation(OpMult, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a = .b }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a != .b }", expected: newBinaryOperation(OpNotEqual, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a =~ .b }", expected: newBinaryOperation(OpRegex, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a !~ .b }", expected: newBinaryOperation(OpNotRegex, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a > .b }", expected: newBinaryOperation(OpGreater, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a >= .b }", expected: newBinaryOperation(OpGreaterEqual, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a < .b }", expected: newBinaryOperation(OpLess, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a <= .b }", expected: newBinaryOperation(OpLessEqual, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a ^ .b }", expected: newBinaryOperation(OpPower, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a && .b }", expected: newBinaryOperation(OpAnd, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a || .b }", expected: newBinaryOperation(OpOr, NewAttribute("a"), NewAttribute("b"))},
{in: "{ .a = .b }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ .a != .b }", expected: newBinaryOperation(OpNotEqual, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ .a =~ .b }", expected: newBinaryOperation(OpRegex, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ .a !~ .b }", expected: newBinaryOperation(OpNotRegex, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ .a > .b }", expected: newBinaryOperation(OpGreater, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ .a >= .b }", expected: newBinaryOperation(OpGreaterEqual, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ .a < .b }", expected: newBinaryOperation(OpLess, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ .a <= .b }", expected: newBinaryOperation(OpLessEqual, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ .a ^ .b }", expected: newBinaryOperation(OpPower, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ .a && .b }", expected: newBinaryOperation(OpAnd, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ .a || .b }", expected: newBinaryOperation(OpOr, NewAttribute("a"), NewAttribute("b")), alsoTestWithoutSpace: true},
{in: "{ !.b }", expected: newUnaryOperation(OpNot, NewAttribute("b"))},
{in: "{ -.b }", expected: newUnaryOperation(OpSub, NewAttribute("b"))},

// Against statics
{in: "{ .a = `foo` }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewStaticString("foo")), alsoTestWithoutSpace: true},
{in: "{ .a = 3 }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewStaticInt(3)), alsoTestWithoutSpace: true},
{in: "{ .a = 3.0 }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewStaticFloat(3)), alsoTestWithoutSpace: true},
{in: "{ .a = true }", expected: newBinaryOperation(OpEqual, NewAttribute("a"), NewStaticBool(true)), alsoTestWithoutSpace: true},
}

test := func(q string, expected FieldExpression) {
actual, err := Parse(q)
require.NoError(t, err, q)
require.Equal(t, &RootExpr{newPipeline(newSpansetFilter(expected))}, actual, q)
}

for _, tc := range tests {
t.Run(tc.in, func(t *testing.T) {
actual, err := Parse(tc.in)

require.NoError(t, err)
require.Equal(t, &RootExpr{newPipeline(newSpansetFilter(tc.expected))}, actual)
test(tc.in, tc.expected)
if tc.alsoTestWithoutSpace {
test(strings.ReplaceAll(tc.in, " ", ""), tc.expected)
}
})
}
}
Expand Down

0 comments on commit e9b4391

Please sign in to comment.