Skip to content

Commit

Permalink
Merge pull request #9620 from Tomcat-Engineering/more-math-functions
Browse files Browse the repository at this point in the history
Add more math functions to influxql
  • Loading branch information
jsternberg committed Apr 17, 2018
2 parents 7463195 + 42581c7 commit 672a32a
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 20 deletions.
50 changes: 42 additions & 8 deletions query/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

// 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 c.compileExpr(expr.Args[0])
return nil
}

func (c *compiledStatement) compileDimensions(stmt *influxql.SelectStatement) error {
Expand Down Expand Up @@ -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
}

// 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 c.validateCondition(expr.Args[0])
return nil
default:
return nil
}
Expand Down
38 changes: 38 additions & 0 deletions query/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
113 changes: 101 additions & 12 deletions query/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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] {
Expand All @@ -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:
Expand Down Expand Up @@ -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
}
}

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 fn(value), true
return arg0, arg1, true
}

func round(x float64) float64 {
Expand Down

0 comments on commit 672a32a

Please sign in to comment.