diff --git a/influxql/ast.go b/influxql/ast.go index 72e1d980d4..337ebbc444 100644 --- a/influxql/ast.go +++ b/influxql/ast.go @@ -885,12 +885,48 @@ func (s *SelectStatement) Validate(tr targetRequirement) error { } } + if err := s.validateDerivative(); err != nil { + return err + } + + return nil +} + +func (s *SelectStatement) validateDerivative() error { + if !s.IsNonNestedDerivative() { + return nil + } + // If a derivative is requested, it must be the only field in the query. We don't support // multiple fields in combination w/ derivaties yet. - if s.IsNonNestedDerivative() && len(s.Fields) != 1 { + if len(s.Fields) != 1 { return fmt.Errorf("derivative cannot be used with other fields") } + aggr := s.FunctionCalls() + if len(aggr) != 1 { + return fmt.Errorf("derivative cannot be used with other fields") + } + + // Derivative requires two arguments + derivativeCall := aggr[0] + if len(derivativeCall.Args) != 2 { + return fmt.Errorf("derivative requires a field and duration argument") + } + + // First arg must be a field or aggr over a field e.g. (mean(field)) + _, callOk := derivativeCall.Args[0].(*Call) + _, varOk := derivativeCall.Args[0].(*VarRef) + + if !(callOk || varOk) { + return fmt.Errorf("derivative requires a field argument") + } + + // Second must be a duration .e.g (1h) + if _, ok := derivativeCall.Args[1].(*DurationLiteral); !ok { + return fmt.Errorf("derivative requires a duration argument") + } + return nil } diff --git a/influxql/functions.go b/influxql/functions.go index ab422c98b5..604ebb280b 100644 --- a/influxql/functions.go +++ b/influxql/functions.go @@ -39,9 +39,9 @@ func InitializeMapFunc(c *Call) (MapFunc, error) { } // Ensure that there is either a single argument or if for percentile, two - if c.Name == "percentile" { + if c.Name == "percentile" || c.Name == "derivative" { if len(c.Args) != 2 { - return nil, fmt.Errorf("expected two arguments for percentile()") + return nil, fmt.Errorf("expected two arguments for %s()", c.Name) } } else if len(c.Args) != 1 { return nil, fmt.Errorf("expected one argument for %s()", c.Name) @@ -86,10 +86,6 @@ func InitializeMapFunc(c *Call) (MapFunc, error) { } return MapEcho, nil case "derivative": - if len(c.Args) == 0 { - return nil, fmt.Errorf("expected argument in derivative()") - } - // If the arg is another aggregate e.g. derivative(mean(value)), then // use the map func for that nested aggregate if fn, ok := c.Args[0].(*Call); ok { @@ -136,10 +132,6 @@ func InitializeReduceFunc(c *Call) (ReduceFunc, error) { } return ReducePercentile(lit.Val), nil case "derivative": - if len(c.Args) == 0 { - return nil, fmt.Errorf("expected argument in derivative()") - } - // If the arg is another aggregate e.g. derivative(mean(value)), then // use the map func for that nested aggregate if fn, ok := c.Args[0].(*Call); ok { diff --git a/influxql/functions_test.go b/influxql/functions_test.go index 30aa21b528..bd6a32fd31 100644 --- a/influxql/functions_test.go +++ b/influxql/functions_test.go @@ -1,6 +1,9 @@ package influxql -import "testing" +import ( + "testing" + "time" +) import "sort" @@ -117,6 +120,7 @@ func TestInitializeMapFuncDerivative(t *testing.T) { Name: "derivative", Args: []Expr{ &VarRef{Val: " field1"}, + &DurationLiteral{Val: time.Hour}, }, } @@ -130,6 +134,7 @@ func TestInitializeMapFuncDerivative(t *testing.T) { Name: "derivative", Args: []Expr{ &Call{Name: "mean", Args: []Expr{&VarRef{Val: "field1"}}}, + &DurationLiteral{Val: time.Hour}, }, }