diff --git a/pkg/ql/ast_nodes.go b/pkg/ql/ast_nodes.go index 067cd6a91..f9e8bac19 100644 --- a/pkg/ql/ast_nodes.go +++ b/pkg/ql/ast_nodes.go @@ -20,14 +20,15 @@ type ( Columns []Column - Null struct{} + LNull struct{} + LBoolean struct{ Value bool } - String struct { + LString struct { Value string Args []interface{} } - Number struct { + LNumber struct { Value string } @@ -60,14 +61,23 @@ type ( } ) -func (n Null) Validate() (err error) { return } -func (n Null) String() string { return "NULL" } +func (n LNull) Validate() (err error) { return } +func (n LNull) String() string { return "NULL" } -func (n String) Validate() (err error) { return } -func (n String) String() string { return fmt.Sprintf("%q", n.Value) } +func (n LBoolean) Validate() (err error) { return } +func (n LBoolean) String() string { + if n.Value { + return "TRUE" + } else { + return "FALSE" + } +} -func (n Number) Validate() (err error) { return } -func (n Number) String() string { return n.Value } +func (n LString) Validate() (err error) { return } +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 } diff --git a/pkg/ql/ast_parser.go b/pkg/ql/ast_parser.go index b6e37b6bc..2aaa6edfd 100644 --- a/pkg/ql/ast_parser.go +++ b/pkg/ql/ast_parser.go @@ -166,8 +166,12 @@ checkToken: list = append(list, ident) goto next } - case NULL: - list = append(list, Null{}) + case LNULL: + list = append(list, LNull{}) + goto next + case LBOOL: + list = append(list, LBoolean{Value: strings.ToUpper(t.literal) == "TRUE"}) + goto next case OPERATOR: if len(list) > 0 { // Merge with previous operator node @@ -186,11 +190,11 @@ checkToken: list = append(list, keyword) } goto next - case NUMBER: - list = append(list, Number{Value: t.literal}) + case LNUMBER: + list = append(list, LNumber{Value: t.literal}) goto next - case STRING: - list = append(list, String{Value: t.literal}) + case LSTRING: + list = append(list, LString{Value: t.literal}) goto next case PARENTHESIS_OPEN: depth := p.level @@ -258,11 +262,11 @@ next: p.level++ parenthesisOpened = true goto next - case NUMBER: - list = append(list, Number{Value: t.literal}) + case LNUMBER: + list = append(list, LNumber{Value: t.literal}) goto next - case STRING: - list = append(list, String{Value: t.literal}) + case LSTRING: + list = append(list, LString{Value: t.literal}) goto next case OPERATOR: list = append(list, Operator{Kind: t.literal}) diff --git a/pkg/ql/ast_parser_test.go b/pkg/ql/ast_parser_test.go index 65016a71e..e85c96e45 100644 --- a/pkg/ql/ast_parser_test.go +++ b/pkg/ql/ast_parser_test.go @@ -29,7 +29,7 @@ func TestAstParser_Parser(t *testing.T) { ASTNodes{ Ident{Value: "arg2"}, Operator{Kind: "/"}, - Number{Value: "100"}, + LNumber{Value: "100"}, }, }, }, @@ -42,7 +42,7 @@ func TestAstParser_Parser(t *testing.T) { ASTNodes{ Ident{Value: "arg1"}, Operator{Kind: "*"}, - Number{Value: "5"}, + LNumber{Value: "5"}, }, }, }, @@ -50,9 +50,9 @@ func TestAstParser_Parser(t *testing.T) { ASTNodes{ Ident{Value: "arg2"}, Operator{Kind: "/"}, - Number{Value: "100"}, + LNumber{Value: "100"}, Operator{Kind: "+"}, - Number{Value: "10"}, + LNumber{Value: "10"}, }, }, }, @@ -63,7 +63,7 @@ func TestAstParser_Parser(t *testing.T) { Name: "date_format", Arguments: ASTSet{ Ident{Value: "created_at"}, - String{Value: "%Y"}, + LString{Value: "%Y"}, }, }, }, @@ -92,7 +92,7 @@ func TestAstParser_Parser(t *testing.T) { }, }, Operator{Kind: "!="}, - Number{Value: "2010"}, + LNumber{Value: "2010"}, }, }, { @@ -106,7 +106,7 @@ func TestAstParser_Parser(t *testing.T) { }, }, Operator{Kind: "!="}, - Number{Value: "2010"}, + LNumber{Value: "2010"}, Operator{"AND"}, Function{ Name: "month", @@ -115,7 +115,7 @@ func TestAstParser_Parser(t *testing.T) { }, }, Operator{Kind: "="}, - Number{Value: "6"}, + LNumber{Value: "6"}, }, }, { @@ -126,7 +126,7 @@ func TestAstParser_Parser(t *testing.T) { Operator{Kind: "="}, Function{Name: "year", Arguments: ASTSet{Function{Name: "now"}}}, Operator{Kind: "-"}, - Number{Value: "1"}, + LNumber{Value: "1"}, }, }, { @@ -144,7 +144,7 @@ func TestAstParser_Parser(t *testing.T) { tree: ASTNodes{ Ident{Value: "foo"}, Operator{Kind: "LIKE"}, - String{Value: "bar%"}, + LString{Value: "bar%"}, }, }, { @@ -153,7 +153,7 @@ func TestAstParser_Parser(t *testing.T) { tree: ASTNodes{ Ident{Value: "foo"}, Operator{Kind: "NOT LIKE"}, - String{Value: "bar%"}, + LString{Value: "bar%"}, }, }, { @@ -162,7 +162,7 @@ func TestAstParser_Parser(t *testing.T) { tree: ASTNodes{ Ident{Value: "foo"}, Operator{Kind: "="}, - Null{}, + LNull{}, }, }, { @@ -171,7 +171,7 @@ func TestAstParser_Parser(t *testing.T) { tree: ASTNodes{ Ident{Value: "foo"}, Operator{Kind: "IS NOT"}, - Null{}, + LNull{}, }, }, { @@ -180,7 +180,7 @@ func TestAstParser_Parser(t *testing.T) { tree: ASTNodes{ Ident{Value: "foo"}, Operator{Kind: "IS"}, - Null{}, + LNull{}, }, }, { @@ -216,6 +216,24 @@ func TestAstParser_Parser(t *testing.T) { Ident{Value: "foo3"}, }, }, + { + parser: NewParser().ParseExpression, + in: `false AND true`, + tree: ASTNodes{ + LBoolean{Value: false}, + Operator{"AND"}, + LBoolean{Value: true}, + }, + }, + { + parser: NewParser().ParseExpression, + in: `false AND true`, + tree: ASTNodes{ + LBoolean{Value: false}, + Operator{"AND"}, + LBoolean{Value: true}, + }, + }, // @todo support IN (....) //{ // parser: NewParser().ParseExpression, @@ -224,9 +242,9 @@ func TestAstParser_Parser(t *testing.T) { // Ident{Value: "abc"}, // Operator{"IN"}, // ASTNodes{ - // Number{Value: "1"}, - // Number{Value: "2"}, - // Number{Value: "3"}, + // LNumber{Value: "1"}, + // LNumber{Value: "2"}, + // LNumber{Value: "3"}, // }, // }, //}, @@ -333,7 +351,7 @@ func TestAstParser_ColumnParser(t *testing.T) { Name: "DATE_FORMAT", Arguments: ASTSet{ Ident{Value: "some_date"}, - String{Value: "%Y-%m-01"}, + LString{Value: "%Y-%m-01"}, }, }, }, diff --git a/pkg/ql/lexer_test.go b/pkg/ql/lexer_test.go index e7f1633e5..66381b537 100644 --- a/pkg/ql/lexer_test.go +++ b/pkg/ql/lexer_test.go @@ -27,10 +27,10 @@ func TestScanner_ScanSimple(t *testing.T) { {s: `>=`, tok: OPERATOR, lit: ">="}, {s: `<>`, tok: OPERATOR, lit: "<>"}, {s: `+`, tok: OPERATOR, lit: "+"}, - {s: `'fooo'`, tok: STRING, lit: "fooo"}, - {s: `'escaped \' quote'`, tok: STRING, lit: "escaped ' quote"}, - {s: `'double \\ escape'`, tok: STRING, lit: "double \\ escape"}, - {s: `12345`, tok: NUMBER, lit: "12345"}, + {s: `'fooo'`, tok: LSTRING, lit: "fooo"}, + {s: `'escaped \' quote'`, tok: LSTRING, lit: "escaped ' quote"}, + {s: `'double \\ escape'`, tok: LSTRING, lit: "double \\ escape"}, + {s: `12345`, tok: LNUMBER, lit: "12345"}, // Identifiers {s: `foo`, tok: IDENT, lit: `foo`}, @@ -39,6 +39,9 @@ func TestScanner_ScanSimple(t *testing.T) { // Parenthesis {s: `(`, tok: PARENTHESIS_OPEN, lit: `(`}, {s: `)`, tok: PARENTHESIS_CLOSE, lit: `)`}, + + // Literals + {s: `true`, tok: LBOOL, lit: `TRUE`}, } for i, test := range tests { @@ -63,15 +66,15 @@ func TestScanner_ScanComplex(t *testing.T) { {`arg1 * arg2`, []tokenCode{IDENT, WS, OPERATOR, WS, IDENT}}, {`date_format(created_at,'%Y')`, - []tokenCode{IDENT, PARENTHESIS_OPEN, IDENT, COMMA, STRING, PARENTHESIS_CLOSE}}, + []tokenCode{IDENT, PARENTHESIS_OPEN, IDENT, COMMA, LSTRING, PARENTHESIS_CLOSE}}, {`foo LIKE 'abc%'`, - []tokenCode{IDENT, WS, OPERATOR, WS, STRING}}, + []tokenCode{IDENT, WS, OPERATOR, WS, LSTRING}}, {`foo NOT LIKE 'abc%'`, - []tokenCode{IDENT, WS, OPERATOR, WS, OPERATOR, WS, STRING}}, + []tokenCode{IDENT, WS, OPERATOR, WS, OPERATOR, WS, LSTRING}}, {`foo DESC`, []tokenCode{IDENT, WS, KEYWORD}}, {`year(now())-1`, - []tokenCode{IDENT, PARENTHESIS_OPEN, IDENT, PARENTHESIS_OPEN, PARENTHESIS_CLOSE, PARENTHESIS_CLOSE, OPERATOR, NUMBER}}, + []tokenCode{IDENT, PARENTHESIS_OPEN, IDENT, PARENTHESIS_OPEN, PARENTHESIS_CLOSE, PARENTHESIS_CLOSE, OPERATOR, LNUMBER}}, } for _, test := range tests { diff --git a/pkg/ql/squirrel.go b/pkg/ql/squirrel.go index c613cc91f..29f61f763 100644 --- a/pkg/ql/squirrel.go +++ b/pkg/ql/squirrel.go @@ -68,10 +68,18 @@ func (n Ident) ToSql() (string, []interface{}, error) { return n.Value, n.Args, nil } -func (n Null) ToSql() (string, []interface{}, error) { +func (n LNull) ToSql() (string, []interface{}, error) { return "NULL", nil, nil } +func (n LBoolean) ToSql() (string, []interface{}, error) { + if n.Value { + return "TRUE", nil, nil + } else { + return "FALSE", nil, nil + } +} + func (n Function) ToSql() (string, []interface{}, error) { if paramsSql, args, err := n.Arguments.ToSql(); err != nil { return "", nil, err @@ -101,10 +109,10 @@ func (n Operator) ToSql() (string, []interface{}, error) { return " " + op + " ", nil, nil } -func (n String) ToSql() (string, []interface{}, error) { +func (n LString) ToSql() (string, []interface{}, error) { return "?", []interface{}{n.Value}, nil } -func (n Number) ToSql() (string, []interface{}, error) { +func (n LNumber) ToSql() (string, []interface{}, error) { return n.Value, nil, nil } diff --git a/pkg/ql/token_codes.go b/pkg/ql/token_codes.go index bd2fd9f64..2a9f7f075 100644 --- a/pkg/ql/token_codes.go +++ b/pkg/ql/token_codes.go @@ -16,13 +16,14 @@ const ( EOF WS // 2 IDENT - NUMBER // 4 - STRING + LNULL + LBOOL // 4 + LNUMBER + LSTRING COMMA // , DOT // . OPERATOR // + - / * PARENTHESIS_OPEN PARENTHESIS_CLOSE KEYWORD - NULL ) diff --git a/pkg/ql/token_consumers.go b/pkg/ql/token_consumers.go index bc4d4cb48..8d3ef5732 100644 --- a/pkg/ql/token_consumers.go +++ b/pkg/ql/token_consumers.go @@ -86,7 +86,9 @@ func (TokenConsumerIdent) Consume(s RuneReader) Token { switch lit { case "NULL": - return Token{code: NULL} + return Token{code: LNULL} + case "TRUE", "FALSE": + return Token{code: LBOOL, literal: lit} case "IS", "LIKE", "NOT", "AND", "OR", "XOR": return Token{code: OPERATOR, literal: lit} case "DESC", "ASC", "INTERVAL": @@ -111,7 +113,7 @@ func (str TokenConsumerString) Consume(s RuneReader) Token { if ch = s.read(); ch == eof { break } else if !escaping && str.Test(ch) { // test for quote - return Token{code: STRING, literal: buf.String()} + return Token{code: LSTRING, literal: buf.String()} } else { escaping = !escaping && ch == '\\' if !escaping { @@ -147,7 +149,7 @@ func (str TokenConsumerNumber) Consume(s RuneReader) Token { } // Otherwise return as a regular identifier. - return Token{code: NUMBER, literal: buf.String()} + return Token{code: LNUMBER, literal: buf.String()} } // isLetter returns true if the rune is a letter.