Quellcode durchsuchen

feat: add nil check for functions (#1971)

* add math nil check

Signed-off-by: yisaer <disxiaofei@163.com>

* add misc nil check

Signed-off-by: yisaer <disxiaofei@163.com>

* add obj funs nil

Signed-off-by: yisaer <disxiaofei@163.com>

* add srf funs nil

Signed-off-by: yisaer <disxiaofei@163.com>

* add str funcs nil

Signed-off-by: yisaer <disxiaofei@163.com>

* add array funcs nil

Signed-off-by: yisaer <disxiaofei@163.com>

* add agg funcs nil

Signed-off-by: yisaer <disxiaofei@163.com>

* fix test

Signed-off-by: yisaer <disxiaofei@163.com>

* fix test

Signed-off-by: yisaer <disxiaofei@163.com>

* address the comment math func

Signed-off-by: yisaer <disxiaofei@163.com>

* address the comment misc func

Signed-off-by: yisaer <disxiaofei@163.com>

* address the comment obj func

Signed-off-by: yisaer <disxiaofei@163.com>

* address the comment srf func

Signed-off-by: yisaer <disxiaofei@163.com>

* address the comment str func

Signed-off-by: yisaer <disxiaofei@163.com>

* address the comment array func

Signed-off-by: yisaer <disxiaofei@163.com>

* address the comment array func

Signed-off-by: yisaer <disxiaofei@163.com>

---------

Signed-off-by: yisaer <disxiaofei@163.com>
Song Gao vor 1 Jahr
Ursprung
Commit
42b4d744c2

+ 47 - 11
internal/binder/function/funcs_agg.go

@@ -53,7 +53,8 @@ func registerAggFunc() {
 			}
 			}
 			return nil, true
 			return nil, true
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["count"] = builtinFunc{
 	builtins["count"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -61,7 +62,8 @@ func registerAggFunc() {
 			arg0 := args[0].([]interface{})
 			arg0 := args[0].([]interface{})
 			return getCount(arg0), true
 			return getCount(arg0), true
 		},
 		},
-		val: ValidateOneArg,
+		val:   ValidateOneArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["max"] = builtinFunc{
 	builtins["max"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -102,7 +104,8 @@ func registerAggFunc() {
 			}
 			}
 			return nil, true
 			return nil, true
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["min"] = builtinFunc{
 	builtins["min"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -143,7 +146,8 @@ func registerAggFunc() {
 			}
 			}
 			return nil, true
 			return nil, true
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["sum"] = builtinFunc{
 	builtins["sum"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -172,7 +176,8 @@ func registerAggFunc() {
 			}
 			}
 			return nil, true
 			return nil, true
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["collect"] = builtinFunc{
 	builtins["collect"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -212,6 +217,7 @@ func registerAggFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["stddev"] = builtinFunc{
 	builtins["stddev"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -233,7 +239,8 @@ func registerAggFunc() {
 			}
 			}
 			return nil, true
 			return nil, true
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["stddevs"] = builtinFunc{
 	builtins["stddevs"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -255,7 +262,8 @@ func registerAggFunc() {
 			}
 			}
 			return nil, true
 			return nil, true
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["var"] = builtinFunc{
 	builtins["var"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -277,7 +285,8 @@ func registerAggFunc() {
 			}
 			}
 			return nil, true
 			return nil, true
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["vars"] = builtinFunc{
 	builtins["vars"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -299,7 +308,8 @@ func registerAggFunc() {
 			}
 			}
 			return nil, true
 			return nil, true
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["percentile_cont"] = builtinFunc{
 	builtins["percentile_cont"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -335,7 +345,8 @@ func registerAggFunc() {
 			}
 			}
 			return nil, true
 			return nil, true
 		},
 		},
-		val: ValidateTwoNumberArg,
+		val:   ValidateTwoNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["percentile_disc"] = builtinFunc{
 	builtins["percentile_disc"] = builtinFunc{
 		fType: ast.FuncTypeAgg,
 		fType: ast.FuncTypeAgg,
@@ -370,7 +381,8 @@ func registerAggFunc() {
 			}
 			}
 			return nil, true
 			return nil, true
 		},
 		},
-		val: ValidateTwoNumberArg,
+		val:   ValidateTwoNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 }
 }
 
 
@@ -396,6 +408,9 @@ func getFirstValidArg(s []interface{}) interface{} {
 func sliceIntTotal(s []interface{}) (int64, error) {
 func sliceIntTotal(s []interface{}) (int64, error) {
 	var total int64
 	var total int64
 	for _, v := range s {
 	for _, v := range s {
+		if v == nil {
+			continue
+		}
 		vi, err := cast.ToInt64(v, cast.CONVERT_SAMEKIND)
 		vi, err := cast.ToInt64(v, cast.CONVERT_SAMEKIND)
 		if err == nil {
 		if err == nil {
 			total += vi
 			total += vi
@@ -409,6 +424,9 @@ func sliceIntTotal(s []interface{}) (int64, error) {
 func sliceFloatTotal(s []interface{}) (float64, error) {
 func sliceFloatTotal(s []interface{}) (float64, error) {
 	var total float64
 	var total float64
 	for _, v := range s {
 	for _, v := range s {
+		if v == nil {
+			continue
+		}
 		if vf, ok := v.(float64); ok {
 		if vf, ok := v.(float64); ok {
 			total += vf
 			total += vf
 		} else if v != nil {
 		} else if v != nil {
@@ -420,6 +438,9 @@ func sliceFloatTotal(s []interface{}) (float64, error) {
 
 
 func sliceIntMax(s []interface{}, max int64) (int64, error) {
 func sliceIntMax(s []interface{}, max int64) (int64, error) {
 	for _, v := range s {
 	for _, v := range s {
+		if v == nil {
+			continue
+		}
 		vi, err := cast.ToInt64(v, cast.CONVERT_SAMEKIND)
 		vi, err := cast.ToInt64(v, cast.CONVERT_SAMEKIND)
 		if err == nil {
 		if err == nil {
 			if vi > max {
 			if vi > max {
@@ -434,6 +455,9 @@ func sliceIntMax(s []interface{}, max int64) (int64, error) {
 
 
 func sliceFloatMax(s []interface{}, max float64) (float64, error) {
 func sliceFloatMax(s []interface{}, max float64) (float64, error) {
 	for _, v := range s {
 	for _, v := range s {
+		if v == nil {
+			continue
+		}
 		if vf, ok := v.(float64); ok {
 		if vf, ok := v.(float64); ok {
 			if max < vf {
 			if max < vf {
 				max = vf
 				max = vf
@@ -447,6 +471,9 @@ func sliceFloatMax(s []interface{}, max float64) (float64, error) {
 
 
 func sliceStringMax(s []interface{}, max string) (string, error) {
 func sliceStringMax(s []interface{}, max string) (string, error) {
 	for _, v := range s {
 	for _, v := range s {
+		if v == nil {
+			continue
+		}
 		if vs, ok := v.(string); ok {
 		if vs, ok := v.(string); ok {
 			if max < vs {
 			if max < vs {
 				max = vs
 				max = vs
@@ -460,6 +487,9 @@ func sliceStringMax(s []interface{}, max string) (string, error) {
 
 
 func sliceIntMin(s []interface{}, min int64) (int64, error) {
 func sliceIntMin(s []interface{}, min int64) (int64, error) {
 	for _, v := range s {
 	for _, v := range s {
+		if v == nil {
+			continue
+		}
 		vi, err := cast.ToInt64(v, cast.CONVERT_SAMEKIND)
 		vi, err := cast.ToInt64(v, cast.CONVERT_SAMEKIND)
 		if err == nil {
 		if err == nil {
 			if vi < min {
 			if vi < min {
@@ -474,6 +504,9 @@ func sliceIntMin(s []interface{}, min int64) (int64, error) {
 
 
 func sliceFloatMin(s []interface{}, min float64) (float64, error) {
 func sliceFloatMin(s []interface{}, min float64) (float64, error) {
 	for _, v := range s {
 	for _, v := range s {
+		if v == nil {
+			continue
+		}
 		if vf, ok := v.(float64); ok {
 		if vf, ok := v.(float64); ok {
 			if min > vf {
 			if min > vf {
 				min = vf
 				min = vf
@@ -487,6 +520,9 @@ func sliceFloatMin(s []interface{}, min float64) (float64, error) {
 
 
 func sliceStringMin(s []interface{}, min string) (string, error) {
 func sliceStringMin(s []interface{}, min string) (string, error) {
 	for _, v := range s {
 	for _, v := range s {
+		if v == nil {
+			continue
+		}
 		if vs, ok := v.(string); ok {
 		if vs, ok := v.(string); ok {
 			if vs < min {
 			if vs < min {
 				min = vs
 				min = vs

+ 77 - 0
internal/binder/function/funcs_agg_test.go

@@ -19,6 +19,8 @@ import (
 	"reflect"
 	"reflect"
 	"testing"
 	"testing"
 
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/lf-edge/ekuiper/internal/conf"
 	"github.com/lf-edge/ekuiper/internal/conf"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
@@ -261,3 +263,78 @@ func TestPercentileExec(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestAggFuncNil(t *testing.T) {
+	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)
+	oldBuiltins := builtins
+	defer func() {
+		builtins = oldBuiltins
+	}()
+	builtins = map[string]builtinFunc{}
+	registerAggFunc()
+	for name, function := range builtins {
+		switch name {
+		case "avg":
+			r, b := function.exec(fctx, []interface{}{[]interface{}{nil}})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, r, nil, fmt.Sprintf("%v failed", name))
+			r, b = function.exec(fctx, []interface{}{[]interface{}{1, nil}})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, r, int64(1), fmt.Sprintf("%v failed", name))
+			r, b = function.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+		case "count":
+			r, b := function.exec(fctx, []interface{}{[]interface{}{nil}})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, r, 0, fmt.Sprintf("%v failed", name))
+			r, b = function.exec(fctx, []interface{}{[]interface{}{1, nil}})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, r, 1, fmt.Sprintf("%v failed", name))
+			r, b = function.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+		case "max":
+			r, b := function.exec(fctx, []interface{}{[]interface{}{nil}})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+			r, b = function.exec(fctx, []interface{}{[]interface{}{1, 2, nil}})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, r, int64(2), fmt.Sprintf("%v failed", name))
+			r, b = function.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+		case "min":
+			r, b := function.exec(fctx, []interface{}{[]interface{}{nil}})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+			r, b = function.exec(fctx, []interface{}{[]interface{}{1, 2, nil}})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, r, int64(1), fmt.Sprintf("%v failed", name))
+			r, b = function.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+		case "sum":
+			r, b := function.exec(fctx, []interface{}{[]interface{}{nil}})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+			r, b = function.exec(fctx, []interface{}{[]interface{}{1, 2, nil}})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, r, int64(3), fmt.Sprintf("%v failed", name))
+			r, b = function.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+		case "collect":
+			r, b := function.exec(fctx, []interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+		default:
+			r, b := function.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+		}
+	}
+}

+ 43 - 7
internal/binder/function/funcs_array.go

@@ -44,7 +44,17 @@ func registerArrayFunc() {
 	builtins["array_create"] = builtinFunc{
 	builtins["array_create"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			return args, true
+			var index int
+			for _, arg := range args {
+				if arg != nil {
+					args[index] = arg
+					index++
+				}
+			}
+			if index == 0 {
+				return nil, true
+			}
+			return args[:index], true
 		},
 		},
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return nil
 			return nil
@@ -53,6 +63,9 @@ func registerArrayFunc() {
 	builtins["array_position"] = builtinFunc{
 	builtins["array_position"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			if args[0] == nil {
+				return -1, true
+			}
 			array, ok := args[0].([]interface{})
 			array, ok := args[0].([]interface{})
 			if !ok {
 			if !ok {
 				return errorArrayFirstArgumentNotArrayError, false
 				return errorArrayFirstArgumentNotArrayError, false
@@ -99,6 +112,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(2, len(args))
 			return ValidateLen(2, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["array_contains"] = builtinFunc{
 	builtins["array_contains"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -117,6 +131,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(2, len(args))
 			return ValidateLen(2, len(args))
 		},
 		},
+		check: returnFalseIfHasAnyNil,
 	}
 	}
 
 
 	builtins["array_remove"] = builtinFunc{
 	builtins["array_remove"] = builtinFunc{
@@ -140,6 +155,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(2, len(args))
 			return ValidateLen(2, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 
 
 	builtins["array_last_position"] = builtinFunc{
 	builtins["array_last_position"] = builtinFunc{
@@ -163,6 +179,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(2, len(args))
 			return ValidateLen(2, len(args))
 		},
 		},
+		check: returnFalseIfHasAnyNil,
 	}
 	}
 
 
 	builtins["array_contains_any"] = builtinFunc{
 	builtins["array_contains_any"] = builtinFunc{
@@ -190,6 +207,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(2, len(args))
 			return ValidateLen(2, len(args))
 		},
 		},
+		check: returnFalseIfHasAnyNil,
 	}
 	}
 
 
 	builtins["array_intersect"] = builtinFunc{
 	builtins["array_intersect"] = builtinFunc{
@@ -228,18 +246,25 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(2, len(args))
 			return ValidateLen(2, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 
 
 	builtins["array_union"] = builtinFunc{
 	builtins["array_union"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			array1, ok1 := args[0].([]interface{})
-			if !ok1 {
-				return errorArrayFirstArgumentNotArrayError, false
+			var array1, array2 []interface{}
+			var ok bool
+			if args[0] != nil {
+				array1, ok = args[0].([]interface{})
+				if !ok {
+					return errorArrayFirstArgumentNotArrayError, false
+				}
 			}
 			}
-			array2, ok2 := args[1].([]interface{})
-			if !ok2 {
-				return errorArraySecondArgumentNotArrayError, false
+			if args[1] != nil {
+				array2, ok = args[1].([]interface{})
+				if !ok {
+					return errorArraySecondArgumentNotArrayError, false
+				}
 			}
 			}
 			union := make([]interface{}, 0, len(array1)+len(array2))
 			union := make([]interface{}, 0, len(array1)+len(array2))
 			set := make(map[interface{}]bool)
 			set := make(map[interface{}]bool)
@@ -292,6 +317,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(1, len(args))
 			return ValidateLen(1, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["array_min"] = builtinFunc{
 	builtins["array_min"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -323,6 +349,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(1, len(args))
 			return ValidateLen(1, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["array_except"] = builtinFunc{
 	builtins["array_except"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -354,6 +381,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(2, len(args))
 			return ValidateLen(2, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["repeat"] = builtinFunc{
 	builtins["repeat"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -377,6 +405,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(2, len(args))
 			return ValidateLen(2, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["sequence"] = builtinFunc{
 	builtins["sequence"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -426,6 +455,7 @@ func registerArrayFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["array_cardinality"] = builtinFunc{
 	builtins["array_cardinality"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -439,6 +469,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(1, len(args))
 			return ValidateLen(1, len(args))
 		},
 		},
+		check: return0IfHasAnyNil,
 	}
 	}
 	builtins["array_flatten"] = builtinFunc{
 	builtins["array_flatten"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -463,6 +494,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(1, len(args))
 			return ValidateLen(1, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["array_distinct"] = builtinFunc{
 	builtins["array_distinct"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -487,6 +519,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(1, len(args))
 			return ValidateLen(1, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["array_map"] = builtinFunc{
 	builtins["array_map"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -530,6 +563,7 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(2, len(args))
 			return ValidateLen(2, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["array_join"] = builtinFunc{
 	builtins["array_join"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -582,6 +616,7 @@ func registerArrayFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["array_shuffle"] = builtinFunc{
 	builtins["array_shuffle"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -600,5 +635,6 @@ func registerArrayFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(1, len(args))
 			return ValidateLen(1, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 }
 }

+ 46 - 0
internal/binder/function/funcs_array_test.go

@@ -19,6 +19,8 @@ import (
 	"reflect"
 	"reflect"
 	"testing"
 	"testing"
 
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/lf-edge/ekuiper/internal/conf"
 	"github.com/lf-edge/ekuiper/internal/conf"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
@@ -750,3 +752,47 @@ func TestArrayShuffle(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestArrayFuncNil(t *testing.T) {
+	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)
+	oldBuiltins := builtins
+	defer func() {
+		builtins = oldBuiltins
+	}()
+	builtins = map[string]builtinFunc{}
+	registerArrayFunc()
+	for mathFuncName, mathFunc := range builtins {
+		switch mathFuncName {
+		case "array_create":
+			r, b := mathFunc.exec(fctx, []interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", mathFuncName))
+			require.Equal(t, r, nil, fmt.Sprintf("%v failed", mathFuncName))
+			r, b = mathFunc.exec(fctx, []interface{}{nil, 1})
+			require.True(t, b, fmt.Sprintf("%v failed", mathFuncName))
+			require.Equal(t, r, []interface{}{1}, fmt.Sprintf("%v failed", mathFuncName))
+		case "array_position":
+			r, b := mathFunc.exec(fctx, []interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", mathFuncName))
+			require.Equal(t, r, -1, fmt.Sprintf("%v failed", mathFuncName))
+		case "array_contains", "array_last_position", "array_contains_any":
+			r, b := mathFunc.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", mathFuncName))
+			require.False(t, r.(bool), fmt.Sprintf("%v failed", mathFuncName))
+		case "array_union":
+			r, b := mathFunc.exec(fctx, []interface{}{[]interface{}{1}, nil})
+			require.True(t, b, fmt.Sprintf("%v failed", mathFuncName))
+			require.Equal(t, r, []interface{}{1}, fmt.Sprintf("%v failed", mathFuncName))
+		case "array_cardinality":
+			r, b := mathFunc.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", mathFuncName))
+			require.Equal(t, r, 0, fmt.Sprintf("%v failed", mathFuncName))
+		default:
+			r, b := mathFunc.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", mathFuncName))
+			require.Nil(t, r, fmt.Sprintf("%v failed", mathFuncName))
+		}
+	}
+}

+ 48 - 24
internal/binder/function/funcs_math.go

@@ -45,7 +45,8 @@ func registerMathFunc() {
 				return fmt.Errorf("only float64 & int type are supported"), false
 				return fmt.Errorf("only float64 & int type are supported"), false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["acos"] = builtinFunc{
 	builtins["acos"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -61,7 +62,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["asin"] = builtinFunc{
 	builtins["asin"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -77,7 +79,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["atan"] = builtinFunc{
 	builtins["atan"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -93,7 +96,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["atan2"] = builtinFunc{
 	builtins["atan2"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -113,7 +117,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateTwoNumberArg,
+		val:   ValidateTwoNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["bitand"] = builtinFunc{
 	builtins["bitand"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -128,7 +133,8 @@ func registerMathFunc() {
 			}
 			}
 			return v1 & v2, true
 			return v1 & v2, true
 		},
 		},
-		val: ValidateTwoIntArg,
+		val:   ValidateTwoIntArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["bitor"] = builtinFunc{
 	builtins["bitor"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -143,7 +149,8 @@ func registerMathFunc() {
 			}
 			}
 			return v1 | v2, true
 			return v1 | v2, true
 		},
 		},
-		val: ValidateTwoIntArg,
+		val:   ValidateTwoIntArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["bitxor"] = builtinFunc{
 	builtins["bitxor"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -158,7 +165,8 @@ func registerMathFunc() {
 			}
 			}
 			return v1 ^ v2, true
 			return v1 ^ v2, true
 		},
 		},
-		val: ValidateTwoIntArg,
+		val:   ValidateTwoIntArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["bitnot"] = builtinFunc{
 	builtins["bitnot"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -178,6 +186,7 @@ func registerMathFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["ceil"] = builtinFunc{
 	builtins["ceil"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -188,7 +197,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["cos"] = builtinFunc{
 	builtins["cos"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -204,7 +214,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["cosh"] = builtinFunc{
 	builtins["cosh"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -220,7 +231,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["exp"] = builtinFunc{
 	builtins["exp"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -236,7 +248,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["ln"] = builtinFunc{
 	builtins["ln"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -252,7 +265,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["log"] = builtinFunc{
 	builtins["log"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -268,7 +282,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["mod"] = builtinFunc{
 	builtins["mod"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -283,7 +298,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateTwoNumberArg,
+		val:   ValidateTwoNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["power"] = builtinFunc{
 	builtins["power"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -298,14 +314,15 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateTwoNumberArg,
+		val:   ValidateTwoNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["rand"] = builtinFunc{
 	builtins["rand"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			return rand.Float64(), true
 			return rand.Float64(), true
 		},
 		},
-		val: ValidateOneArg,
+		val: ValidateNoArg,
 	}
 	}
 	builtins["round"] = builtinFunc{
 	builtins["round"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -316,7 +333,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["sign"] = builtinFunc{
 	builtins["sign"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -333,7 +351,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["sin"] = builtinFunc{
 	builtins["sin"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -349,7 +368,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["sinh"] = builtinFunc{
 	builtins["sinh"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -365,7 +385,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["sqrt"] = builtinFunc{
 	builtins["sqrt"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -381,7 +402,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["tan"] = builtinFunc{
 	builtins["tan"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -397,7 +419,8 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["tanh"] = builtinFunc{
 	builtins["tanh"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -413,6 +436,7 @@ func registerMathFunc() {
 				return e, false
 				return e, false
 			}
 			}
 		},
 		},
-		val: ValidateOneNumberArg,
+		val:   ValidateOneNumberArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 }
 }

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

@@ -20,6 +20,8 @@ import (
 	"reflect"
 	"reflect"
 	"testing"
 	"testing"
 
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/lf-edge/ekuiper/internal/conf"
 	"github.com/lf-edge/ekuiper/internal/conf"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
@@ -382,3 +384,22 @@ func TestFuncMath(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestFuncMathNil(t *testing.T) {
+	oldBuiltins := builtins
+	defer func() {
+		builtins = oldBuiltins
+	}()
+	builtins = map[string]builtinFunc{}
+	registerMathFunc()
+	for mathFuncName, mathFunc := range builtins {
+		switch mathFuncName {
+		case "rand":
+			continue
+		default:
+			r, b := mathFunc.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", mathFuncName))
+			require.Nil(t, r, fmt.Sprintf("%v failed", mathFuncName))
+		}
+	}
+}

+ 23 - 25
internal/binder/function/funcs_misc.go

@@ -60,20 +60,19 @@ func registerMiscFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["to_json"] = builtinFunc{
 	builtins["to_json"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return "null", true
-			}
 			rr, err := json.Marshal(args[0])
 			rr, err := json.Marshal(args[0])
 			if err != nil {
 			if err != nil {
 				return fmt.Errorf("fail to convert %v to json", args[0]), false
 				return fmt.Errorf("fail to convert %v to json", args[0]), false
 			}
 			}
 			return string(rr), true
 			return string(rr), true
 		},
 		},
-		val: ValidateOneArg,
+		val:   ValidateOneArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["parse_json"] = builtinFunc{
 	builtins["parse_json"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -121,6 +120,7 @@ func registerMiscFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["encode"] = builtinFunc{
 	builtins["encode"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -158,6 +158,7 @@ func registerMiscFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["decode"] = builtinFunc{
 	builtins["decode"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -199,6 +200,7 @@ func registerMiscFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["trunc"] = builtinFunc{
 	builtins["trunc"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -231,13 +233,11 @@ func registerMiscFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["md5"] = builtinFunc{
 	builtins["md5"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			h := md5.New()
 			h := md5.New()
 			_, err := io.WriteString(h, arg0)
 			_, err := io.WriteString(h, arg0)
@@ -246,14 +246,12 @@ func registerMiscFunc() {
 			}
 			}
 			return fmt.Sprintf("%x", h.Sum(nil)), true
 			return fmt.Sprintf("%x", h.Sum(nil)), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["sha1"] = builtinFunc{
 	builtins["sha1"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			h := sha1.New()
 			h := sha1.New()
 			_, err := io.WriteString(h, arg0)
 			_, err := io.WriteString(h, arg0)
@@ -262,14 +260,12 @@ func registerMiscFunc() {
 			}
 			}
 			return fmt.Sprintf("%x", h.Sum(nil)), true
 			return fmt.Sprintf("%x", h.Sum(nil)), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["sha256"] = builtinFunc{
 	builtins["sha256"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			h := sha256.New()
 			h := sha256.New()
 			_, err := io.WriteString(h, arg0)
 			_, err := io.WriteString(h, arg0)
@@ -278,14 +274,12 @@ func registerMiscFunc() {
 			}
 			}
 			return fmt.Sprintf("%x", h.Sum(nil)), true
 			return fmt.Sprintf("%x", h.Sum(nil)), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["sha384"] = builtinFunc{
 	builtins["sha384"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			h := sha512.New384()
 			h := sha512.New384()
 			_, err := io.WriteString(h, arg0)
 			_, err := io.WriteString(h, arg0)
@@ -294,14 +288,12 @@ func registerMiscFunc() {
 			}
 			}
 			return fmt.Sprintf("%x", h.Sum(nil)), true
 			return fmt.Sprintf("%x", h.Sum(nil)), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["sha512"] = builtinFunc{
 	builtins["sha512"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			h := sha512.New()
 			h := sha512.New()
 			_, err := io.WriteString(h, arg0)
 			_, err := io.WriteString(h, arg0)
@@ -310,7 +302,8 @@ func registerMiscFunc() {
 			}
 			}
 			return fmt.Sprintf("%x", h.Sum(nil)), true
 			return fmt.Sprintf("%x", h.Sum(nil)), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtinStatfulFuncs["compress"] = func() api.Function {
 	builtinStatfulFuncs["compress"] = func() api.Function {
 		conf.Log.Infof("initializing compress function")
 		conf.Log.Infof("initializing compress function")
@@ -395,6 +388,7 @@ func registerMiscFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["rule_id"] = builtinFunc{
 	builtins["rule_id"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -438,7 +432,8 @@ func registerMiscFunc() {
 			}
 			}
 			return 0, true
 			return 0, true
 		},
 		},
-		val: ValidateOneArg,
+		val:   ValidateOneArg,
+		check: return0IfHasAnyNil,
 	}
 	}
 	builtins["json_path_query"] = builtinFunc{
 	builtins["json_path_query"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -525,6 +520,7 @@ func registerMiscFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["delay"] = builtinFunc{
 	builtins["delay"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -545,6 +541,7 @@ func registerMiscFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["get_keyed_state"] = builtinFunc{
 	builtins["get_keyed_state"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -579,6 +576,7 @@ func registerMiscFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 }
 }
 
 

+ 45 - 0
internal/binder/function/funcs_misc_test.go

@@ -19,6 +19,8 @@ import (
 	"reflect"
 	"reflect"
 	"testing"
 	"testing"
 
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/lf-edge/ekuiper/internal/conf"
 	"github.com/lf-edge/ekuiper/internal/conf"
 	"github.com/lf-edge/ekuiper/internal/keyedstate"
 	"github.com/lf-edge/ekuiper/internal/keyedstate"
 	"github.com/lf-edge/ekuiper/internal/testx"
 	"github.com/lf-edge/ekuiper/internal/testx"
@@ -378,3 +380,46 @@ func TestKeyedStateExec(t *testing.T) {
 	}
 	}
 	_ = keyedstate.ClearKeyedState()
 	_ = keyedstate.ClearKeyedState()
 }
 }
+
+func TestMiscFuncNil(t *testing.T) {
+	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)
+	oldBuiltins := builtins
+	defer func() {
+		builtins = oldBuiltins
+	}()
+	builtins = map[string]builtinFunc{}
+	registerMiscFunc()
+	for name, function := range builtins {
+		switch name {
+		case "compress", "decompress", "newuuid", "tstamp", "rule_id", "window_start", "window_end",
+			"json_path_query", "json_path_query_first", "coalesce", "meta", "json_path_exists":
+			continue
+		case "isnull":
+			v, b := function.exec(fctx, []interface{}{nil})
+			require.True(t, b)
+			require.Equal(t, v, true)
+		case "cardinality":
+			v, b := function.check([]interface{}{nil})
+			require.True(t, b)
+			require.Equal(t, v, 0)
+		case "to_json":
+			v, b := function.exec(fctx, []interface{}{nil})
+			require.True(t, b)
+			require.Equal(t, v, "null")
+		case "parse_json":
+			v, b := function.exec(fctx, []interface{}{nil})
+			require.True(t, b)
+			require.Equal(t, v, nil)
+			v, b = function.exec(fctx, []interface{}{"null"})
+			require.True(t, b)
+			require.Equal(t, v, nil)
+		default:
+			v, b := function.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, v, fmt.Sprintf("%v failed", name))
+		}
+	}
+}

+ 9 - 4
internal/binder/function/funcs_obj.go

@@ -35,7 +35,8 @@ func registerObjectFunc() {
 			}
 			}
 			return fmt.Errorf("the argument should be map[string]interface{}"), false
 			return fmt.Errorf("the argument should be map[string]interface{}"), false
 		},
 		},
-		val: ValidateOneArg,
+		val:   ValidateOneArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["values"] = builtinFunc{
 	builtins["values"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -50,7 +51,8 @@ func registerObjectFunc() {
 			}
 			}
 			return fmt.Errorf("the argument should be map[string]interface{}"), false
 			return fmt.Errorf("the argument should be map[string]interface{}"), false
 		},
 		},
-		val: ValidateOneArg,
+		val:   ValidateOneArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["object"] = builtinFunc{
 	builtins["object"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -82,6 +84,7 @@ func registerObjectFunc() {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 		val: func(ctx api.FunctionContext, args []ast.Expr) error {
 			return ValidateLen(2, len(args))
 			return ValidateLen(2, len(args))
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["zip"] = builtinFunc{
 	builtins["zip"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -110,7 +113,8 @@ func registerObjectFunc() {
 			}
 			}
 			return m, true
 			return m, true
 		},
 		},
-		val: ValidateOneArg,
+		val:   ValidateOneArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["items"] = builtinFunc{
 	builtins["items"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -128,6 +132,7 @@ func registerObjectFunc() {
 			}
 			}
 			return list, true
 			return list, true
 		},
 		},
-		val: ValidateOneArg,
+		val:   ValidateOneArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 }
 }

+ 16 - 0
internal/binder/function/funcs_obj_test.go

@@ -20,6 +20,8 @@ import (
 	"sort"
 	"sort"
 	"testing"
 	"testing"
 
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/lf-edge/ekuiper/internal/conf"
 	"github.com/lf-edge/ekuiper/internal/conf"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
@@ -236,3 +238,17 @@ func TestObjectFunctions(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestObjectFunctionsNil(t *testing.T) {
+	oldBuiltins := builtins
+	defer func() {
+		builtins = oldBuiltins
+	}()
+	builtins = map[string]builtinFunc{}
+	registerObjectFunc()
+	for name, function := range builtins {
+		r, b := function.check([]interface{}{nil})
+		require.True(t, b, fmt.Sprintf("%v failed", name))
+		require.Nil(t, r, fmt.Sprintf("%v failed", name))
+	}
+}

+ 2 - 1
internal/binder/function/funcs_srf.go

@@ -30,6 +30,7 @@ func registerSetReturningFunc() {
 			}
 			}
 			return argArray, true
 			return argArray, true
 		},
 		},
-		val: ValidateOneArg,
+		val:   ValidateOneArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 }
 }

+ 17 - 0
internal/binder/function/funcs_srf_test.go

@@ -15,9 +15,12 @@
 package function
 package function
 
 
 import (
 import (
+	"fmt"
 	"reflect"
 	"reflect"
 	"testing"
 	"testing"
 
 
+	"github.com/stretchr/testify/require"
+
 	"github.com/lf-edge/ekuiper/internal/conf"
 	"github.com/lf-edge/ekuiper/internal/conf"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	kctx "github.com/lf-edge/ekuiper/internal/topo/context"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
 	"github.com/lf-edge/ekuiper/internal/topo/state"
@@ -75,3 +78,17 @@ func TestUnnestFunctions(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestUnnestFunctionsNil(t *testing.T) {
+	oldBuiltins := builtins
+	defer func() {
+		builtins = oldBuiltins
+	}()
+	builtins = map[string]builtinFunc{}
+	registerSetReturningFunc()
+	for name, function := range builtins {
+		r, b := function.check([]interface{}{nil})
+		require.True(t, b, fmt.Sprintf("%v failed", name))
+		require.Nil(t, r, fmt.Sprintf("%v failed", name))
+	}
+}

+ 32 - 58
internal/binder/function/funcs_str.go

@@ -52,13 +52,11 @@ func registerStrFunc() {
 	builtins["endswith"] = builtinFunc{
 	builtins["endswith"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil || args[1] == nil {
-				return false, true
-			}
 			arg0, arg1 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1])
 			arg0, arg1 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1])
 			return strings.HasSuffix(arg0, arg1), true
 			return strings.HasSuffix(arg0, arg1), true
 		},
 		},
-		val: ValidateTwoStrArg,
+		val:   ValidateTwoStrArg,
+		check: returnFalseIfHasAnyNil,
 	}
 	}
 	builtins["indexof"] = builtinFunc{
 	builtins["indexof"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -80,29 +78,27 @@ func registerStrFunc() {
 				return len(v), true
 				return len(v), true
 			case map[string]interface{}:
 			case map[string]interface{}:
 				return len(v), true
 				return len(v), true
+			case nil:
+				return 0, true
 			default:
 			default:
 			}
 			}
 			return utf8.RuneCountInString(arg0), true
 			return utf8.RuneCountInString(arg0), true
 		},
 		},
-		val: ValidateOneArg,
+		val:   ValidateOneArg,
+		check: return0IfHasAnyNil,
 	}
 	}
 	builtins["lower"] = builtinFunc{
 	builtins["lower"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			return strings.ToLower(arg0), true
 			return strings.ToLower(arg0), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["lpad"] = builtinFunc{
 	builtins["lpad"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			arg1, err := cast.ToInt(args[1], cast.STRICT)
 			arg1, err := cast.ToInt(args[1], cast.STRICT)
 			if err != nil {
 			if err != nil {
@@ -110,18 +106,17 @@ func registerStrFunc() {
 			}
 			}
 			return strings.Repeat(" ", arg1) + arg0, true
 			return strings.Repeat(" ", arg1) + arg0, true
 		},
 		},
-		val: ValidateOneStrOneInt,
+		val:   ValidateOneStrOneInt,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["ltrim"] = builtinFunc{
 	builtins["ltrim"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			return strings.TrimLeftFunc(arg0, unicode.IsSpace), true
 			return strings.TrimLeftFunc(arg0, unicode.IsSpace), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["numbytes"] = builtinFunc{
 	builtins["numbytes"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
@@ -129,14 +124,12 @@ func registerStrFunc() {
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			return len(arg0), true
 			return len(arg0), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: return0IfHasAnyNil,
 	}
 	}
 	builtins["format_time"] = builtinFunc{
 	builtins["format_time"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0, err := cast.InterfaceToTime(args[0], "")
 			arg0, err := cast.InterfaceToTime(args[0], "")
 			if err != nil {
 			if err != nil {
 				return err, false
 				return err, false
@@ -161,13 +154,11 @@ func registerStrFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["regexp_matches"] = builtinFunc{
 	builtins["regexp_matches"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil || args[1] == nil {
-				return false, true
-			}
 			arg0, arg1 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1])
 			arg0, arg1 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1])
 			if matched, err := regexp.MatchString(arg1, arg0); err != nil {
 			if matched, err := regexp.MatchString(arg1, arg0); err != nil {
 				return err, false
 				return err, false
@@ -175,14 +166,12 @@ func registerStrFunc() {
 				return matched, true
 				return matched, true
 			}
 			}
 		},
 		},
-		val: ValidateTwoStrArg,
+		val:   ValidateTwoStrArg,
+		check: returnFalseIfHasAnyNil,
 	}
 	}
 	builtins["regexp_replace"] = builtinFunc{
 	builtins["regexp_replace"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil || args[1] == nil || args[2] == nil {
-				return nil, true
-			}
 			arg0, arg1, arg2 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1]), cast.ToStringAlways(args[2])
 			arg0, arg1, arg2 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1]), cast.ToStringAlways(args[2])
 			if re, err := regexp.Compile(arg1); err != nil {
 			if re, err := regexp.Compile(arg1); err != nil {
 				return err, false
 				return err, false
@@ -201,13 +190,11 @@ func registerStrFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["regexp_substr"] = builtinFunc{
 	builtins["regexp_substr"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil || args[1] == nil {
-				return nil, true
-			}
 			arg0, arg1 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1])
 			arg0, arg1 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1])
 			if re, err := regexp.Compile(arg1); err != nil {
 			if re, err := regexp.Compile(arg1); err != nil {
 				return err, false
 				return err, false
@@ -215,14 +202,12 @@ func registerStrFunc() {
 				return re.FindString(arg0), true
 				return re.FindString(arg0), true
 			}
 			}
 		},
 		},
-		val: ValidateTwoStrArg,
+		val:   ValidateTwoStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["rpad"] = builtinFunc{
 	builtins["rpad"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			arg1, err := cast.ToInt(args[1], cast.STRICT)
 			arg1, err := cast.ToInt(args[1], cast.STRICT)
 			if err != nil {
 			if err != nil {
@@ -230,25 +215,21 @@ func registerStrFunc() {
 			}
 			}
 			return arg0 + strings.Repeat(" ", arg1), true
 			return arg0 + strings.Repeat(" ", arg1), true
 		},
 		},
-		val: ValidateOneStrOneInt,
+		val:   ValidateOneStrOneInt,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["rtrim"] = builtinFunc{
 	builtins["rtrim"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			return strings.TrimRightFunc(arg0, unicode.IsSpace), true
 			return strings.TrimRightFunc(arg0, unicode.IsSpace), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["substring"] = builtinFunc{
 	builtins["substring"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			arg1, err := cast.ToInt(args[1], cast.STRICT)
 			arg1, err := cast.ToInt(args[1], cast.STRICT)
 			if err != nil {
 			if err != nil {
@@ -312,24 +293,20 @@ func registerStrFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["startswith"] = builtinFunc{
 	builtins["startswith"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return false, true
-			}
 			arg0, arg1 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1])
 			arg0, arg1 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1])
 			return strings.HasPrefix(arg0, arg1), true
 			return strings.HasPrefix(arg0, arg1), true
 		},
 		},
-		val: ValidateTwoStrArg,
+		val:   ValidateTwoStrArg,
+		check: returnFalseIfHasAnyNil,
 	}
 	}
 	builtins["split_value"] = builtinFunc{
 	builtins["split_value"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil || args[1] == nil {
-				return nil, true
-			}
 			arg0, arg1 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1])
 			arg0, arg1 := cast.ToStringAlways(args[0]), cast.ToStringAlways(args[1])
 			ss := strings.Split(arg0, arg1)
 			ss := strings.Split(arg0, arg1)
 			v, _ := cast.ToInt(args[2], cast.STRICT)
 			v, _ := cast.ToInt(args[2], cast.STRICT)
@@ -360,27 +337,24 @@ func registerStrFunc() {
 			}
 			}
 			return nil
 			return nil
 		},
 		},
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["trim"] = builtinFunc{
 	builtins["trim"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			return strings.TrimSpace(arg0), true
 			return strings.TrimSpace(arg0), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 	builtins["upper"] = builtinFunc{
 	builtins["upper"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
-			if args[0] == nil {
-				return nil, true
-			}
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
 			return strings.ToUpper(arg0), true
 			return strings.ToUpper(arg0), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val:   ValidateOneStrArg,
+		check: returnNilIfHasAnyNil,
 	}
 	}
 }
 }

+ 64 - 0
internal/binder/function/funcs_str_test.go

@@ -0,0 +1,64 @@
+// 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"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"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"
+)
+
+func TestStrFuncNil(t *testing.T) {
+	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)
+	oldBuiltins := builtins
+	defer func() {
+		builtins = oldBuiltins
+	}()
+	builtins = map[string]builtinFunc{}
+	registerStrFunc()
+	for name, function := range builtins {
+		switch name {
+		case "concat":
+			r, b := function.exec(fctx, []interface{}{"1", nil, "2"})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, "12", r)
+		case "endswith", "regexp_matches", "startswith":
+			r, b := function.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, false, r)
+		case "indexof":
+			r, b := function.exec(fctx, []interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, -1, r)
+		case "length", "numbytes":
+			r, b := function.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Equal(t, 0, r)
+		default:
+			r, b := function.check([]interface{}{nil})
+			require.True(t, b, fmt.Sprintf("%v failed", name))
+			require.Nil(t, r, fmt.Sprintf("%v failed", name))
+		}
+	}
+}

+ 31 - 2
internal/binder/function/function.go

@@ -23,14 +23,16 @@ import (
 )
 )
 
 
 type (
 type (
-	funcExe func(ctx api.FunctionContext, args []interface{}) (interface{}, bool)
-	funcVal func(ctx api.FunctionContext, args []ast.Expr) error
+	funcExe      func(ctx api.FunctionContext, args []interface{}) (interface{}, bool)
+	funcVal      func(ctx api.FunctionContext, args []ast.Expr) error
+	funcCheckNil func(args []interface{}) (interface{}, bool)
 )
 )
 
 
 type builtinFunc struct {
 type builtinFunc struct {
 	fType ast.FuncType
 	fType ast.FuncType
 	exec  funcExe
 	exec  funcExe
 	val   funcVal
 	val   funcVal
+	check funcCheckNil
 }
 }
 
 
 var (
 var (
@@ -114,3 +116,30 @@ var m = &Manager{}
 func GetManager() *Manager {
 func GetManager() *Manager {
 	return m
 	return m
 }
 }
+
+func returnNilIfHasAnyNil(args []interface{}) (returned interface{}, skipExec bool) {
+	for _, arg := range args {
+		if arg == nil {
+			return nil, true
+		}
+	}
+	return nil, false
+}
+
+func returnFalseIfHasAnyNil(args []interface{}) (returned interface{}, skipExec bool) {
+	for _, arg := range args {
+		if arg == nil {
+			return false, true
+		}
+	}
+	return nil, false
+}
+
+func return0IfHasAnyNil(args []interface{}) (returned interface{}, skipExec bool) {
+	for _, arg := range args {
+		if arg == nil {
+			return 0, true
+		}
+	}
+	return nil, false
+}

+ 6 - 0
internal/binder/function/static_executor.go

@@ -55,6 +55,12 @@ func (f *funcExecutor) ExecWithName(args []interface{}, ctx api.FunctionContext,
 	if !ok {
 	if !ok {
 		return fmt.Errorf("unknow name"), false
 		return fmt.Errorf("unknow name"), false
 	}
 	}
+	if fs.check != nil {
+		r, skipExec := fs.check(args)
+		if skipExec {
+			return r, true
+		}
+	}
 	return fs.exec(ctx, args)
 	return fs.exec(ctx, args)
 }
 }
 
 

+ 0 - 11
internal/topo/operator/project_test.go

@@ -2296,17 +2296,6 @@ func TestProjectPlanError(t *testing.T) {
 			},
 			},
 			result: errors.New("run Select error: call func round error: cannot convert string(common string) to float64"),
 			result: errors.New("run Select error: call func round error: cannot convert string(common string) to float64"),
 		},
 		},
-		// 4
-		{
-			sql: `SELECT round(a) as r FROM test`,
-			data: &xsql.Tuple{
-				Emitter: "test",
-				Message: xsql.Message{
-					"abc": "common string",
-				},
-			},
-			result: errors.New("run Select error: call func round error: cannot convert <nil>(<nil>) to float64"),
-		},
 		// 5
 		// 5
 		{
 		{
 			sql: "SELECT avg(a) as avg FROM test Inner Join test1 on test.id = test1.id GROUP BY TumblingWindow(ss, 10), test1.color",
 			sql: "SELECT avg(a) as avg FROM test Inner Join test1 on test.id = test1.id GROUP BY TumblingWindow(ss, 10), test1.color",