Skip to content

Commit

Permalink
feat(traceql): parse autocomplete queries
Browse files Browse the repository at this point in the history
  • Loading branch information
tdakkota committed May 23, 2024
1 parent b11e9e2 commit 4f5cf41
Show file tree
Hide file tree
Showing 3 changed files with 329 additions and 66 deletions.
91 changes: 91 additions & 0 deletions internal/traceql/autocomplete.go
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
}
156 changes: 156 additions & 0 deletions internal/traceql/autocomplete_test.go
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)
})
}
}
Loading

0 comments on commit 4f5cf41

Please sign in to comment.