Просмотр исходного кода

fix(funcs): fix math func errors

1. Bitand, bitor, bitxor argument error
2. Math function return nil for NaN result to comply with SQL standard
3. Add ut

Signed-off-by: Jiyong Huang <huangjy@emqx.io>
Jiyong Huang 2 лет назад
Родитель
Сommit
137f286da8
2 измененных файлов с 470 добавлено и 17 удалено
  1. 87 17
      internal/binder/function/funcs_math.go
  2. 383 0
      internal/binder/function/funcs_math_test.go

+ 87 - 17
internal/binder/function/funcs_math.go

@@ -50,7 +50,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Acos(v), true
+				r := math.Acos(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -61,7 +66,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Asin(v), true
+				r := math.Asin(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -72,7 +82,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Atan(v), true
+				r := math.Atan(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -84,7 +99,12 @@ func registerMathFunc() {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v1, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				if v2, e1 := cast.ToFloat64(args[1], cast.CONVERT_SAMEKIND); e1 == nil {
-					return math.Atan2(v1, v2), true
+					r := math.Atan2(v1, v2)
+					if math.IsNaN(r) {
+						return nil, true
+					} else {
+						return r, true
+					}
 				} else {
 					return e1, false
 				}
@@ -101,7 +121,7 @@ func registerMathFunc() {
 			if err != nil {
 				return fmt.Errorf("Expect int type for the first operand but got %v", args[0]), false
 			}
-			v2, err := cast.ToInt(args[0], cast.STRICT)
+			v2, err := cast.ToInt(args[1], cast.STRICT)
 			if err != nil {
 				return fmt.Errorf("Expect int type for the second operand but got %v", args[1]), false
 			}
@@ -116,7 +136,7 @@ func registerMathFunc() {
 			if err != nil {
 				return fmt.Errorf("Expect int type for the first operand but got %v", args[0]), false
 			}
-			v2, err := cast.ToInt(args[0], cast.STRICT)
+			v2, err := cast.ToInt(args[1], cast.STRICT)
 			if err != nil {
 				return fmt.Errorf("Expect int type for the second operand but got %v", args[1]), false
 			}
@@ -131,7 +151,7 @@ func registerMathFunc() {
 			if err != nil {
 				return fmt.Errorf("Expect int type for the first operand but got %v", args[0]), false
 			}
-			v2, err := cast.ToInt(args[0], cast.STRICT)
+			v2, err := cast.ToInt(args[1], cast.STRICT)
 			if err != nil {
 				return fmt.Errorf("Expect int type for the second operand but got %v", args[1]), false
 			}
@@ -173,7 +193,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Cos(v), true
+				r := math.Cos(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -184,7 +209,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Cosh(v), true
+				r := math.Cosh(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -195,7 +225,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Exp(v), true
+				r := math.Exp(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -206,7 +241,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Log2(v), true
+				r := math.Log2(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -217,7 +257,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Log10(v), true
+				r := math.Log10(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -293,7 +338,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Sin(v), true
+				r := math.Sin(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -304,7 +354,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Sinh(v), true
+				r := math.Sinh(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -315,7 +370,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Sqrt(v), true
+				r := math.Sqrt(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -326,7 +386,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Tan(v), true
+				r := math.Tan(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}
@@ -337,7 +402,12 @@ func registerMathFunc() {
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
-				return math.Tanh(v), true
+				r := math.Tanh(v)
+				if math.IsNaN(r) {
+					return nil, true
+				} else {
+					return r, true
+				}
 			} else {
 				return e, false
 			}

+ 383 - 0
internal/binder/function/funcs_math_test.go

@@ -0,0 +1,383 @@
+// Copyright 2023 EMQ Technologies Co., Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package function
+
+import (
+	"fmt"
+	"github.com/lf-edge/ekuiper/internal/conf"
+	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
+	"github.com/lf-edge/ekuiper/internal/topo/state"
+	"github.com/lf-edge/ekuiper/pkg/api"
+	"math"
+	"reflect"
+	"testing"
+)
+
+func TestFuncMath(t *testing.T) {
+	fAbs, ok := builtins["abs"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fCeil, ok := builtins["ceil"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fExp, ok := builtins["exp"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fLn, ok := builtins["ln"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fLog10, ok := builtins["log"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fSqrt, ok := builtins["sqrt"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fPow, ok := builtins["power"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fBitAnd, ok := builtins["bitand"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fBitOr, ok := builtins["bitor"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fAcos, ok := builtins["acos"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fAsin, ok := builtins["asin"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fAtan, ok := builtins["atan"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fAtan2, ok := builtins["atan2"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fBitXor, ok := builtins["bitxor"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fBitNot, ok := builtins["bitnot"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fCos, ok := builtins["cos"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fCosh, ok := builtins["cosh"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fMod, ok := builtins["mod"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fRound, ok := builtins["round"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fSign, ok := builtins["sign"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fSin, ok := builtins["sin"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fSinh, ok := builtins["sinh"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fTan, ok := builtins["tan"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	fTanh, ok := builtins["tanh"]
+	if !ok {
+		t.Fatal("builtin not found")
+	}
+	contextLogger := conf.Log.WithField("rule", "testExec")
+	ctx := kctx.WithValue(kctx.Background(), kctx.LoggerKey, contextLogger)
+	tempStore, _ := state.CreateStore("mockRule0", api.AtMostOnce)
+	fctx := kctx.NewDefaultFuncContext(ctx.WithMeta("mockRule0", "test", tempStore), 2)
+	var tests = []struct {
+		args []interface{}
+		res  []interface{}
+	}{
+		{ // 0
+			args: []interface{}{
+				-10, 2,
+			},
+			res: []interface{}{
+				10,
+				float64(-10),
+				math.Exp(-10),
+				nil,
+				nil,
+				nil,
+				float64(100),
+				2,
+				-10,
+				nil,
+				nil,
+				math.Atan(-10),
+				math.Atan2(-10, 2),
+				-12,
+				9,
+				math.Cos(-10),
+				math.Cosh(-10),
+				float64(0),
+				float64(-10),
+				-1,
+				math.Sin(-10),
+				math.Sinh(-10),
+				math.Tan(-10),
+				math.Tanh(-10),
+			},
+		}, { // 1
+			args: []interface{}{
+				10, 2,
+			},
+			res: []interface{}{
+				10,
+				float64(10),
+				math.Exp(10),
+				math.Log2(10),
+				math.Log10(10),
+				math.Sqrt(10),
+				float64(100),
+				2,
+				10,
+				nil,
+				nil,
+				math.Atan(10),
+				math.Atan2(10, 2),
+				8,
+				-11,
+				math.Cos(10),
+				math.Cosh(10),
+				float64(0),
+				float64(10),
+				1,
+				math.Sin(10),
+				math.Sinh(10),
+				math.Tan(10),
+				math.Tanh(10),
+			},
+		}, { // 2
+			args: []interface{}{
+				-10.5, 2,
+			},
+			res: []interface{}{
+				float64(10.5),
+				float64(-10),
+				math.Exp(-10.5),
+				nil,
+				nil,
+				nil,
+				110.25,
+				fmt.Errorf("Expect int type for the first operand but got -10.5"),
+				fmt.Errorf("Expect int type for the first operand but got -10.5"),
+				nil,
+				nil,
+				math.Atan(-10.5),
+				math.Atan2(-10.5, 2),
+				fmt.Errorf("Expect int type for the first operand but got -10.5"),
+				fmt.Errorf("Expect int type for operand but got -10.5"),
+				math.Cos(-10.5),
+				math.Cosh(-10.5),
+				-0.5,
+				float64(-11),
+				-1,
+				math.Sin(-10.5),
+				math.Sinh(-10.5),
+				math.Tan(-10.5),
+				math.Tanh(-10.5),
+			},
+		}, { // 3
+			args: []interface{}{
+				10.5, 2,
+			},
+			res: []interface{}{
+				10.5,
+				float64(11),
+				math.Exp(10.5),
+				math.Log2(10.5),
+				math.Log10(10.5),
+				math.Sqrt(10.5),
+				110.25,
+				fmt.Errorf("Expect int type for the first operand but got 10.5"),
+				fmt.Errorf("Expect int type for the first operand but got 10.5"),
+				nil,
+				nil,
+				math.Atan(10.5),
+				math.Atan2(10.5, 2),
+				fmt.Errorf("Expect int type for the first operand but got 10.5"),
+				fmt.Errorf("Expect int type for operand but got 10.5"),
+				math.Cos(10.5),
+				math.Cosh(10.5),
+				0.5,
+				float64(11),
+				1,
+				math.Sin(10.5),
+				math.Sinh(10.5),
+				math.Tan(10.5),
+				math.Tanh(10.5),
+			},
+		}, { // 4
+			args: []interface{}{
+				0, 2,
+			},
+			res: []interface{}{
+				0,
+				float64(0),
+				float64(1),
+				math.Inf(-1),
+				math.Inf(-1),
+				float64(0),
+				float64(0),
+				0,
+				2,
+				math.Acos(0),
+				math.Asin(0),
+				math.Atan(0),
+				math.Atan2(0, 2),
+				2,
+				-1,
+				math.Cos(0),
+				math.Cosh(0),
+				float64(0),
+				float64(0),
+				0,
+				math.Sin(0),
+				math.Sinh(0),
+				math.Tan(0),
+				math.Tanh(0),
+			},
+		},
+	}
+	for i, tt := range tests {
+		rAbs, _ := fAbs.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rAbs, tt.res[0]) {
+			t.Errorf("%d.0 abs result mismatch,\ngot:\t%v \nwant:\t%v", i, rAbs, tt.res[0])
+		}
+		rCeil, _ := fCeil.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rCeil, tt.res[1]) {
+			t.Errorf("%d.1 ceil result mismatch,\ngot:\t%v \nwant:\t%v", i, rCeil, tt.res[1])
+		}
+		rExp, _ := fExp.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rExp, tt.res[2]) {
+			t.Errorf("%d.2 exp result mismatch,\ngot:\t%v \nwant:\t%v", i, rExp, tt.res[2])
+		}
+		rLn, _ := fLn.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rLn, tt.res[3]) {
+			t.Errorf("%d.3 ln result mismatch,\ngot:\t%v \nwant:\t%v", i, rLn, tt.res[3])
+		}
+		rLog10, _ := fLog10.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rLog10, tt.res[4]) {
+			t.Errorf("%d.4 log result mismatch,\ngot:\t%v \nwant:\t%v", i, rLog10, tt.res[4])
+		}
+		rSqrt, _ := fSqrt.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rSqrt, tt.res[5]) {
+			t.Errorf("%d.5 sqrt result mismatch,\ngot:\t%v \nwant:\t%v", i, rSqrt, tt.res[5])
+		}
+		rPow, _ := fPow.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rPow, tt.res[6]) {
+			t.Errorf("%d.6 power result mismatch,\ngot:\t%v \nwant:\t%v", i, rPow, tt.res[6])
+		}
+		rBitAnd, _ := fBitAnd.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rBitAnd, tt.res[7]) {
+			t.Errorf("%d.7 bitand result mismatch,\ngot:\t%v \nwant:\t%v", i, rBitAnd, tt.res[7])
+		}
+		rBitOr, _ := fBitOr.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rBitOr, tt.res[8]) {
+			t.Errorf("%d.8 bitor result mismatch,\ngot:\t%v \nwant:\t%v", i, rBitOr, tt.res[8])
+		}
+		rAcos, _ := fAcos.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rAcos, tt.res[9]) {
+			t.Errorf("%d.9 acos result mismatch,\ngot:\t%v \nwant:\t%v", i, rAcos, tt.res[9])
+		}
+		rAsin, _ := fAsin.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rAsin, tt.res[10]) {
+			t.Errorf("%d.10 asin result mismatch,\ngot:\t%v \nwant:\t%v", i, rAsin, tt.res[10])
+		}
+		rAtan, _ := fAtan.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rAtan, tt.res[11]) {
+			t.Errorf("%d.11 atan result mismatch,\ngot:\t%v \nwant:\t%v", i, rAtan, tt.res[11])
+		}
+		rAtan2, _ := fAtan2.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rAtan2, tt.res[12]) {
+			t.Errorf("%d.12 atan2 result mismatch,\ngot:\t%v \nwant:\t%v", i, rAtan2, tt.res[12])
+		}
+		rBitXor, _ := fBitXor.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rBitXor, tt.res[13]) {
+			t.Errorf("%d.13 bitxor result mismatch,\ngot:\t%v \nwant:\t%v", i, rBitXor, tt.res[13])
+		}
+		rBitNot, _ := fBitNot.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rBitNot, tt.res[14]) {
+			t.Errorf("%d.14 bitnot result mismatch,\ngot:\t%v \nwant:\t%v", i, rBitNot, tt.res[14])
+		}
+		rCos, _ := fCos.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rCos, tt.res[15]) {
+			t.Errorf("%d.15 cos result mismatch,\ngot:\t%v \nwant:\t%v", i, rCos, tt.res[15])
+		}
+		rCosh, _ := fCosh.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rCosh, tt.res[16]) {
+			t.Errorf("%d.16 cosh result mismatch,\ngot:\t%v \nwant:\t%v", i, rCosh, tt.res[16])
+		}
+		rMod, _ := fMod.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rMod, tt.res[17]) {
+			t.Errorf("%d.17 mod result mismatch,\ngot:\t%v \nwant:\t%v", i, rMod, tt.res[17])
+		}
+		rRound, _ := fRound.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rRound, tt.res[18]) {
+			t.Errorf("%d.18 round result mismatch,\ngot:\t%v \nwant:\t%v", i, rRound, tt.res[18])
+		}
+		rSign, _ := fSign.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rSign, tt.res[19]) {
+			t.Errorf("%d.19 sign result mismatch,\ngot:\t%v \nwant:\t%v", i, rSign, tt.res[19])
+		}
+		rSin, _ := fSin.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rSin, tt.res[20]) {
+			t.Errorf("%d.20 sin result mismatch,\ngot:\t%v \nwant:\t%v", i, rSin, tt.res[20])
+		}
+		rSinh, _ := fSinh.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rSinh, tt.res[21]) {
+			t.Errorf("%d.21 sinh result mismatch,\ngot:\t%v \nwant:\t%v", i, rSinh, tt.res[21])
+		}
+		rTan, _ := fTan.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rTan, tt.res[22]) {
+			t.Errorf("%d.22 tan result mismatch,\ngot:\t%v \nwant:\t%v", i, rTan, tt.res[22])
+		}
+		rTanh, _ := fTanh.exec(fctx, tt.args)
+		if !reflect.DeepEqual(rTanh, tt.res[23]) {
+			t.Errorf("%d.23 tanh result mismatch,\ngot:\t%v \nwant:\t%v", i, rTanh, tt.res[23])
+		}
+	}
+}