pkg/ql refactoring, add proper bool literal handling
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user