瀏覽代碼

feat: support for more medium-priority array funcs (#1904)

* add medium-priority array funcs and tests

Signed-off-by: wangxye <1031989637@qq.com>

* update medium-priority array funcs and tests

Signed-off-by: wangxye <1031989637@qq.com>

---------

Signed-off-by: wangxye <1031989637@qq.com>
Abyss 1 年之前
父節點
當前提交
9d97a8fb26
共有 2 個文件被更改,包括 643 次插入6 次删除
  1. 319 4
      internal/binder/function/funcs_array.go
  2. 324 2
      internal/binder/function/funcs_array_test.go

+ 319 - 4
internal/binder/function/funcs_array.go

@@ -16,6 +16,7 @@ package function
 
 import (
 	"fmt"
+	"math"
 
 	"github.com/lf-edge/ekuiper/pkg/api"
 	"github.com/lf-edge/ekuiper/pkg/ast"
@@ -23,8 +24,13 @@ import (
 )
 
 var (
-	errorArrayArgumentError = fmt.Errorf("first argument should be array of interface{}")
-	errorArrayIndex         = fmt.Errorf("index out of range")
+	errorArrayFirstArgumentNotArrayError   = fmt.Errorf("first argument should be array of interface{}")
+	errorArrayIndex                        = fmt.Errorf("index out of range")
+	errorArraySecondArgumentNotArrayError  = fmt.Errorf("second argument should be array of interface{}")
+	errorArrayFirstArgumentNotIntError     = fmt.Errorf("first argument should be int")
+	errorArraySecondArgumentNotIntError    = fmt.Errorf("second argument should be int")
+	errorArrayThirdArgumentNotIntError     = fmt.Errorf("third argument should be int")
+	errorArrayContainsNonNumOrBoolValError = fmt.Errorf("array contain elements that are not numeric or Boolean")
 )
 
 func registerArrayFunc() {
@@ -42,7 +48,7 @@ func registerArrayFunc() {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			array, ok := args[0].([]interface{})
 			if !ok {
-				return errorArrayArgumentError, false
+				return errorArrayFirstArgumentNotArrayError, false
 			}
 			for i, item := range array {
 				if item == args[1] {
@@ -92,7 +98,7 @@ func registerArrayFunc() {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			array, ok := args[0].([]interface{})
 			if !ok {
-				return errorArrayArgumentError, false
+				return errorArrayFirstArgumentNotArrayError, false
 			}
 			for _, item := range array {
 				if item == args[1] {
@@ -105,4 +111,313 @@ func registerArrayFunc() {
 			return ValidateLen(2, len(args))
 		},
 	}
+
+	builtins["array_remove"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			array, ok := args[0].([]interface{})
+			if !ok {
+				return errorArrayFirstArgumentNotArrayError, false
+			}
+
+			index := 0
+
+			for _, item := range array {
+				if item != args[1] {
+					array[index] = item
+					index++
+				}
+			}
+			return array[:index], true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
+
+	builtins["array_last_position"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			array, ok := args[0].([]interface{})
+			if !ok {
+				return errorArrayFirstArgumentNotArrayError, false
+			}
+
+			lastPos := -1
+			for i := len(array) - 1; i >= 0; i-- {
+				if array[i] == args[1] {
+					lastPos = i
+					break
+				}
+			}
+
+			return lastPos, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
+
+	builtins["array_contains_any"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			array1, ok1 := args[0].([]interface{})
+			if !ok1 {
+				return errorArrayFirstArgumentNotArrayError, false
+			}
+			array2, ok2 := args[1].([]interface{})
+			if !ok2 {
+				return errorArraySecondArgumentNotArrayError, false
+			}
+
+			for _, a := range array1 {
+				for _, b := range array2 {
+					if a == b {
+						return true, true
+					}
+				}
+			}
+
+			return false, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
+
+	builtins["array_intersect"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			array1, ok1 := args[0].([]interface{})
+			if !ok1 {
+				return errorArrayFirstArgumentNotArrayError, false
+			}
+			array2, ok2 := args[1].([]interface{})
+			if !ok2 {
+				return errorArraySecondArgumentNotArrayError, false
+			}
+
+			capacity := len(array1)
+			if len(array2) > capacity {
+				capacity = len(array2)
+			}
+
+			intersection := make([]interface{}, 0, capacity)
+			set := make(map[interface{}]bool)
+
+			for _, a := range array1 {
+				set[a] = true
+			}
+
+			for _, b := range array2 {
+				if set[b] {
+					intersection = append(intersection, b)
+					set[b] = false
+				}
+			}
+
+			return intersection, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
+
+	builtins["array_union"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			array1, ok1 := args[0].([]interface{})
+			if !ok1 {
+				return errorArrayFirstArgumentNotArrayError, false
+			}
+			array2, ok2 := args[1].([]interface{})
+			if !ok2 {
+				return errorArraySecondArgumentNotArrayError, false
+			}
+			union := make([]interface{}, 0, len(array1)+len(array2))
+			set := make(map[interface{}]bool)
+
+			for _, a := range array1 {
+				if !set[a] {
+					union = append(union, a)
+					set[a] = true
+				}
+			}
+			for _, b := range array2 {
+				if !set[b] {
+					set[b] = true
+					union = append(union, b)
+				}
+			}
+
+			return union, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
+	builtins["array_max"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			array, ok := args[0].([]interface{})
+			if !ok {
+				return errorArrayFirstArgumentNotArrayError, false
+			}
+			var res interface{}
+			var maxVal float64 = math.Inf(-1)
+
+			for _, val := range array {
+				if val == nil {
+					return nil, true
+				}
+				f, err := cast.ToFloat64(val, cast.CONVERT_ALL)
+				if err != nil {
+					return errorArrayContainsNonNumOrBoolValError, false
+				}
+
+				if f > maxVal {
+					maxVal = f
+					res = val
+				}
+			}
+			return res, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(1, len(args))
+		},
+	}
+	builtins["array_min"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			array, ok := args[0].([]interface{})
+			if !ok {
+				return errorArrayFirstArgumentNotArrayError, false
+			}
+			var res interface{}
+			var min float64 = math.Inf(1)
+
+			for _, val := range array {
+				if val == nil {
+					return nil, true
+				}
+
+				f, err := cast.ToFloat64(val, cast.CONVERT_ALL)
+				if err != nil {
+					return errorArrayContainsNonNumOrBoolValError, false
+				}
+
+				if f < min {
+					min = f
+					res = val
+				}
+			}
+			return res, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(1, len(args))
+		},
+	}
+	builtins["array_except"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			array1, ok1 := args[0].([]interface{})
+			if !ok1 {
+				return errorArrayFirstArgumentNotArrayError, false
+			}
+			array2, ok2 := args[1].([]interface{})
+			if !ok2 {
+				return errorArraySecondArgumentNotArrayError, false
+			}
+			except := make([]interface{}, 0, len(array1))
+			set := make(map[interface{}]bool)
+
+			for _, v := range array2 {
+				set[v] = true
+			}
+
+			for _, v := range array1 {
+				if !set[v] {
+					except = append(except, v)
+					set[v] = true
+				}
+			}
+
+			return except, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
+	builtins["repeat"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			elemt, ok := args[0].(interface{})
+			if !ok {
+				return errorArrayFirstArgumentNotArrayError, false
+			}
+			count, ok := args[1].(int)
+			if !ok {
+				return errorArraySecondArgumentNotIntError, false
+			}
+
+			arr := make([]interface{}, count)
+			for i := range arr {
+				arr[i] = elemt
+			}
+
+			return arr, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
+	builtins["sequence"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			var step, start, stop int
+			var ok bool
+			start, ok = args[0].(int)
+			if !ok {
+				return errorArrayFirstArgumentNotIntError, false
+			}
+			stop, ok = args[1].(int)
+			if !ok {
+				return errorArraySecondArgumentNotIntError, false
+			}
+			if len(args) == 3 {
+				step, ok = args[2].(int)
+				if !ok {
+					return errorArrayThirdArgumentNotIntError, false
+				}
+
+				if step == 0 {
+					return fmt.Errorf("invalid step: should not be zero"), false
+				}
+
+			} else {
+				if start < stop {
+					step = 1
+				} else {
+					step = -1
+				}
+			}
+
+			n := (stop-start)/step + 1
+
+			arr := make([]interface{}, n)
+			for i := range arr {
+				arr[i] = start + i*step
+			}
+
+			return arr, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			if err := ValidateLen(2, len(args)); err != nil {
+				if err := ValidateLen(3, len(args)); err != nil {
+					return fmt.Errorf("Expect two or three arguments but found %d.", len(args))
+				}
+			}
+			return nil
+		},
+	}
 }

+ 324 - 2
internal/binder/function/funcs_array_test.go

@@ -49,7 +49,7 @@ func TestArrayFunctions(t *testing.T) {
 			args: []interface{}{
 				1, 2,
 			},
-			result: errorArrayArgumentError,
+			result: errorArrayFirstArgumentNotArrayError,
 		},
 		{
 			name: "array_position",
@@ -114,7 +114,7 @@ func TestArrayFunctions(t *testing.T) {
 			args: []interface{}{
 				1, 2,
 			},
-			result: errorArrayArgumentError,
+			result: errorArrayFirstArgumentNotArrayError,
 		},
 		{
 			name: "array_contains",
@@ -130,6 +130,328 @@ func TestArrayFunctions(t *testing.T) {
 			},
 			result: false,
 		},
+		{
+			name: "array_remove",
+			args: []interface{}{
+				[]interface{}{3, 1, 2}, 1,
+			},
+			result: []interface{}{3, 2},
+		},
+		{
+			name: "array_remove",
+			args: []interface{}{
+				[]interface{}{'a', 'b', 'c'}, 'c',
+			},
+			result: []interface{}{'a', 'b'},
+		},
+		{
+			name: "array_remove",
+			args: []interface{}{
+				[]interface{}{1, 2, 3, 4, 3}, 3,
+			},
+			result: []interface{}{1, 2, 4},
+		},
+		{
+			name: "array_remove",
+			args: []interface{}{
+				[]interface{}{3, 3, 3}, 3,
+			},
+			result: []interface{}{},
+		},
+		{
+			name: "array_last_position",
+			args: []interface{}{
+				[]interface{}{5, nil, 5}, 5,
+			},
+			result: 2,
+		},
+		{
+			name: "array_last_position",
+			args: []interface{}{
+				[]interface{}{5, nil, 5}, "hello",
+			},
+			result: -1,
+		},
+		{
+			name: "array_last_position",
+			args: []interface{}{
+				[]interface{}{5, nil, 7}, 5,
+			},
+			result: 0,
+		},
+		{
+			name: "array_last_position",
+			args: []interface{}{
+				1, 2,
+			},
+			result: errorArrayFirstArgumentNotArrayError,
+		},
+		{
+			name: "array_last_position",
+			args: []interface{}{
+				[]interface{}{5, "hello", nil}, nil,
+			},
+			result: 2,
+		},
+		{
+			name: "array_contains_any",
+			args: []interface{}{
+				[]interface{}{1, 2, 3}, []interface{}{0, 2, 4},
+			},
+			result: true,
+		},
+		{
+			name: "array_contains_any",
+			args: []interface{}{
+				[]interface{}{1, 2, 3}, []interface{}{4, "hello", 6},
+			},
+			result: false,
+		},
+		{
+			name: "array_contains_any",
+			args: []interface{}{
+				[]interface{}{1, 2, 3}, []interface{}{},
+			},
+			result: false,
+		},
+		{
+			name: "array_contains_any",
+			args: []interface{}{
+				[]interface{}{1, 2, 3, 4}, 1,
+			},
+			result: errorArraySecondArgumentNotArrayError,
+		},
+		{
+			name: "array_intersect",
+			args: []interface{}{
+				[]interface{}{1, 1, 2, 3}, []interface{}{1, 3, 4},
+			},
+			result: []interface{}{1, 3},
+		},
+		{
+			name: "array_intersect",
+			args: []interface{}{
+				[]interface{}{"hello", "ekuiper", 2, 3}, []interface{}{"ekuiper", 3, 4},
+			},
+			result: []interface{}{"ekuiper", 3},
+		},
+		{
+			name: "array_intersect",
+			args: []interface{}{
+				[]interface{}{"hello", "ekuiper", 2, 3}, "ekuiper",
+			},
+			result: errorArraySecondArgumentNotArrayError,
+		},
+		{
+			name: "array_intersect",
+			args: []interface{}{
+				"1", []interface{}{1, 2, 3},
+			},
+			result: errorArrayFirstArgumentNotArrayError,
+		},
+		{
+			name: "array_union",
+			args: []interface{}{
+				[]interface{}{1, 1, 2, 3}, []interface{}{1, 3, 4},
+			},
+			result: []interface{}{1, 2, 3, 4},
+		},
+		{
+			name: "array_union",
+			args: []interface{}{
+				[]interface{}{"hello", "ekuiper", 2, 3}, []interface{}{"ekuiper", 3, 4},
+			},
+			result: []interface{}{"hello", "ekuiper", 2, 3, 4},
+		},
+		{
+			name: "array_union",
+			args: []interface{}{
+				[]interface{}{1, 1, 2, 3}, "ekuiper",
+			},
+			result: errorArraySecondArgumentNotArrayError,
+		},
+		{
+			name: "array_union",
+			args: []interface{}{
+				"1", []interface{}{1, 2, 3},
+			},
+			result: errorArrayFirstArgumentNotArrayError,
+		},
+		{
+			name: "array_max",
+			args: []interface{}{
+				[]interface{}{1},
+			},
+			result: 1,
+		},
+		{
+			name: "array_max",
+			args: []interface{}{
+				[]interface{}{1, nil, 3},
+			},
+			result: nil,
+		},
+		{
+			name: "array_max",
+			args: []interface{}{
+				[]interface{}{1, "4", 3},
+			},
+			result: "4",
+		},
+		{
+			name: "array_max",
+			args: []interface{}{
+				[]interface{}{1, "a4a", 3},
+			},
+			result: errorArrayContainsNonNumOrBoolValError,
+		},
+		{
+			name: "array_max",
+			args: []interface{}{
+				[]interface{}{1.2, 4.2, 3.0},
+			},
+			result: 4.2,
+		},
+		{
+			name: "array_max",
+			args: []interface{}{
+				[]interface{}{1, 3.2, 4.1, 2},
+			},
+			result: 4.1,
+		},
+		{
+			name: "array_min",
+			args: []interface{}{
+				[]interface{}{1, nil, 3},
+			},
+			result: nil,
+		},
+		{
+			name: "array_min",
+			args: []interface{}{
+				[]interface{}{1, "0", 3},
+			},
+			result: "0",
+		},
+		{
+			name: "array_min",
+			args: []interface{}{
+				[]interface{}{1.2, 4.2, 3.0},
+			},
+			result: 1.2,
+		},
+		{
+			name: "array_min",
+			args: []interface{}{
+				[]interface{}{1, "a4a", 3},
+			},
+			result: errorArrayContainsNonNumOrBoolValError,
+		},
+		{
+			name: "array_min",
+			args: []interface{}{
+				[]interface{}{1, 3.2, 4.1, 2},
+			},
+			result: 1,
+		},
+		{
+			name: "array_except",
+			args: []interface{}{
+				[]interface{}{1, 2, 3}, []interface{}{1, 3, 4},
+			},
+			result: []interface{}{2},
+		},
+		{
+			name: "array_except",
+			args: []interface{}{
+				[]interface{}{1, 2, 3}, []interface{}{4, 5, 6},
+			},
+			result: []interface{}{1, 2, 3},
+		},
+		{
+			name: "array_except",
+			args: []interface{}{
+				[]interface{}{1, 2, 1, 2, 3}, []interface{}{1, 2, 1, 1, 2, 2, 4},
+			},
+			result: []interface{}{3},
+		},
+		{
+			name: "array_except",
+			args: []interface{}{
+				[]interface{}{1, 1, 1, 1, 3}, []interface{}{4},
+			},
+			result: []interface{}{1, 3},
+		},
+		{
+			name: "repeat",
+			args: []interface{}{
+				1, 5,
+			},
+			result: []interface{}{1, 1, 1, 1, 1},
+		},
+		{
+			name: "repeat",
+			args: []interface{}{
+				1, "hellow",
+			},
+			result: errorArraySecondArgumentNotIntError,
+		},
+		{
+			name: "repeat",
+			args: []interface{}{
+				"hello", 3,
+			},
+			result: []interface{}{"hello", "hello", "hello"},
+		},
+		{
+			name: "repeat",
+			args: []interface{}{
+				"rockset", 0,
+			},
+			result: []interface{}{},
+		},
+		{
+			name: "sequence",
+			args: []interface{}{
+				1, 5,
+			},
+			result: []interface{}{1, 2, 3, 4, 5},
+		},
+		{
+			name: "sequence",
+			args: []interface{}{
+				"1", 10, 2,
+			},
+			result: errorArrayFirstArgumentNotIntError,
+		},
+		{
+			name: "sequence",
+			args: []interface{}{
+				1, "6", 2,
+			},
+			result: errorArraySecondArgumentNotIntError,
+		},
+		{
+			name: "sequence",
+			args: []interface{}{
+				1, 7, "1",
+			},
+			result: errorArrayThirdArgumentNotIntError,
+		},
+		{
+			name: "sequence",
+			args: []interface{}{
+				1, 10, 2,
+			},
+			result: []interface{}{1, 3, 5, 7, 9},
+		},
+		{
+			name: "sequence",
+			args: []interface{}{
+				10, 1, -3,
+			},
+			result: []interface{}{10, 7, 4, 1},
+		},
 	}
 	for i, tt := range tests {
 		f, ok := builtins[tt.name]