2018-05-15 20:11:32 +00:00
|
|
|
package influxql
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2018-05-22 17:28:04 +00:00
|
|
|
"github.com/influxdata/influxql"
|
2018-05-21 21:20:06 +00:00
|
|
|
"github.com/influxdata/platform/query"
|
2018-05-22 17:28:04 +00:00
|
|
|
"github.com/influxdata/platform/query/functions"
|
2018-05-15 20:11:32 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Transpiler converts InfluxQL queries into a query spec.
|
|
|
|
type Transpiler struct{}
|
|
|
|
|
|
|
|
func NewTranspiler() *Transpiler {
|
|
|
|
return new(Transpiler)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Transpiler) Transpile(ctx context.Context, txt string) (*query.Spec, error) {
|
|
|
|
// Parse the text of the query.
|
|
|
|
q, err := influxql.ParseQuery(txt)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(q.Statements) != 1 {
|
|
|
|
// TODO(jsternberg): Handle queries with multiple statements.
|
|
|
|
return nil, errors.New("unimplemented: only one statement is allowed")
|
|
|
|
}
|
|
|
|
|
|
|
|
s, ok := q.Statements[0].(*influxql.SelectStatement)
|
|
|
|
if !ok {
|
|
|
|
// TODO(jsternberg): Support meta queries.
|
|
|
|
return nil, errors.New("only supports select statements")
|
|
|
|
}
|
|
|
|
|
|
|
|
transpiler := newTranspilerState(s)
|
|
|
|
return transpiler.Transpile(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
type transpilerState struct {
|
2018-06-08 18:07:10 +00:00
|
|
|
stmt *influxql.SelectStatement
|
|
|
|
spec *query.Spec
|
|
|
|
nextID map[string]int
|
|
|
|
now time.Time
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newTranspilerState(stmt *influxql.SelectStatement) *transpilerState {
|
|
|
|
state := &transpilerState{
|
|
|
|
stmt: stmt.Clone(),
|
|
|
|
spec: &query.Spec{},
|
|
|
|
nextID: make(map[string]int),
|
|
|
|
now: time.Now(),
|
|
|
|
}
|
|
|
|
// Omit the time from the cloned statement so it doesn't show up in
|
|
|
|
// the list of column names.
|
|
|
|
state.stmt.OmitTime = true
|
|
|
|
return state
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *transpilerState) Transpile(ctx context.Context) (*query.Spec, error) {
|
2018-06-08 18:07:10 +00:00
|
|
|
groups := identifyGroups(t.stmt)
|
|
|
|
if len(groups) == 0 {
|
2018-05-15 20:11:32 +00:00
|
|
|
return nil, errors.New("no fields")
|
|
|
|
}
|
|
|
|
|
2018-06-08 18:07:10 +00:00
|
|
|
cursors := make([]cursor, 0, len(groups))
|
|
|
|
for _, gr := range groups {
|
|
|
|
cur, err := gr.createCursor(t)
|
2018-05-15 20:11:32 +00:00
|
|
|
if err != nil {
|
2018-06-08 18:07:10 +00:00
|
|
|
return nil, err
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
2018-06-08 18:07:10 +00:00
|
|
|
cursors = append(cursors, cur)
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
2018-06-08 18:07:10 +00:00
|
|
|
// Join the cursors together on the measurement name.
|
|
|
|
// TODO(jsternberg): This needs to join on all remaining partition keys.
|
|
|
|
cur := Join(t, cursors, []string{"_measurement"}, nil)
|
2018-05-15 20:11:32 +00:00
|
|
|
|
2018-06-08 18:07:10 +00:00
|
|
|
// Map each of the fields into another cursor. This evaluates any lingering expressions.
|
|
|
|
cur, err := t.mapFields(cur)
|
2018-05-24 21:33:51 +00:00
|
|
|
if err != nil {
|
2018-05-15 20:11:32 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-24 21:33:51 +00:00
|
|
|
|
2018-06-08 18:07:10 +00:00
|
|
|
// Yield the cursor from the last cursor to a stream with the name of the statement id.
|
|
|
|
// TODO(jsternberg): Include the statement id in the transpiler state when we create
|
|
|
|
// the state so we can yield to something other than zero.
|
|
|
|
t.op("yield", &functions.YieldOpSpec{Name: "0"}, cur.ID())
|
2018-05-15 20:11:32 +00:00
|
|
|
return t.spec, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *transpilerState) op(name string, spec query.OperationSpec, parents ...query.OperationID) query.OperationID {
|
|
|
|
op := query.Operation{
|
|
|
|
ID: query.OperationID(fmt.Sprintf("%s%d", name, t.nextID[name])),
|
|
|
|
Spec: spec,
|
|
|
|
}
|
|
|
|
t.spec.Operations = append(t.spec.Operations, &op)
|
|
|
|
for _, pid := range parents {
|
|
|
|
t.spec.Edges = append(t.spec.Edges, query.Edge{
|
|
|
|
Parent: pid,
|
|
|
|
Child: op.ID,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
t.nextID[name]++
|
|
|
|
return op.ID
|
|
|
|
}
|