Add support for intervals for AST, report filtering
This commit is contained in:
@@ -40,6 +40,11 @@ type (
|
||||
Keyword string
|
||||
}
|
||||
|
||||
Interval struct {
|
||||
Value string
|
||||
Unit string
|
||||
}
|
||||
|
||||
Column struct {
|
||||
Expr ASTNodes
|
||||
Alias string
|
||||
@@ -58,6 +63,8 @@ func (n Operator) String() string { return n.Kind }
|
||||
|
||||
func (n Keyword) String() string { return n.Keyword }
|
||||
|
||||
func (n Interval) String() string { return fmt.Sprintf("INTERVAL %s %s", n.Value, n.Unit) }
|
||||
|
||||
func (n Function) String() string { return fmt.Sprintf("%s(%s)", n.Name, n.Arguments) }
|
||||
|
||||
func (n Ident) String() string { return n.Value }
|
||||
|
||||
@@ -176,7 +176,11 @@ checkToken:
|
||||
list = append(list, Operator{Kind: t.literal})
|
||||
goto next
|
||||
case KEYWORD:
|
||||
list = append(list, Keyword{Keyword: t.literal})
|
||||
if keyword, err := p.parseKeyword(t); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
list = append(list, keyword)
|
||||
}
|
||||
goto next
|
||||
case NUMBER:
|
||||
list = append(list, Number{Value: t.literal})
|
||||
@@ -239,6 +243,13 @@ next:
|
||||
case OPERATOR:
|
||||
list = append(list, Operator{Kind: t.literal})
|
||||
goto next
|
||||
case KEYWORD:
|
||||
if keyword, err := p.parseKeyword(t); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
list = append(list, keyword)
|
||||
}
|
||||
goto next
|
||||
case IDENT:
|
||||
if p.peekToken(1).Is(OPERATOR) {
|
||||
// Looks like we have an expression ahead of us
|
||||
@@ -280,3 +291,33 @@ next:
|
||||
return nil, fmt.Errorf("unexpected token while parsing set (%v)", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseKeyword(t Token) (list ASTNode, err error) {
|
||||
switch strings.ToUpper(t.literal) {
|
||||
case "INTERVAL":
|
||||
i := Interval{Value: p.nextToken().literal}
|
||||
u := p.nextToken()
|
||||
|
||||
if u.code != IDENT {
|
||||
return nil, fmt.Errorf("expecting identifier, got %v", t)
|
||||
} else {
|
||||
switch strings.ToUpper(u.literal) {
|
||||
case "MICROSECOND", "SECOND", "MINUTE", "HOUR",
|
||||
"DAY", "WEEK", "MONTH", "QUARTER", "YEAR",
|
||||
"SECOND_MICROSECOND", "MINUTE_MICROSECOND", "MINUTE_SECOND", "HOUR_MICROSECOND", "HOUR_SECOND",
|
||||
"HOUR_MINUTE", "DAY_MICROSECOND", "DAY_SECOND", "DAY_MINUTE", "DAY_HOUR", "YEAR_MONTH":
|
||||
// All good
|
||||
break
|
||||
default:
|
||||
return nil, fmt.Errorf("expecting interval unit, got %v", u.literal)
|
||||
}
|
||||
}
|
||||
|
||||
i.Unit = u.literal
|
||||
|
||||
return i, nil
|
||||
|
||||
default:
|
||||
return Keyword{Keyword: t.literal}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,15 @@ func TestAstParser_Parser(t *testing.T) {
|
||||
Number{Value: "1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
parser: NewParser().ParseExpression,
|
||||
in: `NOW() > DATE_SUB(col, INTERVAL 31 DAY)`,
|
||||
tree: ASTNodes{
|
||||
Function{Name: "NOW"},
|
||||
Operator{Kind: ">"},
|
||||
Function{Name: "DATE_SUB", Arguments: ASTSet{Ident{Value: "col"}, Interval{Value: "31", Unit: "DAY"}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
parser: NewParser().ParseExpression,
|
||||
in: `foo LIKE 'bar%'`,
|
||||
|
||||
@@ -75,6 +75,10 @@ func (n Keyword) ToSql() (string, []interface{}, error) {
|
||||
return n.Keyword, nil, nil
|
||||
}
|
||||
|
||||
func (n Interval) ToSql() (string, []interface{}, error) {
|
||||
return fmt.Sprintf("INTERVAL ? %s", n.Unit), []interface{}{n.Value}, nil
|
||||
}
|
||||
|
||||
func (n Operator) ToSql() (string, []interface{}, error) {
|
||||
var op = n.Kind
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ func (TokenConsumerIdent) Consume(s RuneReader) Token {
|
||||
switch lit {
|
||||
case "LIKE", "NOT", "AND", "OR", "XOR":
|
||||
return Token{code: OPERATOR, literal: lit}
|
||||
case "DESC", "ASC":
|
||||
case "DESC", "ASC", "INTERVAL":
|
||||
return Token{code: KEYWORD, literal: lit}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,17 @@ func stdGroupByFuncHandler(f ql.Function) (ql.Function, error) {
|
||||
default:
|
||||
return f, fmt.Errorf("unsupported group-by function %q", f.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Identifiers should be names of the fields (physical table columns OR json fields, defined in module)
|
||||
func stdFilterFuncHandler(f ql.Function) (ql.Function, error) {
|
||||
switch strings.ToUpper(f.Name) {
|
||||
case "CONCAT", "QUARTER", "YEAR", "DATE", "NOW", "DATE_ADD", "DATE_SUB", "DATE_FORMAT":
|
||||
return f, nil
|
||||
|
||||
default:
|
||||
return f, fmt.Errorf("unsupported group-by function %q", f.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func NewRecordReportBuilder(moduleID uint64) *recordReportBuilder {
|
||||
@@ -83,7 +93,7 @@ func (b *recordReportBuilder) SetFilter(filters string) (err error) {
|
||||
p := ql.NewParser()
|
||||
|
||||
p.OnIdent = ql.MakeIdentWrapHandler(jsonWrap, "created_at", "updated_at", "id", "user_id")
|
||||
p.OnFunction = stdGroupByFuncHandler
|
||||
p.OnFunction = stdFilterFuncHandler
|
||||
|
||||
b.filter, err = p.ParseExpression(filters)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user