2018-05-15 20:11:32 +00:00
|
|
|
package influxql
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2018-06-08 21:10:50 +00:00
|
|
|
"strconv"
|
2018-05-15 20:11:32 +00:00
|
|
|
"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.
|
2018-06-11 16:21:39 +00:00
|
|
|
type Transpiler struct {
|
|
|
|
Config *Config
|
|
|
|
}
|
2018-05-15 20:11:32 +00:00
|
|
|
|
|
|
|
func NewTranspiler() *Transpiler {
|
2018-06-13 15:07:06 +00:00
|
|
|
return &Transpiler{}
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
2018-06-11 16:21:39 +00:00
|
|
|
func NewTranspilerWithConfig(cfg Config) *Transpiler {
|
|
|
|
return &Transpiler{
|
|
|
|
Config: &cfg,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-15 20:11:32 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-06-11 16:21:39 +00:00
|
|
|
transpiler := newTranspilerState(t.Config)
|
2018-06-08 21:10:50 +00:00
|
|
|
for i, s := range q.Statements {
|
|
|
|
stmt, ok := s.(*influxql.SelectStatement)
|
|
|
|
if !ok {
|
|
|
|
// TODO(jsternberg): Support meta queries.
|
2018-07-03 20:32:39 +00:00
|
|
|
return nil, fmt.Errorf("only supports select statements: %T", s)
|
2018-06-08 21:10:50 +00:00
|
|
|
} else if err := transpiler.Transpile(ctx, i, stmt); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
2018-06-08 21:10:50 +00:00
|
|
|
return transpiler.spec, nil
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type transpilerState struct {
|
2018-06-08 21:10:50 +00:00
|
|
|
id int
|
2018-06-08 18:07:10 +00:00
|
|
|
stmt *influxql.SelectStatement
|
2018-06-11 16:21:39 +00:00
|
|
|
config Config
|
2018-06-08 18:07:10 +00:00
|
|
|
spec *query.Spec
|
|
|
|
nextID map[string]int
|
|
|
|
now time.Time
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
2018-06-11 16:21:39 +00:00
|
|
|
func newTranspilerState(config *Config) *transpilerState {
|
2018-05-15 20:11:32 +00:00
|
|
|
state := &transpilerState{
|
|
|
|
spec: &query.Spec{},
|
|
|
|
nextID: make(map[string]int),
|
|
|
|
}
|
2018-06-11 16:21:39 +00:00
|
|
|
if config != nil {
|
|
|
|
state.config = *config
|
|
|
|
}
|
2018-06-13 15:07:06 +00:00
|
|
|
if state.config.NowFn == nil {
|
|
|
|
state.config.NowFn = time.Now
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stamp the current time using the now function from the config or the default.
|
|
|
|
state.now = state.config.NowFn()
|
2018-05-15 20:11:32 +00:00
|
|
|
return state
|
|
|
|
}
|
|
|
|
|
2018-06-08 21:10:50 +00:00
|
|
|
func (t *transpilerState) Transpile(ctx context.Context, id int, stmt *influxql.SelectStatement) error {
|
|
|
|
// Clone the select statement and omit the time from the list of column names.
|
|
|
|
t.stmt = stmt.Clone()
|
|
|
|
t.stmt.OmitTime = true
|
|
|
|
t.id = id
|
|
|
|
|
2018-07-05 16:37:49 +00:00
|
|
|
groups, err := identifyGroups(t.stmt)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else if len(groups) == 0 {
|
|
|
|
return errors.New("at least 1 non-time field must be queried")
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
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 21:10:50 +00:00
|
|
|
return 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.
|
2018-07-10 23:02:37 +00:00
|
|
|
// TODO(jsternberg): This needs to join on all remaining group keys.
|
2018-06-08 18:07:10 +00:00
|
|
|
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.
|
2018-07-05 16:37:49 +00:00
|
|
|
cur, err = t.mapFields(cur)
|
2018-05-24 21:33:51 +00:00
|
|
|
if err != nil {
|
2018-06-08 21:10:50 +00:00
|
|
|
return err
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
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.
|
2018-06-08 21:10:50 +00:00
|
|
|
t.op("yield", &functions.YieldOpSpec{Name: strconv.Itoa(t.id)}, cur.ID())
|
|
|
|
return nil
|
2018-05-15 20:11:32 +00:00
|
|
|
}
|
|
|
|
|
2018-06-08 19:18:41 +00:00
|
|
|
func (t *transpilerState) mapType(ref *influxql.VarRef) influxql.DataType {
|
|
|
|
// TODO(jsternberg): Actually evaluate the type against the schema.
|
|
|
|
return influxql.Tag
|
|
|
|
}
|
|
|
|
|
2018-06-11 15:13:11 +00:00
|
|
|
func (t *transpilerState) from(m *influxql.Measurement) (query.OperationID, error) {
|
|
|
|
db, rp := m.Database, m.RetentionPolicy
|
|
|
|
if db == "" {
|
2018-06-11 16:21:39 +00:00
|
|
|
if t.config.DefaultDatabase == "" {
|
|
|
|
return "", errors.New("database is required")
|
|
|
|
}
|
|
|
|
db = t.config.DefaultDatabase
|
2018-06-11 15:13:11 +00:00
|
|
|
}
|
|
|
|
if rp == "" {
|
2018-06-11 16:21:39 +00:00
|
|
|
if t.config.DefaultRetentionPolicy != "" {
|
|
|
|
rp = t.config.DefaultRetentionPolicy
|
|
|
|
} else {
|
|
|
|
rp = "autogen"
|
|
|
|
}
|
2018-06-11 15:13:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
spec := &functions.FromOpSpec{
|
|
|
|
Bucket: fmt.Sprintf("%s/%s", db, rp),
|
|
|
|
}
|
|
|
|
return t.op("from", spec), nil
|
|
|
|
}
|
|
|
|
|
2018-05-15 20:11:32 +00:00
|
|
|
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
|
|
|
|
}
|