-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(traceql): parse autocomplete queries
- Loading branch information
Showing
3 changed files
with
329 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package traceql | ||
|
||
import "github.com/go-faster/oteldb/internal/traceql/lexer" | ||
|
||
// Autocomplete is a AND set of spanset matchers. | ||
type Autocomplete struct { | ||
Matchers []BinaryFieldExpr | ||
} | ||
|
||
// ParseAutocomplete parses matchers from potentially uncomplete TraceQL spanset filter from string. | ||
func ParseAutocomplete(input string) (c Autocomplete) { | ||
p, err := newParser(input) | ||
if err != nil { | ||
return c | ||
} | ||
|
||
if err := p.consume(lexer.OpenBrace); err != nil { | ||
return c | ||
} | ||
|
||
for { | ||
left, ok, err := parseSimpleFieldExpr(&p) | ||
if err != nil || !ok { | ||
return c | ||
} | ||
|
||
op, ok := p.peekBinaryOp() | ||
if !ok || !(op.IsOrdering() || op.IsRegex()) { | ||
return c | ||
} | ||
// Consume op. | ||
p.next() | ||
|
||
right, ok, err := parseSimpleFieldExpr(&p) | ||
switch { | ||
case err != nil: | ||
return c | ||
case !ok: | ||
// Handle cases like `{ .foo = <missing field> && .bar = 10 }`. | ||
op, ok := p.peekBinaryOp() | ||
if !ok { | ||
return c | ||
} | ||
if op != OpAnd { | ||
return Autocomplete{} | ||
} | ||
default: | ||
c.Matchers = append(c.Matchers, BinaryFieldExpr{ | ||
Left: left, | ||
Op: op, | ||
Right: right, | ||
}) | ||
} | ||
|
||
switch t := p.peek(); t.Type { | ||
case lexer.EOF: | ||
return c | ||
case lexer.CloseBrace: | ||
p.next() | ||
return c | ||
default: | ||
op, ok := p.peekBinaryOp() | ||
if !ok { | ||
return c | ||
} | ||
if op != OpAnd { | ||
return Autocomplete{} | ||
} | ||
// Consume op. | ||
p.next() | ||
} | ||
} | ||
} | ||
|
||
func parseSimpleFieldExpr(p *parser) (FieldExpr, bool, error) { | ||
switch s, ok, err := p.tryStatic(); { | ||
case err != nil: | ||
return nil, false, err | ||
case ok: | ||
return s, true, nil | ||
} | ||
|
||
switch a, ok, err := p.tryAttribute(); { | ||
case err != nil: | ||
return nil, false, err | ||
case ok: | ||
return a, true, nil | ||
} | ||
|
||
return nil, false, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package traceql | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestParseAutocomplete(t *testing.T) { | ||
tests := []struct { | ||
input string | ||
want []BinaryFieldExpr | ||
}{ | ||
{ | ||
``, | ||
nil, | ||
}, | ||
{ | ||
`{}`, | ||
nil, | ||
}, | ||
{ | ||
`{ .a = }`, | ||
nil, | ||
}, | ||
{ | ||
`{ .a = `, | ||
nil, | ||
}, | ||
// Simple cases. | ||
{ | ||
`{ .a = 10 }`, | ||
[]BinaryFieldExpr{ | ||
{ | ||
Left: &Attribute{Name: "a"}, | ||
Op: OpEq, | ||
Right: &Static{Type: TypeInt, Data: 10}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
`{ .a = 10 && .b =~ "foo.+" }`, | ||
[]BinaryFieldExpr{ | ||
{ | ||
Left: &Attribute{Name: "a"}, | ||
Op: OpEq, | ||
Right: &Static{Type: TypeInt, Data: 10}, | ||
}, | ||
{ | ||
Left: &Attribute{Name: "b"}, | ||
Op: OpRe, | ||
Right: &Static{Type: TypeString, Str: "foo.+"}, | ||
}, | ||
}, | ||
}, | ||
// Missing brace. | ||
{ | ||
`{ .a = 10 `, | ||
[]BinaryFieldExpr{ | ||
{ | ||
Left: &Attribute{Name: "a"}, | ||
Op: OpEq, | ||
Right: &Static{Type: TypeInt, Data: 10}, | ||
}, | ||
}, | ||
}, | ||
// Missing sub-expression. | ||
{ | ||
`{ .a = && .b = 20 && .c = 30 }`, | ||
[]BinaryFieldExpr{ | ||
{ | ||
Left: &Attribute{Name: "b"}, | ||
Op: OpEq, | ||
Right: &Static{Type: TypeInt, Data: 20}, | ||
}, | ||
{ | ||
Left: &Attribute{Name: "c"}, | ||
Op: OpEq, | ||
Right: &Static{Type: TypeInt, Data: 30}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
`{ .a = 10 && .b = && .c = 30 }`, | ||
[]BinaryFieldExpr{ | ||
{ | ||
Left: &Attribute{Name: "a"}, | ||
Op: OpEq, | ||
Right: &Static{Type: TypeInt, Data: 10}, | ||
}, | ||
{ | ||
Left: &Attribute{Name: "c"}, | ||
Op: OpEq, | ||
Right: &Static{Type: TypeInt, Data: 30}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
`{ .a = 10 && .b = 20 && .c = }`, | ||
[]BinaryFieldExpr{ | ||
{ | ||
Left: &Attribute{Name: "a"}, | ||
Op: OpEq, | ||
Right: &Static{Type: TypeInt, Data: 10}, | ||
}, | ||
{ | ||
Left: &Attribute{Name: "b"}, | ||
Op: OpEq, | ||
Right: &Static{Type: TypeInt, Data: 20}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
`{ .a = && .b = && .c = 30 }`, | ||
[]BinaryFieldExpr{ | ||
{ | ||
Left: &Attribute{Name: "c"}, | ||
Op: OpEq, | ||
Right: &Static{Type: TypeInt, Data: 30}, | ||
}, | ||
}, | ||
}, | ||
// Contains OR operation. | ||
{ | ||
`{ .a = 10 && .b = 20 || .c = 30 }`, | ||
nil, | ||
}, | ||
{ | ||
`{ .a = 10 && .b = || .c = 30 }`, | ||
nil, | ||
}, | ||
// Complicated sub-expression. | ||
{ | ||
`{ .status = 2*100 && .baz = 10 }`, | ||
nil, | ||
}, | ||
{ | ||
`{ .status / 100 = 2 && .baz = 10 }`, | ||
nil, | ||
}, | ||
} | ||
for i, tt := range tests { | ||
tt := tt | ||
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) { | ||
defer func() { | ||
if t.Failed() { | ||
t.Logf("Input: %#q", tt.input) | ||
} | ||
}() | ||
|
||
got := ParseAutocomplete(tt.input) | ||
require.Equal(t, tt.want, got.Matchers) | ||
}) | ||
} | ||
} |
Oops, something went wrong.