Repository: lf-edge/ekuiper
Pull request #1597 (2023-01-09 07:24:10)

fix(function): fix int validation to allow int64 and other int types


Patch:
diff --git a/internal/binder/function/funcs_analytic.go b/internal/binder/function/funcs_analytic.go
index d6450c13..15d01d76 100644
--- a/internal/binder/function/funcs_analytic.go
+++ b/internal/binder/function/funcs_analytic.go
@@ -1,4 +1,4 @@
-// Copyright 2022 EMQ Technologies Co., Ltd.
+// Copyright 2022-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.
@@ -18,6 +18,7 @@ import (
 	"fmt"
 	"github.com/lf-edge/ekuiper/pkg/api"
 	"github.com/lf-edge/ekuiper/pkg/ast"
+	"github.com/lf-edge/ekuiper/pkg/cast"
 	"reflect"
 	"strconv"
 )
@@ -148,11 +149,10 @@ func registerAnalyticFunc() {
 				if paraLen == 1 {
 					size = 1
 				} else {
-					siz, ok := args[1].(int)
-					if !ok {
-						return fmt.Errorf("second arg is not a int but got %v", args[1]), false
+					size, err = cast.ToInt(args[1], cast.STRICT)
+					if err != nil {
+						return fmt.Errorf("error converting second arg %v to int: %v", args[1], err), false
 					}
-					size = siz
 				}
 				rq = newRingqueue(size)
 				rq.fill(dftVal)
diff --git a/internal/binder/function/funcs_math.go b/internal/binder/function/funcs_math.go
index 15693ab5..c9befb35 100644
--- a/internal/binder/function/funcs_math.go
+++ b/internal/binder/function/funcs_math.go
@@ -1,4 +1,4 @@
-// Copyright 2022 EMQ Technologies Co., Ltd.
+// Copyright 2022-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.
@@ -18,6 +18,7 @@ import (
 	"fmt"
 	"github.com/lf-edge/ekuiper/pkg/api"
 	"github.com/lf-edge/ekuiper/pkg/ast"
+	"github.com/lf-edge/ekuiper/pkg/cast"
 	"math"
 	"math/rand"
 )
@@ -26,13 +27,20 @@ func registerMathFunc() {
 	builtins["abs"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, ok := args[0].(int); ok {
-				t := float64(v)
-				var ret = int(math.Abs(t))
-				return ret, true
-			} else if v, ok := args[0].(float64); ok {
+			switch v := args[0].(type) {
+			case int:
+				return int(math.Abs(float64(v))), true
+			case int64:
+				return int64(math.Abs(float64(v))), true
+			case float64:
 				return math.Abs(v), true
-			} else {
+			default:
+				if vi, err := cast.ToInt(v, cast.STRICT); err == nil {
+					return int(math.Abs(float64(vi))), true
+				}
+				if vf, err := cast.ToFloat64(v, cast.STRICT); err == nil {
+					return math.Abs(vf), true
+				}
 				return fmt.Errorf("only float64 & int type are supported"), false
 			}
 		},
@@ -41,7 +49,7 @@ func registerMathFunc() {
 	builtins["acos"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Acos(v), true
 			} else {
 				return e, false
@@ -52,7 +60,7 @@ func registerMathFunc() {
 	builtins["asin"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Asin(v), true
 			} else {
 				return e, false
@@ -63,7 +71,7 @@ func registerMathFunc() {
 	builtins["atan"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Atan(v), true
 			} else {
 				return e, false
@@ -74,8 +82,8 @@ func registerMathFunc() {
 	builtins["atan2"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v1, e := toF64(args[0]); e == nil {
-				if v2, e1 := toF64(args[1]); e1 == nil {
+			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
 				} else {
 					return e1, false
@@ -89,51 +97,56 @@ func registerMathFunc() {
 	builtins["bitand"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			v1, ok1 := args[0].(int)
-			v2, ok2 := args[1].(int)
-			if ok1 && ok2 {
-				return v1 & v2, true
-			} else {
-				return fmt.Errorf("Expect int type for both operands."), false
+			v1, err := cast.ToInt(args[0], cast.STRICT)
+			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)
+			if err != nil {
+				return fmt.Errorf("Expect int type for the second operand but got %v", args[1]), false
+			}
+			return v1 & v2, true
 		},
 		val: ValidateTwoIntArg,
 	}
 	builtins["bitor"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			v1, ok1 := args[0].(int)
-			v2, ok2 := args[1].(int)
-			if ok1 && ok2 {
-				return v1 | v2, true
-			} else {
-				return fmt.Errorf("Expect int type for both operands."), false
+			v1, err := cast.ToInt(args[0], cast.STRICT)
+			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)
+			if err != nil {
+				return fmt.Errorf("Expect int type for the second operand but got %v", args[1]), false
 			}
+			return v1 | v2, true
 		},
 		val: ValidateTwoIntArg,
 	}
 	builtins["bitxor"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			v1, ok1 := args[0].(int)
-			v2, ok2 := args[1].(int)
-			if ok1 && ok2 {
-				return v1 ^ v2, true
-			} else {
-				return fmt.Errorf("Expect int type for both operands."), false
+			v1, err := cast.ToInt(args[0], cast.STRICT)
+			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)
+			if err != nil {
+				return fmt.Errorf("Expect int type for the second operand but got %v", args[1]), false
 			}
+			return v1 ^ v2, true
 		},
 		val: ValidateTwoIntArg,
 	}
 	builtins["bitnot"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			v1, ok1 := args[0].(int)
-			if ok1 {
-				return ^v1, true
-			} else {
-				return fmt.Errorf("Expect int type for operand."), false
+			v1, err := cast.ToInt(args[0], cast.STRICT)
+			if err != nil {
+				return fmt.Errorf("Expect int type for operand but got %v", args[0]), false
 			}
+			return ^v1, true
 		},
 		val: func(_ api.FunctionContext, args []ast.Expr) error {
 			if err := ValidateLen(1, len(args)); err != nil {
@@ -148,7 +161,7 @@ func registerMathFunc() {
 	builtins["ceil"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Ceil(v), true
 			} else {
 				return e, false
@@ -159,7 +172,7 @@ func registerMathFunc() {
 	builtins["cos"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Cos(v), true
 			} else {
 				return e, false
@@ -170,7 +183,7 @@ func registerMathFunc() {
 	builtins["cosh"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Cosh(v), true
 			} else {
 				return e, false
@@ -181,7 +194,7 @@ func registerMathFunc() {
 	builtins["exp"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Exp(v), true
 			} else {
 				return e, false
@@ -192,7 +205,7 @@ func registerMathFunc() {
 	builtins["ln"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Log2(v), true
 			} else {
 				return e, false
@@ -203,7 +216,7 @@ func registerMathFunc() {
 	builtins["log"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Log10(v), true
 			} else {
 				return e, false
@@ -214,8 +227,8 @@ func registerMathFunc() {
 	builtins["mod"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
-				if v1, e1 := toF64(args[1]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
+				if v1, e1 := cast.ToFloat64(args[1], cast.CONVERT_SAMEKIND); e == nil {
 					return math.Mod(v, v1), true
 				} else {
 					return e1, false
@@ -229,8 +242,8 @@ func registerMathFunc() {
 	builtins["power"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v1, e := toF64(args[0]); e == nil {
-				if v2, e2 := toF64(args[1]); e2 == nil {
+			if v1, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
+				if v2, e2 := cast.ToFloat64(args[1], cast.CONVERT_SAMEKIND); e2 == nil {
 					return math.Pow(v1, v2), true
 				} else {
 					return e2, false
@@ -251,7 +264,7 @@ func registerMathFunc() {
 	builtins["round"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Round(v), true
 			} else {
 				return e, false
@@ -262,7 +275,7 @@ func registerMathFunc() {
 	builtins["sign"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				if v > 0 {
 					return 1, true
 				} else if v < 0 {
@@ -279,7 +292,7 @@ func registerMathFunc() {
 	builtins["sin"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Sin(v), true
 			} else {
 				return e, false
@@ -290,7 +303,7 @@ func registerMathFunc() {
 	builtins["sinh"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Sinh(v), true
 			} else {
 				return e, false
@@ -301,7 +314,7 @@ func registerMathFunc() {
 	builtins["sqrt"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Sqrt(v), true
 			} else {
 				return e, false
@@ -312,7 +325,7 @@ func registerMathFunc() {
 	builtins["tan"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Tan(v), true
 			} else {
 				return e, false
@@ -323,7 +336,7 @@ func registerMathFunc() {
 	builtins["tanh"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if v, e := toF64(args[0]); e == nil {
+			if v, e := cast.ToFloat64(args[0], cast.CONVERT_SAMEKIND); e == nil {
 				return math.Tanh(v), true
 			} else {
 				return e, false
@@ -332,12 +345,3 @@ func registerMathFunc() {
 		val: ValidateOneNumberArg,
 	}
 }
-
-func toF64(arg interface{}) (float64, error) {
-	if v, ok := arg.(float64); ok {
-		return v, nil
-	} else if v, ok := arg.(int); ok {
-		return float64(v), nil
-	}
-	return 0, fmt.Errorf("only float64 & int type are supported")
-}


Test patch:
diff --git a/internal/topo/operator/math_func_test.go b/internal/topo/operator/math_func_test.go
index d134e2c5..628d530b 100644
--- a/internal/topo/operator/math_func_test.go
+++ b/internal/topo/operator/math_func_test.go
@@ -1,4 +1,4 @@
-// Copyright 2022 EMQ Technologies Co., Ltd.
+// Copyright 2022-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.
@@ -42,6 +42,18 @@ func TestMathAndConversionFunc_Apply1(t *testing.T) {
 				"a": 1,
 			}},
 		},
+		{
+			sql: "SELECT abs(a) AS a FROM test",
+			data: &xsql.Tuple{
+				Emitter: "test",
+				Message: xsql.Message{
+					"a": int64(-1),
+				},
+			},
+			result: []map[string]interface{}{{
+				"a": int64(1),
+			}},
+		},
 
 		{
 			sql: "SELECT abs(a) AS a FROM test",
diff --git a/internal/topo/operator/project_test.go b/internal/topo/operator/project_test.go
index 20d9b2c1..e00f866d 100644
--- a/internal/topo/operator/project_test.go
+++ b/internal/topo/operator/project_test.go
@@ -1,4 +1,4 @@
-// Copyright 2022 EMQ Technologies Co., Ltd.
+// Copyright 2022-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.
@@ -2229,7 +2229,7 @@ func TestProjectPlanError(t *testing.T) {
 					"a": "common string",
 				},
 			},
-			result: errors.New("run Select error: call func round error: only float64 & int type are supported"),
+			result: errors.New("run Select error: call func round error: cannot convert string(common string) to float64"),
 		},
 		//4
 		{
@@ -2240,7 +2240,7 @@ func TestProjectPlanError(t *testing.T) {
 					"abc": "common string",
 				},
 			},
-			result: errors.New("run Select error: call func round error: only float64 & int type are supported"),
+			result: errors.New("run Select error: call func round error: cannot convert <nil>(<nil>) to float64"),
 		},
 		//5
 		{