influxdb/query/influxql/map.go

206 lines
5.7 KiB
Go

package influxql
import (
"fmt"
"strings"
"time"
"github.com/influxdata/flux/ast"
"github.com/influxdata/influxql"
)
// mapCursor holds the mapping of expressions to specific fields that happens at the end of
// the transpilation.
// TODO(jsternberg): This abstraction might be useful for subqueries, but we only need the expr
// at the moment so just hold that.
type mapCursor struct {
expr ast.Expression
}
func (c *mapCursor) Expr() ast.Expression {
return c.expr
}
func (c *mapCursor) Keys() []influxql.Expr {
panic("unimplemented")
}
func (c *mapCursor) Value(expr influxql.Expr) (string, bool) {
panic("unimplemented")
}
// mapFields will take the list of symbols and maps each of the operations
// using the column names.
func (t *transpilerState) mapFields(in cursor) (cursor, error) {
columns := t.stmt.ColumnNames()
if len(columns) != len(t.stmt.Fields) {
// TODO(jsternberg): This scenario should not be possible. Replace the use of ColumnNames with a more
// statically verifiable list of columns when we process the fields from the select statement instead
// of doing this in the future.
panic("number of columns does not match the number of fields")
}
properties := make([]*ast.Property, 0, len(t.stmt.Fields))
for i, f := range t.stmt.Fields {
if ref, ok := f.Expr.(*influxql.VarRef); ok && ref.Val == "time" {
// Skip past any time columns.
continue
}
fieldName, err := t.mapField(f.Expr, in, false)
if err != nil {
return nil, err
}
properties = append(properties, &ast.Property{
Key: fieldName.(ast.PropertyKey),
Value: &ast.StringLiteral{Value: columns[i]},
})
}
return &mapCursor{
expr: &ast.PipeExpression{
Argument: in.Expr(),
Call: &ast.CallExpression{
Callee: &ast.Identifier{
Name: "rename",
},
Arguments: []ast.Expression{
&ast.ObjectExpression{
Properties: []*ast.Property{{
Key: &ast.Identifier{
Name: "columns",
},
Value: &ast.ObjectExpression{
Properties: properties,
},
}},
},
},
},
},
}, nil
}
func (t *transpilerState) mapField(expr influxql.Expr, in cursor, returnMemberExpr bool) (ast.Expression, error) {
if sym, ok := in.Value(expr); ok {
var mappedName ast.Expression
if strings.HasPrefix(sym, "_") {
mappedName = &ast.Identifier{Name: sym}
} else {
mappedName = &ast.StringLiteral{Value: sym}
}
if returnMemberExpr {
return &ast.MemberExpression{
Object: &ast.Identifier{Name: "r"},
Property: mappedName.(ast.PropertyKey),
}, nil
}
return mappedName, nil
}
switch expr := expr.(type) {
case *influxql.Call:
if isMathFunction(expr) {
return nil, fmt.Errorf("unimplemented math function: %q", expr.Name)
}
return nil, fmt.Errorf("missing symbol for %s", expr)
case *influxql.VarRef:
return nil, fmt.Errorf("missing symbol for %s", expr)
case *influxql.BinaryExpr:
return t.evalBinaryExpr(expr, in)
case *influxql.ParenExpr:
return t.mapField(expr.Expr, in, returnMemberExpr)
case *influxql.StringLiteral:
if ts, err := expr.ToTimeLiteral(time.UTC); err == nil {
return &ast.DateTimeLiteral{Value: ts.Val}, nil
}
return &ast.StringLiteral{Value: expr.Val}, nil
case *influxql.NumberLiteral:
return &ast.FloatLiteral{Value: expr.Val}, nil
case *influxql.IntegerLiteral:
return &ast.IntegerLiteral{Value: expr.Val}, nil
case *influxql.BooleanLiteral:
return &ast.BooleanLiteral{Value: expr.Val}, nil
case *influxql.DurationLiteral:
return &ast.DurationLiteral{
Values: durationLiteral(expr.Val),
}, nil
case *influxql.TimeLiteral:
return &ast.DateTimeLiteral{Value: expr.Val}, nil
case *influxql.RegexLiteral:
return &ast.RegexpLiteral{Value: expr.Val}, nil
default:
// TODO(jsternberg): Handle the other expressions by turning them into
// an equivalent expression.
return nil, fmt.Errorf("unimplemented: %T", expr)
}
}
func (t *transpilerState) evalBinaryExpr(expr *influxql.BinaryExpr, in cursor) (ast.Expression, error) {
fn := func() func(left, right ast.Expression) ast.Expression {
b := evalBuilder{}
switch expr.Op {
case influxql.EQ:
return b.eval(ast.EqualOperator)
case influxql.NEQ:
return b.eval(ast.NotEqualOperator)
case influxql.GT:
return b.eval(ast.GreaterThanOperator)
case influxql.GTE:
return b.eval(ast.GreaterThanEqualOperator)
case influxql.LT:
return b.eval(ast.LessThanOperator)
case influxql.LTE:
return b.eval(ast.LessThanEqualOperator)
case influxql.ADD:
return b.eval(ast.AdditionOperator)
case influxql.SUB:
return b.eval(ast.SubtractionOperator)
case influxql.AND:
return b.logical(ast.AndOperator)
case influxql.OR:
return b.logical(ast.OrOperator)
case influxql.EQREGEX:
return b.eval(ast.RegexpMatchOperator)
case influxql.NEQREGEX:
return b.eval(ast.NotRegexpMatchOperator)
default:
return nil
}
}()
if fn == nil {
return nil, fmt.Errorf("unimplemented binary expression: %s", expr.Op)
}
lhs, err := t.mapField(expr.LHS, in, true)
if err != nil {
return nil, err
}
rhs, err := t.mapField(expr.RHS, in, true)
if err != nil {
return nil, err
}
return fn(lhs, rhs), nil
}
// evalBuilder is used for namespacing the logical and eval wrapping functions.
type evalBuilder struct{}
func (evalBuilder) logical(op ast.LogicalOperatorKind) func(left, right ast.Expression) ast.Expression {
return func(left, right ast.Expression) ast.Expression {
return &ast.LogicalExpression{
Operator: op,
Left: left,
Right: right,
}
}
}
func (evalBuilder) eval(op ast.OperatorKind) func(left, right ast.Expression) ast.Expression {
return func(left, right ast.Expression) ast.Expression {
return &ast.BinaryExpression{
Operator: op,
Left: left,
Right: right,
}
}
}