diff --git a/query/compile.go b/query/compile.go index 5d45cb70bf..2aa4dfb986 100644 --- a/query/compile.go +++ b/query/compile.go @@ -68,10 +68,8 @@ func Eval(itrp *interpreter.Interpreter, q string) error { return err } - _, decls := builtIns(itrp) - // Convert AST program to a semantic program - semProg, err := semantic.New(astProg, decls) + semProg, err := semantic.New(astProg, builtinDeclarations.Copy()) if err != nil { return err } @@ -85,18 +83,13 @@ func Eval(itrp *interpreter.Interpreter, q string) error { // NewInterpreter returns an interpreter instance with // pre-constructed options and global scopes. func NewInterpreter() *interpreter.Interpreter { + // Make a copy of the builtin options since they can be modified 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) + return interpreter.NewInterpreter(options, builtinValues) } func nowFunc(now time.Time) values.Function { @@ -151,17 +144,12 @@ func ToSpec(itrp *interpreter.Interpreter, vals ...values.Value) *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 builtinValues = make(map[string]values.Value) var builtinOptions = make(map[string]values.Value) var builtinDeclarations = make(semantic.DeclarationScope) // list of builtin scripts -var builtins = make(map[string]string) +var builtinScripts = make(map[string]string) var finalized bool // RegisterBuiltIn adds any variable declarations in the script to the builtin scope. @@ -169,7 +157,7 @@ func RegisterBuiltIn(name, script string) { if finalized { panic(errors.New("already finalized, cannot register builtin")) } - builtins[name] = script + builtinScripts[name] = script } // RegisterFunction adds a new builtin top level function. @@ -200,11 +188,11 @@ func RegisterBuiltInValue(name string, v values.Value) { if finalized { panic(errors.New("already finalized, cannot register builtin")) } - if _, ok := builtinScope[name]; ok { + if _, ok := builtinValues[name]; ok { panic(fmt.Errorf("duplicate registration for builtin %q", name)) } builtinDeclarations[name] = semantic.NewExternalVariableDeclaration(name, v.Type()) - builtinScope[name] = v + builtinValues[name] = v } // RegisterBuiltInOption adds the value to the builtin scope. @@ -225,9 +213,30 @@ func FinalizeBuiltIns() { 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() + + err := evalBuiltInScripts() + if err != nil { + panic(err) + } +} + +func evalBuiltInScripts() error { + itrp := interpreter.NewMutableInterpreter(builtinOptions, builtinValues) + for name, script := range builtinScripts { + astProg, err := parser.NewAST(script) + if err != nil { + return errors.Wrapf(err, "failed to parse builtin %q", name) + } + semProg, err := semantic.New(astProg, builtinDeclarations) + if err != nil { + return errors.Wrapf(err, "failed to create semantic graph for builtin %q", name) + } + + if err := itrp.Eval(semProg); err != nil { + return errors.Wrapf(err, "failed to evaluate builtin %q", name) + } + } + return nil } var TableObjectType = semantic.NewObjectType(map[string]semantic.Type{ @@ -441,27 +450,7 @@ 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 + return builtinValues, builtinDeclarations } type Administration struct { diff --git a/query/functions/testdata/top.flux b/query/functions/testdata/top.flux new file mode 100644 index 0000000000..7008356f10 --- /dev/null +++ b/query/functions/testdata/top.flux @@ -0,0 +1,3 @@ +from(db: "test") + |> range(start:2018-05-22T19:53:24.421470485Z) + |> top(n:2) diff --git a/query/functions/testdata/top.in.csv b/query/functions/testdata/top.in.csv new file mode 100644 index 0000000000..e94395cb2e --- /dev/null +++ b/query/functions/testdata/top.in.csv @@ -0,0 +1,16 @@ +#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,string,string +#group,false,false,false,false,false,false,true +#default,_result,,,,,, +,result,table,_start,_stop,_time,_value,_field +,,0,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:54:16Z,20,used_percent +,,0,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:56Z,55,used_percent +,,0,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:54:06Z,20,used_percent +,,0,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:26Z,35,used_percent +,,0,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:46Z,70,used_percent +,,0,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:36Z,15,used_percent +,,1,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:54:16Z,20000,used +,,1,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:56Z,55000,used +,,1,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:54:06Z,20000,used +,,1,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:26Z,35000,used +,,1,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:46Z,70000,used +,,1,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:36Z,15000,used diff --git a/query/functions/testdata/top.out.csv b/query/functions/testdata/top.out.csv new file mode 100644 index 0000000000..a828951bad --- /dev/null +++ b/query/functions/testdata/top.out.csv @@ -0,0 +1,8 @@ +#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,string,string +#group,false,false,false,false,false,false,true +#default,_result,,,,,, +,result,table,_start,_stop,_time,_value,_field +,,0,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:46Z,70000,used +,,0,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:56Z,55000,used +,,1,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:46Z,70,used_percent +,,1,2018-05-22T19:53:24.421470485Z,2018-05-22T19:54:24.421470485Z,2018-05-22T19:53:56Z,55,used_percent diff --git a/query/interpreter/interpreter.go b/query/interpreter/interpreter.go index 7448804cde..32ad59dc0b 100644 --- a/query/interpreter/interpreter.go +++ b/query/interpreter/interpreter.go @@ -17,13 +17,27 @@ type Interpreter struct { globals *Scope } -// NewInterpreter instantiates a new Flux Interpreter +// NewInterpreter instantiates a new Flux Interpreter whose builtin values are not mutable. +// Options are always mutable. func NewInterpreter(options, builtins map[string]values.Value) *Interpreter { optionScope := NewScopeWithValues(options) globalScope := optionScope.NestWithValues(builtins) - interpreter := new(Interpreter) - interpreter.options = optionScope - interpreter.globals = globalScope + interpreter := &Interpreter{ + options: optionScope, + globals: globalScope.Nest(), + } + return interpreter +} + +// NewMutableInterpreter instantiates a new Flux Interpreter whose builtin values are mutable. +// Options are always mutable. +func NewMutableInterpreter(options, builtins map[string]values.Value) *Interpreter { + optionScope := NewScopeWithValues(options) + globalScope := optionScope.NestWithValues(builtins) + interpreter := &Interpreter{ + options: optionScope, + globals: globalScope, + } return interpreter } @@ -385,6 +399,7 @@ func (itrp *Interpreter) doArguments(args *semantic.ObjectExpression, scope *Sco if _, ok := obj.Get(p.Key.Name); ok { return nil, fmt.Errorf("duplicate keyword parameter specified: %q", p.Key.Name) } + obj.Set(p.Key.Name, value) } return obj, nil @@ -402,13 +417,12 @@ func NewScope() *Scope { values: make(map[string]values.Value), } } + +// NewScopeWithValues creates a new scope with the initial set of values. +// The vals map will be mutated. func NewScopeWithValues(vals map[string]values.Value) *Scope { - cp := make(map[string]values.Value, len(vals)) - for k, v := range vals { - cp[k] = v - } return &Scope{ - values: cp, + values: vals, } } diff --git a/query/semantic/types.go b/query/semantic/types.go index 54738a1296..7ee4149b4f 100644 --- a/query/semantic/types.go +++ b/query/semantic/types.go @@ -201,7 +201,11 @@ func (t *objectType) Kind() Kind { return Object } func (t *objectType) PropertyType(name string) Type { - return t.properties[name] + typ, ok := t.properties[name] + if ok { + return typ + } + return Invalid } func (t *objectType) Properties() map[string]Type { return t.properties