diff --git a/query/math.go b/query/math.go index c9f266ba62..7b9219c6a4 100644 --- a/query/math.go +++ b/query/math.go @@ -23,14 +23,60 @@ func (MathTypeMapper) MapType(measurement *influxql.Measurement, field string) i func (MathTypeMapper) CallType(name string, args []influxql.DataType) (influxql.DataType, error) { switch name { - case "abs", "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "exp", "log", "ln", "log2", "log10", "sqrt", "pow": - return influxql.Float, nil - case "floor", "ceil", "round": - switch args[0] { + case "sin", "cos", "tan", "atan", "exp", "log", "ln", "log2", "log10", "sqrt": + var arg0 influxql.DataType + if len(args) > 0 { + arg0 = args[0] + } + switch arg0 { + case influxql.Float, influxql.Integer, influxql.Unsigned, influxql.Unknown: + return influxql.Float, nil + default: + return influxql.Unknown, fmt.Errorf("invalid argument type for the first argument in %s(): %s", name, arg0) + } + case "asin", "acos": + var arg0 influxql.DataType + if len(args) > 0 { + arg0 = args[0] + } + switch arg0 { + case influxql.Float, influxql.Unknown: + return influxql.Float, nil + default: + return influxql.Unknown, fmt.Errorf("invalid argument type for the first argument in %s(): %s", name, arg0) + } + case "atan2", "pow": + var arg0, arg1 influxql.DataType + if len(args) > 0 { + arg0 = args[0] + } + if len(args) > 1 { + arg1 = args[1] + } + + switch arg0 { + case influxql.Float, influxql.Integer, influxql.Unsigned, influxql.Unknown: + // Pass through to verify the second argument. + default: + return influxql.Unknown, fmt.Errorf("invalid argument type for the first argument in %s(): %s", name, arg0) + } + + switch arg1 { + case influxql.Float, influxql.Integer, influxql.Unsigned, influxql.Unknown: + return influxql.Float, nil + default: + return influxql.Unknown, fmt.Errorf("invalid argument type for the second argument in %s(): %s", name, arg1) + } + case "abs", "floor", "ceil", "round": + var arg0 influxql.DataType + if len(args) > 0 { + arg0 = args[0] + } + switch arg0 { case influxql.Float, influxql.Integer, influxql.Unsigned, influxql.Unknown: return args[0], nil default: - return influxql.Unknown, fmt.Errorf("invalid argument type for first argument in %s(): %s", name, args[0]) + return influxql.Unknown, fmt.Errorf("invalid argument type for the first argument in %s(): %s", name, arg0) } } return influxql.Unknown, nil @@ -125,7 +171,7 @@ func (v MathValuer) Call(name string, args []interface{}) (interface{}, bool) { } return nil, true case "log2": - if arg0, ok := asFloat(args); ok { + if arg0, ok := asFloat(arg0); ok { return math.Log2(arg0), true } return nil, true diff --git a/query/math_test.go b/query/math_test.go index 3250e5a7f9..3522278100 100644 --- a/query/math_test.go +++ b/query/math_test.go @@ -1,6 +1,7 @@ package query_test import ( + "math" "testing" "github.com/influxdata/influxdb/query" @@ -11,16 +12,108 @@ func TestMath_TypeMapper(t *testing.T) { for _, tt := range []struct { s string typ influxql.DataType + err bool }{ + {s: `abs(f::float)`, typ: influxql.Float}, + {s: `abs(i::integer)`, typ: influxql.Integer}, + {s: `abs(u::unsigned)`, typ: influxql.Unsigned}, + {s: `abs(s::string)`, err: true}, + {s: `abs(b::boolean)`, err: true}, {s: `sin(f::float)`, typ: influxql.Float}, {s: `sin(i::integer)`, typ: influxql.Float}, {s: `sin(u::unsigned)`, typ: influxql.Float}, + {s: `sin(s::string)`, err: true}, + {s: `sin(b::boolean)`, err: true}, {s: `cos(f::float)`, typ: influxql.Float}, {s: `cos(i::integer)`, typ: influxql.Float}, {s: `cos(u::unsigned)`, typ: influxql.Float}, + {s: `cos(s::string)`, err: true}, + {s: `cos(b::boolean)`, err: true}, {s: `tan(f::float)`, typ: influxql.Float}, {s: `tan(i::integer)`, typ: influxql.Float}, {s: `tan(u::unsigned)`, typ: influxql.Float}, + {s: `tan(s::string)`, err: true}, + {s: `tan(b::boolean)`, err: true}, + {s: `asin(f::float)`, typ: influxql.Float}, + {s: `asin(i::integer)`, err: true}, + {s: `asin(u::unsigned)`, err: true}, + {s: `asin(s::string)`, err: true}, + {s: `asin(b::boolean)`, err: true}, + {s: `acos(f::float)`, typ: influxql.Float}, + {s: `acos(i::integer)`, err: true}, + {s: `acos(u::unsigned)`, err: true}, + {s: `acos(s::string)`, err: true}, + {s: `acos(b::boolean)`, err: true}, + {s: `atan(f::float)`, typ: influxql.Float}, + {s: `atan(i::integer)`, typ: influxql.Float}, + {s: `atan(u::unsigned)`, typ: influxql.Float}, + {s: `atan(s::string)`, err: true}, + {s: `atan(b::boolean)`, err: true}, + {s: `atan2(y::float, x::float)`, typ: influxql.Float}, + {s: `atan2(y::integer, x::float)`, typ: influxql.Float}, + {s: `atan2(y::unsigned, x::float)`, typ: influxql.Float}, + {s: `atan2(y::string, x::float)`, err: true}, + {s: `atan2(y::boolean, x::float)`, err: true}, + {s: `atan2(y::float, x::float)`, typ: influxql.Float}, + {s: `atan2(y::float, x::integer)`, typ: influxql.Float}, + {s: `atan2(y::float, x::unsigned)`, typ: influxql.Float}, + {s: `atan2(y::float, x::string)`, err: true}, + {s: `atan2(y::float, x::boolean)`, err: true}, + {s: `exp(f::float)`, typ: influxql.Float}, + {s: `exp(i::integer)`, typ: influxql.Float}, + {s: `exp(u::unsigned)`, typ: influxql.Float}, + {s: `exp(s::string)`, err: true}, + {s: `exp(b::boolean)`, err: true}, + {s: `log(f::float)`, typ: influxql.Float}, + {s: `log(i::integer)`, typ: influxql.Float}, + {s: `log(u::unsigned)`, typ: influxql.Float}, + {s: `log(s::string)`, err: true}, + {s: `log(b::boolean)`, err: true}, + {s: `ln(f::float)`, typ: influxql.Float}, + {s: `ln(i::integer)`, typ: influxql.Float}, + {s: `ln(u::unsigned)`, typ: influxql.Float}, + {s: `ln(s::string)`, err: true}, + {s: `ln(b::boolean)`, err: true}, + {s: `log2(f::float)`, typ: influxql.Float}, + {s: `log2(i::integer)`, typ: influxql.Float}, + {s: `log2(u::unsigned)`, typ: influxql.Float}, + {s: `log2(s::string)`, err: true}, + {s: `log2(b::boolean)`, err: true}, + {s: `log10(f::float)`, typ: influxql.Float}, + {s: `log10(i::integer)`, typ: influxql.Float}, + {s: `log10(u::unsigned)`, typ: influxql.Float}, + {s: `log10(s::string)`, err: true}, + {s: `log10(b::boolean)`, err: true}, + {s: `sqrt(f::float)`, typ: influxql.Float}, + {s: `sqrt(i::integer)`, typ: influxql.Float}, + {s: `sqrt(u::unsigned)`, typ: influxql.Float}, + {s: `sqrt(s::string)`, err: true}, + {s: `sqrt(b::boolean)`, err: true}, + {s: `pow(y::float, x::float)`, typ: influxql.Float}, + {s: `pow(y::integer, x::float)`, typ: influxql.Float}, + {s: `pow(y::unsigned, x::float)`, typ: influxql.Float}, + {s: `pow(y::string, x::string)`, err: true}, + {s: `pow(y::boolean, x::boolean)`, err: true}, + {s: `pow(y::float, x::float)`, typ: influxql.Float}, + {s: `pow(y::float, x::integer)`, typ: influxql.Float}, + {s: `pow(y::float, x::unsigned)`, typ: influxql.Float}, + {s: `pow(y::float, x::string)`, err: true}, + {s: `pow(y::float, x::boolean)`, err: true}, + {s: `floor(f::float)`, typ: influxql.Float}, + {s: `floor(i::integer)`, typ: influxql.Integer}, + {s: `floor(u::unsigned)`, typ: influxql.Unsigned}, + {s: `floor(s::string)`, err: true}, + {s: `floor(b::boolean)`, err: true}, + {s: `ceil(f::float)`, typ: influxql.Float}, + {s: `ceil(i::integer)`, typ: influxql.Integer}, + {s: `ceil(u::unsigned)`, typ: influxql.Unsigned}, + {s: `ceil(s::string)`, err: true}, + {s: `ceil(b::boolean)`, err: true}, + {s: `round(f::float)`, typ: influxql.Float}, + {s: `round(i::integer)`, typ: influxql.Integer}, + {s: `round(u::unsigned)`, typ: influxql.Unsigned}, + {s: `round(s::string)`, err: true}, + {s: `round(b::boolean)`, err: true}, } { t.Run(tt.s, func(t *testing.T) { expr := MustParseExpr(tt.s) @@ -29,10 +122,91 @@ func TestMath_TypeMapper(t *testing.T) { TypeMapper: query.MathTypeMapper{}, } if got, err := typmap.EvalType(expr); err != nil { - t.Errorf("unexpected error: %s", err) + if !tt.err { + t.Errorf("unexpected error: %s", err) + } + } else if tt.err { + t.Error("expected error") } else if want := tt.typ; got != want { t.Errorf("unexpected type:\n\t-: \"%s\"\n\t+: \"%s\"", want, got) } }) } } + +func TestMathValuer_Call(t *testing.T) { + type values map[string]interface{} + for _, tt := range []struct { + s string + values values + exp interface{} + }{ + {s: `abs(f)`, values: values{"f": float64(2)}, exp: float64(2)}, + {s: `abs(f)`, values: values{"f": float64(-2)}, exp: float64(2)}, + {s: `abs(i)`, values: values{"i": int64(2)}, exp: int64(2)}, + {s: `abs(i)`, values: values{"i": int64(-2)}, exp: int64(-2)}, + {s: `abs(u)`, values: values{"u": uint64(2)}, exp: uint64(2)}, + {s: `sin(f)`, values: values{"f": math.Pi / 2}, exp: math.Sin(math.Pi / 2)}, + {s: `sin(i)`, values: values{"i": int64(2)}, exp: math.Sin(2)}, + {s: `sin(u)`, values: values{"u": uint64(2)}, exp: math.Sin(2)}, + {s: `asin(f)`, values: values{"f": float64(0.5)}, exp: math.Asin(0.5)}, + {s: `cos(f)`, values: values{"f": math.Pi / 2}, exp: math.Cos(math.Pi / 2)}, + {s: `cos(i)`, values: values{"i": int64(2)}, exp: math.Cos(2)}, + {s: `cos(u)`, values: values{"u": uint64(2)}, exp: math.Cos(2)}, + {s: `acos(f)`, values: values{"f": float64(0.5)}, exp: math.Acos(0.5)}, + {s: `tan(f)`, values: values{"f": math.Pi / 2}, exp: math.Tan(math.Pi / 2)}, + {s: `tan(i)`, values: values{"i": int64(2)}, exp: math.Tan(2)}, + {s: `tan(u)`, values: values{"u": uint64(2)}, exp: math.Tan(2)}, + {s: `atan(f)`, values: values{"f": float64(2)}, exp: math.Atan(2)}, + {s: `atan(i)`, values: values{"i": int64(2)}, exp: math.Atan(2)}, + {s: `atan(u)`, values: values{"u": uint64(2)}, exp: math.Atan(2)}, + {s: `atan2(y, x)`, values: values{"y": float64(2), "x": float64(3)}, exp: math.Atan2(2, 3)}, + {s: `atan2(y, x)`, values: values{"y": int64(2), "x": int64(3)}, exp: math.Atan2(2, 3)}, + {s: `atan2(y, x)`, values: values{"y": uint64(2), "x": uint64(3)}, exp: math.Atan2(2, 3)}, + {s: `floor(f)`, values: values{"f": float64(2.5)}, exp: float64(2)}, + {s: `floor(i)`, values: values{"i": int64(2)}, exp: int64(2)}, + {s: `floor(u)`, values: values{"u": uint64(2)}, exp: uint64(2)}, + {s: `ceil(f)`, values: values{"f": float64(2.5)}, exp: float64(3)}, + {s: `ceil(i)`, values: values{"i": int64(2)}, exp: int64(2)}, + {s: `ceil(u)`, values: values{"u": uint64(2)}, exp: uint64(2)}, + {s: `round(f)`, values: values{"f": float64(2.4)}, exp: float64(2)}, + {s: `round(f)`, values: values{"f": float64(2.6)}, exp: float64(3)}, + {s: `round(i)`, values: values{"i": int64(2)}, exp: int64(2)}, + {s: `round(u)`, values: values{"u": uint64(2)}, exp: uint64(2)}, + {s: `exp(f)`, values: values{"f": float64(3)}, exp: math.Exp(3)}, + {s: `exp(i)`, values: values{"i": int64(3)}, exp: math.Exp(3)}, + {s: `exp(u)`, values: values{"u": uint64(3)}, exp: math.Exp(3)}, + {s: `log(f, 8)`, values: values{"f": float64(3)}, exp: math.Log(3) / math.Log(8)}, + {s: `log(i, 8)`, values: values{"i": int64(3)}, exp: math.Log(3) / math.Log(8)}, + {s: `log(u, 8)`, values: values{"u": uint64(3)}, exp: math.Log(3) / math.Log(8)}, + {s: `ln(f)`, values: values{"f": float64(3)}, exp: math.Log(3)}, + {s: `ln(i)`, values: values{"i": int64(3)}, exp: math.Log(3)}, + {s: `ln(u)`, values: values{"u": uint64(3)}, exp: math.Log(3)}, + {s: `log2(f)`, values: values{"f": float64(3)}, exp: math.Log2(3)}, + {s: `log2(i)`, values: values{"i": int64(3)}, exp: math.Log2(3)}, + {s: `log2(u)`, values: values{"u": uint64(3)}, exp: math.Log2(3)}, + {s: `log10(f)`, values: values{"f": float64(3)}, exp: math.Log10(3)}, + {s: `log10(i)`, values: values{"i": int64(3)}, exp: math.Log10(3)}, + {s: `log10(u)`, values: values{"u": uint64(3)}, exp: math.Log10(3)}, + {s: `sqrt(f)`, values: values{"f": float64(3)}, exp: math.Sqrt(3)}, + {s: `sqrt(i)`, values: values{"i": int64(3)}, exp: math.Sqrt(3)}, + {s: `sqrt(u)`, values: values{"u": uint64(3)}, exp: math.Sqrt(3)}, + {s: `pow(f, 2)`, values: values{"f": float64(4)}, exp: math.Pow(4, 2)}, + {s: `pow(i, 2)`, values: values{"i": int64(4)}, exp: math.Pow(4, 2)}, + {s: `pow(u, 2)`, values: values{"u": uint64(4)}, exp: math.Pow(4, 2)}, + } { + t.Run(tt.s, func(t *testing.T) { + expr := MustParseExpr(tt.s) + + valuer := influxql.ValuerEval{ + Valuer: influxql.MultiValuer( + influxql.MapValuer(tt.values), + query.MathValuer{}, + ), + } + if got, want := valuer.Eval(expr), tt.exp; got != want { + t.Errorf("unexpected value: %v != %v", want, got) + } + }) + } +}