Add new math functions:
- abs - asin, acos, atan, atan2 - exp, ln, log, log2, log10 - pow, sqrtpull/9620/head
parent
7ebfc9c544
commit
42581c7432
|
@ -250,7 +250,7 @@ func (c *compiledField) compileExpr(expr influxql.Expr) error {
|
|||
return nil
|
||||
case *influxql.Call:
|
||||
if isMathFunction(expr) {
|
||||
return c.compileTrigFunction(expr)
|
||||
return c.compileMathFunction(expr)
|
||||
}
|
||||
|
||||
// Register the function call in the list of function calls.
|
||||
|
@ -668,11 +668,29 @@ func (c *compiledField) compileTopBottom(call *influxql.Call) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *compiledField) compileTrigFunction(expr *influxql.Call) error {
|
||||
if exp, got := 1, len(expr.Args); exp != got {
|
||||
return fmt.Errorf("invalid number of arguments for %s, expected %d, got %d", expr.Name, exp, got)
|
||||
func (c *compiledField) compileMathFunction(expr *influxql.Call) error {
|
||||
// How many arguments are we expecting?
|
||||
nargs := 1
|
||||
switch expr.Name {
|
||||
case "atan2", "pow", "log":
|
||||
nargs = 2
|
||||
}
|
||||
return c.compileExpr(expr.Args[0])
|
||||
|
||||
// Did we get the expected number of args?
|
||||
if got := len(expr.Args); got != nargs {
|
||||
return fmt.Errorf("invalid number of arguments for %s, expected %d, got %d", expr.Name, nargs, got)
|
||||
}
|
||||
|
||||
// Compile all the argument expressions that are not just literals.
|
||||
for _, arg := range expr.Args {
|
||||
if _, ok := arg.(influxql.Literal); ok {
|
||||
continue
|
||||
}
|
||||
if err := c.compileExpr(arg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *compiledStatement) compileDimensions(stmt *influxql.SelectStatement) error {
|
||||
|
@ -801,10 +819,26 @@ func (c *compiledStatement) validateCondition(expr influxql.Expr) error {
|
|||
if !isMathFunction(expr) {
|
||||
return fmt.Errorf("invalid function call in condition: %s", expr)
|
||||
}
|
||||
if exp, got := 1, len(expr.Args); exp != got {
|
||||
return fmt.Errorf("invalid number of arguments for %s, expected %d, got %d", expr.Name, exp, got)
|
||||
|
||||
// How many arguments are we expecting?
|
||||
nargs := 1
|
||||
switch expr.Name {
|
||||
case "atan2", "pow":
|
||||
nargs = 2
|
||||
}
|
||||
return c.validateCondition(expr.Args[0])
|
||||
|
||||
// Did we get the expected number of args?
|
||||
if got := len(expr.Args); got != nargs {
|
||||
return fmt.Errorf("invalid number of arguments for %s, expected %d, got %d", expr.Name, nargs, got)
|
||||
}
|
||||
|
||||
// Are all the args valid?
|
||||
for _, arg := range expr.Args {
|
||||
if err := c.validateCondition(arg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -82,6 +82,29 @@ func TestCompile_Success(t *testing.T) {
|
|||
`SELECT value FROM (SELECT value FROM cpu) ORDER BY time DESC`,
|
||||
`SELECT count(distinct(value)), max(value) FROM cpu`,
|
||||
`SELECT last(value) / (1 - 0) FROM cpu`,
|
||||
`SELECT abs(value) FROM cpu`,
|
||||
`SELECT sin(value) FROM cpu`,
|
||||
`SELECT cos(value) FROM cpu`,
|
||||
`SELECT tan(value) FROM cpu`,
|
||||
`SELECT asin(value) FROM cpu`,
|
||||
`SELECT acos(value) FROM cpu`,
|
||||
`SELECT atan(value) FROM cpu`,
|
||||
`SELECT sqrt(value) FROM cpu`,
|
||||
`SELECT pow(value, 2) FROM cpu`,
|
||||
`SELECT pow(value, 3.14) FROM cpu`,
|
||||
`SELECT pow(2, value) FROM cpu`,
|
||||
`SELECT pow(3.14, value) FROM cpu`,
|
||||
`SELECT exp(value) FROM cpu`,
|
||||
`SELECT atan2(value, 0.1) FROM cpu`,
|
||||
`SELECT atan2(0.2, value) FROM cpu`,
|
||||
`SELECT atan2(value, 1) FROM cpu`,
|
||||
`SELECT atan2(2, value) FROM cpu`,
|
||||
`SELECT ln(value) FROM cpu`,
|
||||
`SELECT log(value, 2) FROM cpu`,
|
||||
`SELECT log2(value) FROM cpu`,
|
||||
`SELECT log10(value) FROM cpu`,
|
||||
`SELECT sin(value) - sin(1.3) FROM cpu`,
|
||||
`SELECT value FROM cpu WHERE sin(value) > 0.5`,
|
||||
} {
|
||||
t.Run(tt, func(t *testing.T) {
|
||||
stmt, err := influxql.ParseStatement(tt)
|
||||
|
@ -318,6 +341,21 @@ func TestCompile_Failures(t *testing.T) {
|
|||
{s: `SELECT value FROM myseries WHERE value OR time >= now() - 1m`, err: `invalid condition expression: value`},
|
||||
{s: `SELECT value FROM myseries WHERE time >= now() - 1m OR value`, err: `invalid condition expression: value`},
|
||||
{s: `SELECT value FROM (SELECT value FROM cpu ORDER BY time DESC) ORDER BY time ASC`, err: `subqueries must be ordered in the same direction as the query itself`},
|
||||
{s: `SELECT sin(value, 3) FROM cpu`, err: `invalid number of arguments for sin, expected 1, got 2`},
|
||||
{s: `SELECT cos(2.3, value, 3) FROM cpu`, err: `invalid number of arguments for cos, expected 1, got 3`},
|
||||
{s: `SELECT tan(value, 3) FROM cpu`, err: `invalid number of arguments for tan, expected 1, got 2`},
|
||||
{s: `SELECT asin(value, 3) FROM cpu`, err: `invalid number of arguments for asin, expected 1, got 2`},
|
||||
{s: `SELECT acos(value, 3.2) FROM cpu`, err: `invalid number of arguments for acos, expected 1, got 2`},
|
||||
{s: `SELECT atan() FROM cpu`, err: `invalid number of arguments for atan, expected 1, got 0`},
|
||||
{s: `SELECT sqrt(42, 3, 4) FROM cpu`, err: `invalid number of arguments for sqrt, expected 1, got 3`},
|
||||
{s: `SELECT abs(value, 3) FROM cpu`, err: `invalid number of arguments for abs, expected 1, got 2`},
|
||||
{s: `SELECT ln(value, 3) FROM cpu`, err: `invalid number of arguments for ln, expected 1, got 2`},
|
||||
{s: `SELECT log2(value, 3) FROM cpu`, err: `invalid number of arguments for log2, expected 1, got 2`},
|
||||
{s: `SELECT log10(value, 3) FROM cpu`, err: `invalid number of arguments for log10, expected 1, got 2`},
|
||||
{s: `SELECT pow(value, 3, 3) FROM cpu`, err: `invalid number of arguments for pow, expected 2, got 3`},
|
||||
{s: `SELECT atan2(value, 3, 3) FROM cpu`, err: `invalid number of arguments for atan2, expected 2, got 3`},
|
||||
{s: `SELECT sin(1.3) FROM cpu`, err: `field must contain at least one variable`},
|
||||
{s: `SELECT nofunc(1.3) FROM cpu`, err: `undefined function nofunc()`},
|
||||
} {
|
||||
t.Run(tt.s, func(t *testing.T) {
|
||||
stmt, err := influxql.ParseStatement(tt.s)
|
||||
|
|
113
query/math.go
113
query/math.go
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func isMathFunction(call *influxql.Call) bool {
|
||||
switch call.Name {
|
||||
case "sin", "cos", "tan", "floor", "ceil", "round":
|
||||
case "abs", "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "exp", "log", "ln", "log2", "log10", "sqrt", "pow", "floor", "ceil", "round":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -23,7 +23,7 @@ func (MathTypeMapper) MapType(measurement *influxql.Measurement, field string) i
|
|||
|
||||
func (MathTypeMapper) CallType(name string, args []influxql.DataType) (influxql.DataType, error) {
|
||||
switch name {
|
||||
case "sin", "cos", "tan":
|
||||
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] {
|
||||
|
@ -48,12 +48,30 @@ func (v MathValuer) Call(name string, args []interface{}) (interface{}, bool) {
|
|||
if len(args) == 1 {
|
||||
arg0 := args[0]
|
||||
switch name {
|
||||
case "abs":
|
||||
switch arg0 := arg0.(type) {
|
||||
case float64:
|
||||
return math.Abs(arg0), true
|
||||
case int64, uint64:
|
||||
return arg0, true
|
||||
default:
|
||||
return nil, true
|
||||
}
|
||||
case "sin":
|
||||
return v.callTrigFunction(math.Sin, arg0)
|
||||
if arg0, ok := asFloat(arg0); ok {
|
||||
return math.Sin(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
case "cos":
|
||||
return v.callTrigFunction(math.Cos, arg0)
|
||||
if arg0, ok := asFloat(arg0); ok {
|
||||
return math.Cos(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
case "tan":
|
||||
return v.callTrigFunction(math.Tan, arg0)
|
||||
if arg0, ok := asFloat(arg0); ok {
|
||||
return math.Tan(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
case "floor":
|
||||
switch arg0 := arg0.(type) {
|
||||
case float64:
|
||||
|
@ -81,22 +99,93 @@ func (v MathValuer) Call(name string, args []interface{}) (interface{}, bool) {
|
|||
default:
|
||||
return nil, true
|
||||
}
|
||||
case "asin":
|
||||
if arg0, ok := asFloat(arg0); ok {
|
||||
return math.Asin(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
case "acos":
|
||||
if arg0, ok := asFloat(arg0); ok {
|
||||
return math.Acos(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
case "atan":
|
||||
if arg0, ok := asFloat(arg0); ok {
|
||||
return math.Atan(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
case "exp":
|
||||
if arg0, ok := asFloat(arg0); ok {
|
||||
return math.Exp(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
case "ln":
|
||||
if arg0, ok := asFloat(arg0); ok {
|
||||
return math.Log(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
case "log2":
|
||||
if arg0, ok := asFloat(args); ok {
|
||||
return math.Log2(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
case "log10":
|
||||
if arg0, ok := asFloat(arg0); ok {
|
||||
return math.Log10(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
case "sqrt":
|
||||
if arg0, ok := asFloat(arg0); ok {
|
||||
return math.Sqrt(arg0), true
|
||||
}
|
||||
return nil, true
|
||||
}
|
||||
} else if len(args) == 2 {
|
||||
arg0, arg1 := args[0], args[1]
|
||||
switch name {
|
||||
case "atan2":
|
||||
if arg0, arg1, ok := asFloats(arg0, arg1); ok {
|
||||
return math.Atan2(arg0, arg1), true
|
||||
}
|
||||
return nil, true
|
||||
case "log":
|
||||
if arg0, arg1, ok := asFloats(arg0, arg1); ok {
|
||||
return math.Log(arg0) / math.Log(arg1), true
|
||||
}
|
||||
return nil, true
|
||||
case "pow":
|
||||
if arg0, arg1, ok := asFloats(arg0, arg1); ok {
|
||||
return math.Pow(arg0, arg1), true
|
||||
}
|
||||
return nil, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (MathValuer) callTrigFunction(fn func(x float64) float64, arg0 interface{}) (interface{}, bool) {
|
||||
var value float64
|
||||
switch arg0 := arg0.(type) {
|
||||
func asFloat(x interface{}) (float64, bool) {
|
||||
switch arg0 := x.(type) {
|
||||
case float64:
|
||||
value = arg0
|
||||
return arg0, true
|
||||
case int64:
|
||||
value = float64(arg0)
|
||||
return float64(arg0), true
|
||||
case uint64:
|
||||
return float64(arg0), true
|
||||
default:
|
||||
return nil, false
|
||||
return 0, false
|
||||
}
|
||||
return fn(value), true
|
||||
}
|
||||
|
||||
func asFloats(x, y interface{}) (float64, float64, bool) {
|
||||
arg0, ok := asFloat(x)
|
||||
if !ok {
|
||||
return 0, 0, false
|
||||
}
|
||||
arg1, ok := asFloat(y)
|
||||
if !ok {
|
||||
return 0, 0, false
|
||||
}
|
||||
return arg0, arg1, true
|
||||
}
|
||||
|
||||
func round(x float64) float64 {
|
||||
|
|
Loading…
Reference in New Issue