Bladeren bron

feat: support more functions (#1883)

* add func_2

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

* fix lint

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

* address the comment

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

---------

Signed-off-by: yisaer <disxiaofei@163.com>
Song Gao 1 jaar geleden
bovenliggende
commit
9801d650b5

+ 79 - 0
internal/binder/function/funcs_array.go

@@ -15,8 +15,16 @@
 package function
 package function
 
 
 import (
 import (
+	"fmt"
+
 	"github.com/lf-edge/ekuiper/pkg/api"
 	"github.com/lf-edge/ekuiper/pkg/api"
 	"github.com/lf-edge/ekuiper/pkg/ast"
 	"github.com/lf-edge/ekuiper/pkg/ast"
+	"github.com/lf-edge/ekuiper/pkg/cast"
+)
+
+var (
+	errorArrayArgumentError = fmt.Errorf("first argument should be array of interface{}")
+	errorArrayIndex         = fmt.Errorf("index out of range")
 )
 )
 
 
 func registerArrayFunc() {
 func registerArrayFunc() {
@@ -29,4 +37,75 @@ func registerArrayFunc() {
 			return nil
 			return nil
 		},
 		},
 	}
 	}
+	builtins["array_position"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			array, ok := args[0].([]interface{})
+			if !ok {
+				return errorArrayArgumentError, false
+			}
+			for i, item := range array {
+				if item == args[1] {
+					return i + 1, true
+				}
+			}
+			return 0, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
+	builtins["element_at"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			switch args[0].(type) {
+			case []interface{}:
+				array := args[0].([]interface{})
+				index, err := cast.ToInt(args[1], cast.STRICT)
+				if err != nil {
+					return err, false
+				}
+				if index == 0 {
+					return fmt.Errorf("index should be larger or smaller than 0"), false
+				}
+				if index-1 >= len(array) || (-index)-1 >= len(array) {
+					return errorArrayIndex, false
+				}
+				if index > 0 {
+					return array[index-1], true
+				}
+				return array[len(array)+index], true
+			case map[string]interface{}:
+				m := args[0].(map[string]interface{})
+				key, ok := args[1].(string)
+				if !ok {
+					return fmt.Errorf("second argument should be string"), false
+				}
+				return m[key], true
+			default:
+				return fmt.Errorf("first argument should be []interface{} or map[string]interface{}"), false
+			}
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
+	builtins["array_contains"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			array, ok := args[0].([]interface{})
+			if !ok {
+				return errorArrayArgumentError, false
+			}
+			for _, item := range array {
+				if item == args[1] {
+					return true, true
+				}
+			}
+			return false, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
 }
 }

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

@@ -44,6 +44,99 @@ func TestArrayFunctions(t *testing.T) {
 				1, "2", 3,
 				1, "2", 3,
 			},
 			},
 		},
 		},
+		{
+			name: "array_position",
+			args: []interface{}{
+				1, 2,
+			},
+			result: errorArrayArgumentError,
+		},
+		{
+			name: "array_position",
+			args: []interface{}{
+				[]interface{}{3, 2, 1},
+				1,
+			},
+			result: 3,
+		},
+		{
+			name: "array_position",
+			args: []interface{}{
+				[]interface{}{3, 2, 1},
+				4,
+			},
+			result: 0,
+		},
+		{
+			name: "length",
+			args: []interface{}{
+				[]interface{}{1, 2, 3},
+			},
+			result: 3,
+		},
+		{
+			name: "element_at",
+			args: []interface{}{
+				1, 2,
+			},
+			result: fmt.Errorf("first argument should be []interface{} or map[string]interface{}"),
+		},
+		{
+			name: "element_at",
+			args: []interface{}{
+				[]interface{}{1, 2, 3}, 0,
+			},
+			result: fmt.Errorf("index should be larger or smaller than 0"),
+		},
+		{
+			name: "element_at",
+			args: []interface{}{
+				[]interface{}{1, 2, 3}, 4,
+			},
+			result: errorArrayIndex,
+		},
+		{
+			name: "element_at",
+			args: []interface{}{
+				[]interface{}{1, 2, 3}, -4,
+			},
+			result: errorArrayIndex,
+		},
+		{
+			name: "element_at",
+			args: []interface{}{
+				[]interface{}{1, 2, 3}, 1,
+			},
+			result: 1,
+		},
+		{
+			name: "element_at",
+			args: []interface{}{
+				[]interface{}{1, 2, 3}, -1,
+			},
+			result: 3,
+		},
+		{
+			name: "array_contains",
+			args: []interface{}{
+				1, 2,
+			},
+			result: errorArrayArgumentError,
+		},
+		{
+			name: "array_contains",
+			args: []interface{}{
+				[]interface{}{1, 2}, 2,
+			},
+			result: true,
+		},
+		{
+			name: "array_contains",
+			args: []interface{}{
+				[]interface{}{1, 2}, 3,
+			},
+			result: false,
+		},
 	}
 	}
 	for i, tt := range tests {
 	for i, tt := range tests {
 		f, ok := builtins[tt.name]
 		f, ok := builtins[tt.name]

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

@@ -51,6 +51,36 @@ func TestObjectFunctions(t *testing.T) {
 			args:   []interface{}{1, 2},
 			args:   []interface{}{1, 2},
 			result: fmt.Errorf("the arg for keys should be map[string]interface{}"),
 			result: fmt.Errorf("the arg for keys should be map[string]interface{}"),
 		},
 		},
+		{
+			name: "element_at",
+			args: []interface{}{
+				map[string]interface{}{
+					"a": 1,
+					"b": 2,
+				},
+				"a",
+			},
+			result: 1,
+		},
+		{
+			name: "element_at",
+			args: []interface{}{
+				"1",
+				"a",
+			},
+			result: fmt.Errorf("first argument should be []interface{} or map[string]interface{}"),
+		},
+		{
+			name: "element_at",
+			args: []interface{}{
+				map[string]interface{}{
+					"a": 1,
+					"b": 2,
+				},
+				2,
+			},
+			result: fmt.Errorf("second argument should be string"),
+		},
 	}
 	}
 	for i, tt := range tests {
 	for i, tt := range tests {
 		f, ok := builtins[tt.name]
 		f, ok := builtins[tt.name]

+ 9 - 2
internal/binder/function/funcs_str.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");
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // you may not use this file except in compliance with the License.
@@ -75,9 +75,16 @@ func registerStrFunc() {
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
 			arg0 := cast.ToStringAlways(args[0])
 			arg0 := cast.ToStringAlways(args[0])
+			switch v := args[0].(type) {
+			case []interface{}:
+				return len(v), true
+			case map[string]interface{}:
+				return len(v), true
+			default:
+			}
 			return utf8.RuneCountInString(arg0), true
 			return utf8.RuneCountInString(arg0), true
 		},
 		},
-		val: ValidateOneStrArg,
+		val: ValidateOneArg,
 	}
 	}
 	builtins["lower"] = builtinFunc{
 	builtins["lower"] = builtinFunc{
 		fType: ast.FuncTypeScalar,
 		fType: ast.FuncTypeScalar,