influxdb/query/compile.go

581 lines
14 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"
tableIDKey = "id"
tableKindKey = "kind"
tableParentsKey = "parents"
//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.
func Compile(ctx context.Context, q string, opts ...Option) (*Spec, error) {
o := new(options)
for _, opt := range opts {
opt(o)
}
s, _ := opentracing.StartSpanFromContext(ctx, "parse")
astProg, err := parser.NewAST(q)
if err != nil {
return nil, err
}
s.Finish()
s, _ = opentracing.StartSpanFromContext(ctx, "compile")
defer s.Finish()
qd := new(queryDomain)
scope, decls := builtIns(qd)
interpScope := interpreter.NewScopeWithValues(scope)
// Convert AST program to a semantic program
semProg, err := semantic.New(astProg, decls)
if err != nil {
return nil, err
}
if _, err := interpreter.Eval(semProg, interpScope); err != nil {
return nil, err
}
spec := qd.ToSpec()
if o.verbose {
log.Println("Query Spec: ", Formatted(spec, FmtJSON))
}
return spec, nil
}
type CreateOperationSpec func(args Arguments, a *Administration) (OperationSpec, error)
var builtinScope = 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
}
// 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{
tableIDKey: semantic.String,
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),
})
type TableObject struct {
ID OperationID
Kind OperationKind
Spec OperationSpec
Parents values.Array
}
func (t TableObject) Operation() *Operation {
return &Operation{
ID: t.ID,
Spec: t.Spec,
}
}
func (t TableObject) String() string {
return fmt.Sprintf("{id: %q, kind: %q}", t.ID, t.Kind)
}
func (t TableObject) ToSpec() *Spec {
visited := make(map[OperationID]bool)
spec := new(Spec)
t.buildSpec(spec, visited)
return spec
}
func (t TableObject) buildSpec(spec *Spec, visited map[OperationID]bool) {
id := t.ID
t.Parents.Range(func(i int, v values.Value) {
p := v.(TableObject)
if !visited[p.ID] {
// rescurse up parents
p.buildSpec(spec, visited)
}
spec.Edges = append(spec.Edges, Edge{
Parent: p.ID,
Child: id,
})
})
visited[id] = true
spec.Operations = append(spec.Operations, t.Operation())
}
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 tableIDKey:
return values.NewStringValue(string(t.ID)), true
case tableKindKey:
return values.NewStringValue(string(t.Kind)), true
case tableParentsKey:
return t.Parents, true
default:
return nil, false
}
}
func (t TableObject) keys() []string {
return []string{tableIDKey, tableKindKey, tableParentsKey}
}
func (t TableObject) Set(name string, v values.Value) {
//TableObject is immutable
}
func (t TableObject) Len() int {
return 3
}
func (t TableObject) Range(f func(name string, v values.Value)) {
f(tableIDKey, values.NewStringValue(string(t.ID)))
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")
}
qd := new(queryDomain)
return builtIns(qd)
}
func builtIns(qd *queryDomain) (map[string]values.Value, semantic.DeclarationScope) {
decls := builtinDeclarations.Copy()
scope := make(map[string]values.Value, len(builtinScope))
for k, v := range builtinScope {
if v.Type().Kind() == semantic.Function {
if f, ok := v.Function().(*function); ok {
// Must make separate copy of f. Otherwise function
// tests will modify same f and tests will break.
newfunc := f.copy()
newfunc.qd = qd
v = newfunc
}
}
scope[k] = v
}
interpScope := interpreter.NewScopeWithValues(scope)
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 := interpreter.Eval(semProg, interpScope); err != nil {
panic(errors.Wrapf(err, "failed to evaluate builtin %q", name))
}
}
return scope, decls
}
type Administration struct {
id OperationID
parents values.Array
}
func newAdministration(id OperationID) *Administration {
return &Administration{
id: id,
// 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, p values.Value) {
if p.(TableObject).ID == np.ID {
found = true
}
})
if !found {
a.parents.Append(np)
}
}
type Domain interface {
ToSpec() *Spec
}
func NewDomain() Domain {
return new(queryDomain)
}
type queryDomain struct {
id int
operations []TableObject
}
func (d *queryDomain) NewID(name string) OperationID {
return OperationID(fmt.Sprintf("%s%d", name, d.nextID()))
}
func (d *queryDomain) nextID() int {
id := d.id
d.id++
return id
}
func (d *queryDomain) ToSpec() *Spec {
spec := new(Spec)
visited := make(map[OperationID]bool)
for _, t := range d.operations {
t.buildSpec(spec, visited)
}
return spec
}
type function struct {
name string
t semantic.Type
createOpSpec CreateOperationSpec
qd *queryDomain
hasSideEffect bool
}
func (f *function) copy() *function {
newfunc := new(function)
*newfunc = *f
return newfunc
}
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) {
id := f.qd.NewID(f.name)
a := newAdministration(id)
spec, err := f.createOpSpec(Arguments{Arguments: args}, a)
if err != nil {
return nil, err
}
if a.parents.Len() > 1 {
// Always add parents in a consistent order
a.parents.Sort(func(i, j values.Value) bool {
return i.(TableObject).ID < j.(TableObject).ID
})
}
t := TableObject{
ID: id,
Kind: spec.Kind(),
Spec: spec,
Parents: a.parents,
}
f.qd.operations = append(f.qd.operations, t)
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())
}
}