influxdb/query/compile.go

664 lines
17 KiB
Go

package query
import (
"context"
"fmt"
"log"
"regexp"
"time"
"github.com/influxdata/platform/query/interpreter"
"github.com/influxdata/platform/query/parser"
"github.com/influxdata/platform/query/semantic"
"github.com/influxdata/platform/query/values"
opentracing "github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
)
const (
TableParameter = "table"
tableKindKey = "kind"
tableParentsKey = "parents"
nowOption = "now"
//tableSpecKey = "spec"
)
type Option func(*options)
func Verbose(v bool) Option {
return func(o *options) {
o.verbose = v
}
}
type options struct {
verbose bool
}
// Compile evaluates a Flux script producing a query Spec.
// now parameter must be non-zero, that is the default now time should be set before compiling.
func Compile(ctx context.Context, q string, now time.Time, opts ...Option) (*Spec, error) {
o := new(options)
for _, opt := range opts {
opt(o)
}
s, _ := opentracing.StartSpanFromContext(ctx, "parse")
itrp := NewInterpreter()
itrp.SetOption(nowOption, nowFunc(now))
if err := Eval(itrp, q); err != nil {
return nil, err
}
s.Finish()
s, _ = opentracing.StartSpanFromContext(ctx, "compile")
defer s.Finish()
spec := toSpecFromSideEffecs(itrp)
if o.verbose {
log.Println("Query Spec: ", Formatted(spec, FmtJSON))
}
return spec, nil
}
// Eval evaluates the flux string q and update the given interpreter
func Eval(itrp *interpreter.Interpreter, q string) error {
astProg, err := parser.NewAST(q)
if err != nil {
return err
}
_, decls := builtIns(itrp)
// Convert AST program to a semantic program
semProg, err := semantic.New(astProg, decls)
if err != nil {
return err
}
if err := itrp.Eval(semProg); err != nil {
return err
}
return nil
}
// NewInterpreter returns an interpreter instance with
// pre-constructed options and global scopes.
func NewInterpreter() *interpreter.Interpreter {
options := make(map[string]values.Value, len(builtinOptions))
globals := make(map[string]values.Value, len(builtinScope))
for k, v := range builtinScope {
globals[k] = v
}
for k, v := range builtinOptions {
options[k] = v
}
return interpreter.NewInterpreter(options, globals)
}
func nowFunc(now time.Time) values.Function {
timeVal := values.NewTimeValue(values.ConvertTime(now))
ftype := semantic.NewFunctionType(semantic.FunctionSignature{
ReturnType: semantic.Time,
})
call := func(args values.Object) (values.Value, error) {
return timeVal, nil
}
sideEffect := false
return values.NewFunction(nowOption, ftype, call, sideEffect)
}
func toSpecFromSideEffecs(itrp *interpreter.Interpreter) *Spec {
return ToSpec(itrp, itrp.SideEffects()...)
}
// ToSpec creates a query spec from the interpreter and list of values.
func ToSpec(itrp *interpreter.Interpreter, vals ...values.Value) *Spec {
ider := &ider{
id: 0,
lookup: make(map[*TableObject]OperationID),
}
spec := new(Spec)
visited := make(map[*TableObject]bool)
nodes := make([]*TableObject, 0, len(vals))
for _, val := range vals {
if op, ok := val.(*TableObject); ok {
dup := false
for _, node := range nodes {
if op.Equal(node) {
dup = true
break
}
}
if !dup {
op.buildSpec(ider, spec, visited)
nodes = append(nodes, op)
}
}
}
// now option is Time value
nowValue, _ := itrp.Option(nowOption).Function().Call(nil)
spec.Now = nowValue.Time().Time()
return spec
}
type CreateOperationSpec func(args Arguments, a *Administration) (OperationSpec, error)
var builtinScope = make(map[string]values.Value)
// TODO(Josh): Default option values should be registered similarly to built-in
// functions. Default options should be registered in their own files
// (or in a single file) using the RegisterBuiltInOption function which will
// place the resolved option value in the following map.
var builtinOptions = make(map[string]values.Value)
var builtinDeclarations = make(semantic.DeclarationScope)
// list of builtin scripts
var builtins = make(map[string]string)
var finalized bool
// RegisterBuiltIn adds any variable declarations in the script to the builtin scope.
func RegisterBuiltIn(name, script string) {
if finalized {
panic(errors.New("already finalized, cannot register builtin"))
}
builtins[name] = script
}
// RegisterFunction adds a new builtin top level function.
func RegisterFunction(name string, c CreateOperationSpec, sig semantic.FunctionSignature) {
f := function{
t: semantic.NewFunctionType(sig),
name: name,
createOpSpec: c,
hasSideEffect: false,
}
RegisterBuiltInValue(name, &f)
}
// RegisterFunctionWithSideEffect adds a new builtin top level function that produces side effects.
// For example, the builtin functions yield(), toKafka(), and toHTTP() all produce side effects.
func RegisterFunctionWithSideEffect(name string, c CreateOperationSpec, sig semantic.FunctionSignature) {
f := function{
t: semantic.NewFunctionType(sig),
name: name,
createOpSpec: c,
hasSideEffect: true,
}
RegisterBuiltInValue(name, &f)
}
// RegisterBuiltInValue adds the value to the builtin scope.
func RegisterBuiltInValue(name string, v values.Value) {
if finalized {
panic(errors.New("already finalized, cannot register builtin"))
}
if _, ok := builtinScope[name]; ok {
panic(fmt.Errorf("duplicate registration for builtin %q", name))
}
builtinDeclarations[name] = semantic.NewExternalVariableDeclaration(name, v.Type())
builtinScope[name] = v
}
// RegisterBuiltInOption adds the value to the builtin scope.
func RegisterBuiltInOption(name string, v values.Value) {
if finalized {
panic(errors.New("already finalized, cannot register builtin option"))
}
if _, ok := builtinOptions[name]; ok {
panic(fmt.Errorf("duplicate registration for builtin option %q", name))
}
builtinOptions[name] = v
}
// FinalizeBuiltIns must be called to complete registration.
// Future calls to RegisterFunction, RegisterBuiltIn or RegisterBuiltInValue will panic.
func FinalizeBuiltIns() {
if finalized {
panic("already finalized")
}
finalized = true
// Call BuiltIns to validate all built-in values are valid.
// A panic will occur if any value is invalid.
_, _ = BuiltIns()
}
var TableObjectType = semantic.NewObjectType(map[string]semantic.Type{
tableKindKey: semantic.String,
// TODO(nathanielc): The spec types vary significantly making type comparisons impossible, for now the solution is to state the type as an empty object.
//tableSpecKey: semantic.EmptyObject,
// TODO(nathanielc): Support recursive types, for now we state that the array has empty objects.
tableParentsKey: semantic.NewArrayType(semantic.EmptyObject),
})
// IDer produces the mapping of table Objects to OpertionIDs
type IDer interface {
ID(*TableObject) OperationID
}
// IDerOpSpec is the interface any operation spec that needs
// access to OperationIDs in the query spec must implement.
type IDerOpSpec interface {
IDer(ider IDer)
}
type TableObject struct {
// TODO(Josh): Remove args once the
// OperationSpec interface has an Equal method.
args Arguments
Kind OperationKind
Spec OperationSpec
Parents values.Array
}
func (t *TableObject) Operation(ider IDer) *Operation {
if iderOpSpec, ok := t.Spec.(IDerOpSpec); ok {
iderOpSpec.IDer(ider)
}
return &Operation{
ID: ider.ID(t),
Spec: t.Spec,
}
}
type ider struct {
id int
lookup map[*TableObject]OperationID
}
func (i *ider) nextID() int {
next := i.id
i.id++
return next
}
func (i *ider) get(t *TableObject) (OperationID, bool) {
tableID, ok := i.lookup[t]
return tableID, ok
}
func (i *ider) set(t *TableObject, id int) OperationID {
opID := OperationID(fmt.Sprintf("%s%d", t.Kind, id))
i.lookup[t] = opID
return opID
}
func (i *ider) ID(t *TableObject) OperationID {
tableID, ok := i.get(t)
if !ok {
tableID = i.set(t, i.nextID())
}
return tableID
}
func (t *TableObject) ToSpec() *Spec {
ider := &ider{
id: 0,
lookup: make(map[*TableObject]OperationID),
}
spec := new(Spec)
visited := make(map[*TableObject]bool)
t.buildSpec(ider, spec, visited)
return spec
}
func (t *TableObject) buildSpec(ider IDer, spec *Spec, visited map[*TableObject]bool) {
// Traverse graph upwards to first unvisited node.
// Note: parents are sorted based on parameter name, so the visit order is consistent.
t.Parents.Range(func(i int, v values.Value) {
p := v.(*TableObject)
if !visited[p] {
// rescurse up parents
p.buildSpec(ider, spec, visited)
}
})
// Assign ID to table object after visiting all ancestors.
tableID := ider.ID(t)
// Link table object to all parents after assigning ID.
t.Parents.Range(func(i int, v values.Value) {
p := v.(*TableObject)
spec.Edges = append(spec.Edges, Edge{
Parent: ider.ID(p),
Child: tableID,
})
})
visited[t] = true
spec.Operations = append(spec.Operations, t.Operation(ider))
}
func (t *TableObject) Type() semantic.Type {
return TableObjectType
}
func (t *TableObject) Str() string {
panic(values.UnexpectedKind(semantic.Object, semantic.String))
}
func (t *TableObject) Int() int64 {
panic(values.UnexpectedKind(semantic.Object, semantic.Int))
}
func (t *TableObject) UInt() uint64 {
panic(values.UnexpectedKind(semantic.Object, semantic.UInt))
}
func (t *TableObject) Float() float64 {
panic(values.UnexpectedKind(semantic.Object, semantic.Float))
}
func (t *TableObject) Bool() bool {
panic(values.UnexpectedKind(semantic.Object, semantic.Bool))
}
func (t *TableObject) Time() values.Time {
panic(values.UnexpectedKind(semantic.Object, semantic.Time))
}
func (t *TableObject) Duration() values.Duration {
panic(values.UnexpectedKind(semantic.Object, semantic.Duration))
}
func (t *TableObject) Regexp() *regexp.Regexp {
panic(values.UnexpectedKind(semantic.Object, semantic.Regexp))
}
func (t *TableObject) Array() values.Array {
panic(values.UnexpectedKind(semantic.Object, semantic.Array))
}
func (t *TableObject) Object() values.Object {
return t
}
func (t *TableObject) Equal(rhs values.Value) bool {
if t.Type() != rhs.Type() {
return false
}
r := rhs.Object()
if t.Len() != r.Len() {
return false
}
for _, k := range t.keys() {
val1, ok1 := t.Get(k)
val2, ok2 := r.Get(k)
if !ok1 || !ok2 || !val1.Equal(val2) {
return false
}
}
return true
}
func (t *TableObject) Function() values.Function {
panic(values.UnexpectedKind(semantic.Object, semantic.Function))
}
func (t *TableObject) Get(name string) (values.Value, bool) {
switch name {
case tableKindKey:
return values.NewStringValue(string(t.Kind)), true
case tableParentsKey:
return t.Parents, true
default:
return t.args.Get(name)
}
}
func (t *TableObject) keys() []string {
tableKeys := make([]string, 0, len(t.args.GetAll())+2)
return append(tableKeys, tableParentsKey, tableParentsKey)
}
func (t *TableObject) Set(name string, v values.Value) {
// immutable
}
func (t *TableObject) Len() int {
return len(t.keys())
}
func (t *TableObject) Range(f func(name string, v values.Value)) {
for _, arg := range t.args.GetAll() {
val, _ := t.args.Get(arg)
f(arg, val)
}
f(tableKindKey, values.NewStringValue(string(t.Kind)))
f(tableParentsKey, t.Parents)
}
// DefaultFunctionSignature returns a FunctionSignature for standard functions which accept a table piped argument.
// It is safe to modify the returned signature.
func DefaultFunctionSignature() semantic.FunctionSignature {
return semantic.FunctionSignature{
Params: map[string]semantic.Type{
TableParameter: TableObjectType,
},
ReturnType: TableObjectType,
PipeArgument: TableParameter,
}
}
func BuiltIns() (map[string]values.Value, semantic.DeclarationScope) {
if !finalized {
panic("builtins not finalized")
}
return builtIns(NewInterpreter())
}
func builtIns(itrp *interpreter.Interpreter) (map[string]values.Value, semantic.DeclarationScope) {
decls := builtinDeclarations.Copy()
for name, script := range builtins {
astProg, err := parser.NewAST(script)
if err != nil {
panic(errors.Wrapf(err, "failed to parse builtin %q", name))
}
semProg, err := semantic.New(astProg, decls)
if err != nil {
panic(errors.Wrapf(err, "failed to create semantic graph for builtin %q", name))
}
if err := itrp.Eval(semProg); err != nil {
panic(errors.Wrapf(err, "failed to evaluate builtin %q", name))
}
}
return itrp.GlobalScope().Values(), decls
}
type Administration struct {
parents values.Array
}
func newAdministration() *Administration {
return &Administration{
// TODO(nathanielc): Once we can support recursive types change this to,
// interpreter.NewArray(TableObjectType)
parents: values.NewArray(semantic.EmptyObject),
}
}
// AddParentFromArgs reads the args for the `table` argument and adds the value as a parent.
func (a *Administration) AddParentFromArgs(args Arguments) error {
parent, err := args.GetRequiredObject(TableParameter)
if err != nil {
return err
}
p, ok := parent.(*TableObject)
if !ok {
return fmt.Errorf("argument is not a table object: got %T", parent)
}
a.AddParent(p)
return nil
}
// AddParent instructs the evaluation Context that a new edge should be created from the parent to the current operation.
// Duplicate parents will be removed, so the caller need not concern itself with which parents have already been added.
func (a *Administration) AddParent(np *TableObject) {
// Check for duplicates
found := false
a.parents.Range(func(i int, v values.Value) {
if p, ok := v.(*TableObject); ok && p == np {
found = true
}
})
if !found {
a.parents.Append(np)
}
}
type function struct {
name string
t semantic.Type
createOpSpec CreateOperationSpec
hasSideEffect bool
}
func (f *function) Type() semantic.Type {
return f.t
}
func (f *function) Str() string {
panic(values.UnexpectedKind(semantic.Function, semantic.String))
}
func (f *function) Int() int64 {
panic(values.UnexpectedKind(semantic.Function, semantic.Int))
}
func (f *function) UInt() uint64 {
panic(values.UnexpectedKind(semantic.Function, semantic.UInt))
}
func (f *function) Float() float64 {
panic(values.UnexpectedKind(semantic.Function, semantic.Float))
}
func (f *function) Bool() bool {
panic(values.UnexpectedKind(semantic.Function, semantic.Bool))
}
func (f *function) Time() values.Time {
panic(values.UnexpectedKind(semantic.Function, semantic.Time))
}
func (f *function) Duration() values.Duration {
panic(values.UnexpectedKind(semantic.Function, semantic.Duration))
}
func (f *function) Regexp() *regexp.Regexp {
panic(values.UnexpectedKind(semantic.Function, semantic.Regexp))
}
func (f *function) Array() values.Array {
panic(values.UnexpectedKind(semantic.Function, semantic.Array))
}
func (f *function) Object() values.Object {
panic(values.UnexpectedKind(semantic.Function, semantic.Object))
}
func (f *function) Function() values.Function {
return f
}
func (f *function) Equal(rhs values.Value) bool {
if f.Type() != rhs.Type() {
return false
}
v, ok := rhs.(*function)
return ok && (f == v)
}
func (f *function) HasSideEffect() bool {
return f.hasSideEffect
}
func (f *function) Call(argsObj values.Object) (values.Value, error) {
return interpreter.DoFunctionCall(f.call, argsObj)
}
func (f *function) call(args interpreter.Arguments) (values.Value, error) {
a := newAdministration()
arguments := Arguments{Arguments: args}
spec, err := f.createOpSpec(arguments, a)
if err != nil {
return nil, err
}
t := &TableObject{
args: arguments,
Kind: spec.Kind(),
Spec: spec,
Parents: a.parents,
}
return t, nil
}
type specValue struct {
spec OperationSpec
}
func (v specValue) Type() semantic.Type {
return semantic.EmptyObject
}
func (v specValue) Value() interface{} {
return v.spec
}
func (v specValue) Property(name string) (interpreter.Value, error) {
return nil, errors.New("spec does not have properties")
}
type Arguments struct {
interpreter.Arguments
}
func (a Arguments) GetTime(name string) (Time, bool, error) {
v, ok := a.Get(name)
if !ok {
return Time{}, false, nil
}
qt, err := ToQueryTime(v)
if err != nil {
return Time{}, ok, err
}
return qt, ok, nil
}
func (a Arguments) GetRequiredTime(name string) (Time, error) {
qt, ok, err := a.GetTime(name)
if err != nil {
return Time{}, err
}
if !ok {
return Time{}, fmt.Errorf("missing required keyword argument %q", name)
}
return qt, nil
}
func (a Arguments) GetDuration(name string) (Duration, bool, error) {
v, ok := a.Get(name)
if !ok {
return 0, false, nil
}
return Duration(v.Duration()), true, nil
}
func (a Arguments) GetRequiredDuration(name string) (Duration, error) {
d, ok, err := a.GetDuration(name)
if err != nil {
return 0, err
}
if !ok {
return 0, fmt.Errorf("missing required keyword argument %q", name)
}
return d, nil
}
func ToQueryTime(value values.Value) (Time, error) {
switch value.Type().Kind() {
case semantic.Time:
return Time{
Absolute: value.Time().Time(),
}, nil
case semantic.Duration:
return Time{
Relative: value.Duration().Duration(),
IsRelative: true,
}, nil
case semantic.Int:
return Time{
Absolute: time.Unix(value.Int(), 0),
}, nil
default:
return Time{}, fmt.Errorf("value is not a time, got %v", value.Type())
}
}