package parser_test import ( "strings" "testing" "github.com/chaisql/chai/internal/expr" "github.com/chaisql/chai/internal/expr/functions" "github.com/chaisql/chai/internal/sql/parser" "github.com/chaisql/chai/internal/testutil" "github.com/chaisql/chai/internal/types" "github.com/stretchr/testify/require" ) func TestParserExpr(t *testing.T) { tests := []struct { name string s string expected expr.Expr fails bool }{ // integers {"int", "10", testutil.IntegerValue(10), false}, {"-int", "-10", testutil.IntegerValue(-10), false}, {"+int", "+10", testutil.IntegerValue(10), false}, {"> max int64 -> float64", "10000000000000000000", testutil.DoubleValue(10000000000000000000), false}, {"< min int64 -> float64", "-10000000000000000000", testutil.DoubleValue(-10000000000000000000), false}, {"very large int", "100000000000000000000000000000000000000000000000", testutil.DoubleValue(100000000000000000000000000000000000000000000000), false}, // floats {"+float64", "10.0", testutil.DoubleValue(10), false}, {"-float64", "-10.0", testutil.DoubleValue(-10), false}, // strings {"double quoted string", `'10.0'`, testutil.TextValue("10.0"), false}, {"single quoted string", "'-10.0'", testutil.TextValue("-10.0"), false}, // byteas {"bytea as hex string", `'\xff'`, testutil.ByteaValue([]byte{255}), false}, {"invalid bytea hex string", `'\xzz'`, nil, true}, // parentheses {"parentheses: empty", "()", nil, true}, {"parentheses: values", `(1)`, expr.Parentheses{ E: testutil.IntegerValue(1), }, false}, {"parentheses: expr", `(1 + true * (4 + 3))`, expr.Parentheses{ E: expr.Add( testutil.IntegerValue(1), expr.Mul( testutil.BoolValue(true), expr.Parentheses{ E: expr.Add( testutil.IntegerValue(4), testutil.IntegerValue(3), ), }, ), ), }, false}, // operators {"=", "age = 10", expr.Eq(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {"!=", "age != 10", expr.Neq(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {">", "age > 10", expr.Gt(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {">=", "age >= 10", expr.Gte(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {"<", "age < 10", expr.Lt(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {"<=", "age <= 10", expr.Lte(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {"BETWEEN", "1 BETWEEN 10 AND 11", expr.Between(testutil.IntegerValue(10))(testutil.IntegerValue(1), testutil.IntegerValue(11)), false}, {"+", "age + 10", expr.Add(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {"-", "age - 10", expr.Sub(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {"*", "age * 10", expr.Mul(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {"/", "age / 10", expr.Div(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {"%", "age % 10", expr.Mod(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {"&", "age & 10", expr.BitwiseAnd(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), false}, {"||", "name || 'foo'", expr.Concat(&expr.Column{Name: "name"}, testutil.TextValue("foo")), false}, {"IN", "age IN ages", expr.In(&expr.Column{Name: "age"}, &expr.Column{Name: "ages"}), false}, {"NOT IN", "age NOT IN ages", expr.NotIn(&expr.Column{Name: "age"}, &expr.Column{Name: "ages"}), false}, {"IS", "age IS NULL", expr.Is(&expr.Column{Name: "age"}, testutil.NullValue()), false}, {"IS NOT", "age IS NOT NULL", expr.IsNot(&expr.Column{Name: "age"}, testutil.NullValue()), false}, {"LIKE", "name LIKE 'foo'", expr.Like(&expr.Column{Name: "name"}, testutil.TextValue("foo")), false}, {"NOT LIKE", "name NOT LIKE 'foo'", expr.NotLike(&expr.Column{Name: "name"}, testutil.TextValue("foo")), false}, {"NOT =", "name NOT = 'foo'", nil, true}, {"precedence", "4 > 1 + 2", expr.Gt( testutil.IntegerValue(4), expr.Add( testutil.IntegerValue(1), testutil.IntegerValue(2), ), ), false}, {"AND", "age = 10 AND age <= 11", expr.And( expr.Eq(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), expr.Lte(&expr.Column{Name: "age"}, testutil.IntegerValue(11)), ), false}, {"OR", "age = 10 OR age = 11", expr.Or( expr.Eq(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), expr.Eq(&expr.Column{Name: "age"}, testutil.IntegerValue(11)), ), false}, {"AND then OR", "age >= 10 AND age > $4 OR age < 10.4", expr.Or( expr.And( expr.Gte(&expr.Column{Name: "age"}, testutil.IntegerValue(10)), expr.Gt(&expr.Column{Name: "age"}, expr.PositionalParam(4)), ), expr.Lt(&expr.Column{Name: "age"}, testutil.DoubleValue(10.4)), ), false}, {"with NULL", "age > NULL", expr.Gt(&expr.Column{Name: "age"}, testutil.NullValue()), false}, // unary operators {"CAST", "CAST(a AS TEXT)", &expr.Cast{Expr: &expr.Column{Name: "a"}, CastAs: types.TypeText}, false}, {"NOT", "NOT 10", expr.Not(testutil.IntegerValue(10)), false}, {"NOT", "NOT NOT", nil, true}, {"NOT", "NOT NOT 10", expr.Not(expr.Not(testutil.IntegerValue(10))), false}, {"nextval", "nextval('hello')", &functions.NextVal{Expr: testutil.TextValue("hello")}, false}, // functions {"count(expr) function", "count(a)", &functions.Count{Expr: &expr.Column{Name: "a"}}, false}, {"count(*) function", "count(*)", functions.NewCount(expr.Wildcard{}), false}, {"count (*) function with spaces", "count (*)", functions.NewCount(expr.Wildcard{}), false}, {"packaged function", "floor(1.2)", testutil.FunctionExpr(t, "floor", testutil.DoubleValue(1.2)), false}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { ex, err := parser.NewParser(strings.NewReader(test.s)).ParseExpr() if test.fails { require.Error(t, err) } else { require.NoError(t, err) if !expr.Equal(test.expected, ex) { require.EqualValues(t, test.expected, ex) } } }) } } func TestParserParams(t *testing.T) { tests := []struct { name string s string expected expr.Expr errored bool }{ {"one positional", "age = $1", expr.Eq(&expr.Column{Name: "age"}, expr.PositionalParam(1)), false}, {"multiple positional", "age >= $111 AND age <= $11", expr.And( expr.Gte(&expr.Column{Name: "age"}, expr.PositionalParam(111)), expr.Lte(&expr.Column{Name: "age"}, expr.PositionalParam(11)), ), false}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { ex, err := parser.NewParser(strings.NewReader(test.s)).ParseExpr() if test.errored { require.Error(t, err) } else { require.NoError(t, err) require.EqualValues(t, test.expected, ex) } }) } }