diff --git a/pkg/ql/ast_nodes.go b/pkg/ql/ast_nodes.go index dd7b2382f..758389fd9 100644 --- a/pkg/ql/ast_nodes.go +++ b/pkg/ql/ast_nodes.go @@ -3,6 +3,7 @@ package ql import ( "fmt" "github.com/Masterminds/squirrel" + "strings" ) // SelectStatement represents a SQL SELECT statement. @@ -65,6 +66,29 @@ type ( } ) +var ( + operators = map[string]bool{ + `=`: true, + `!=`: true, + `<`: true, + `>`: true, + `>=`: true, + `<=`: true, + `<>`: true, + `+`: true, + `-`: true, + `*`: true, + `/`: true, + `AND`: true, + `OR`: true, + `XOR`: true, + `NOT LIKE`: true, + `LIKE`: true, + `IS`: true, + `IS NOT`: true, + } +) + func (n LNull) Validate() (err error) { return } func (n LNull) String() string { return "NULL" } @@ -83,8 +107,13 @@ func (n LString) String() string { return fmt.Sprintf("%q", n.Value) } func (n LNumber) Validate() (err error) { return } func (n LNumber) String() string { return n.Value } -func (n Operator) Validate() (err error) { return } -func (n Operator) String() string { return n.Kind } +func (n Operator) Validate() (err error) { + if !operators[strings.ToUpper(n.Kind)] { + return fmt.Errorf("unknown operator '%s'", n.Kind) + } + return +} +func (n Operator) String() string { return n.Kind } func (n Keyword) Validate() (err error) { return } func (n Keyword) String() string { return n.Keyword } @@ -98,7 +127,10 @@ func (n Function) String() string { return fmt.Sprintf("%s(%s)", n.Name, func (n Ident) Validate() (err error) { return } func (n Ident) String() string { return n.Value } -func (n Column) Validate() (err error) { return } +func (n Column) Validate() (err error) { + return n.Expr.Validate() +} + func (n Column) String() (out string) { out = n.Expr.String() if n.Alias != "" { @@ -158,7 +190,15 @@ func (nn ASTSet) String() (out string) { return } -func (nn Columns) Validate() (err error) { return } +func (nn Columns) Validate() (err error) { + for _, n := range nn { + if err = n.Validate(); err != nil { + return + } + } + + return +} func (nn Columns) String() (out string) { for i, n := range nn { if i > 0 { diff --git a/pkg/ql/ast_parser_test.go b/pkg/ql/ast_parser_test.go index 0362ce608..61f49c004 100644 --- a/pkg/ql/ast_parser_test.go +++ b/pkg/ql/ast_parser_test.go @@ -2,6 +2,7 @@ package ql import ( "fmt" + "github.com/stretchr/testify/require" "reflect" "testing" ) @@ -297,7 +298,7 @@ func TestAstParser_ColumnParser(t *testing.T) { var tests = []struct { in string cols Columns - err error + err string }{ { in: `a AS b`, @@ -370,15 +371,43 @@ func TestAstParser_ColumnParser(t *testing.T) { }, }, }, + { + in: `-- - DATE_FORMAT(some_date, '%Y-%m-01')`, + cols: Columns{ + Column{ + Expr: ASTNodes{ + Function{ + Name: "DATE_FORMAT", + Arguments: ASTSet{ + Ident{Value: "some_date"}, + LString{Value: "%Y-%m-01"}, + }, + }, + }, + }, + }, + err: "unknown operator '-- -'", + }, } - p := NewParser() - for i, test := range tests { - if cols, err := p.ParseColumns(test.in); err != test.err { - t.Fatalf("%d. %s: error mismatch:\n expected: %v\n got: %v\n\n", i, test.in, test.err, err) - } else if test.err == nil && !reflect.DeepEqual(test.cols, cols) { - t.Errorf("%d. %s\n\ncols does not match:\n\nexpected: %#v\n got: %#v\n\n", i, test.in, test.cols, cols) - } + for _, test := range tests { + t.Run(test.in, func(t *testing.T) { + var ( + p = NewParser() + req = require.New(t) + ) + + cols, err := p.ParseColumns(test.in) + if test.err == "" { + req.NoError(err) + } else { + req.Error(err, test.err) + } + + if err == nil { + req.Equal(test.cols, cols) + } + }) } } @@ -400,7 +429,6 @@ func TestAstParser_IdentModifier(t *testing.T) { ident.Value = fmt.Sprintf("__wrap_%s_wrap__", ident.Value) return ident, nil } - if tree, err := p.ParseExpression(test.in); err != test.err { t.Fatalf("%d. error mismatch:\n expected: %v\n got: %v\n\n", i, test.err, err) } else if test.err == nil && test.out != tree.String() {