Prechádzať zdrojové kódy

feat: support more object functions (#1888)

* support more object functions

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

* add doc

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

---------

Signed-off-by: yisaer <disxiaofei@163.com>
Song Gao 1 rok pred
rodič
commit
8da4f4a482

+ 4 - 0
docs/en_US/sqls/built-in_functions.md

@@ -193,6 +193,10 @@ Currently, 'zlib', 'gzip', 'flate' and 'zstd' method are supported.
 | Function   | Example                   | Description                                                           |
 |------------|---------------------------|-----------------------------------------------------------------------|
 | keys | keys(map[string]interface{}) | Return an array containing the keys of the map |
+| values | values(map[string]interface{}) | Return an array containing the values of the map |
+| object | object(arr1, arr2) | Construct an object from an array of keys and an array of values. keys must be an array of strings. values must be an arbitrary array of the same length as keys. |
+| zip | zip([arr1, arr2], ......) | Construct an object from an array of entries. Each entry must itself be an array of size 2: the first element is the key (and must be a string), and the second element is the value. |
+| items | items(map[string]interface{}) | Return an array containing the entries of obj. Each entry is a 2-element array; the first is the key, the second is the value. |
 
 ## Analytic Functions
 

+ 4 - 0
docs/zh_CN/sqls/built-in_functions.md

@@ -193,6 +193,10 @@ eKuiper 具有许多内置函数,可以对数据执行计算。
 | 函数         | 示例                        | 说明              |
 |------------|---------------------------|-----------------|
 | keys | keys(map[string]interface{}) | 返回的给定的 map 参数中的所有 key 值 |
+| values | values(map[string]interface{}) | 返回给定的 map 参数中的所有 value 值 |
+| object | object(arr1, arr2) | 接受两个 list 参数来构造 map 对象,第一个 list 作为 map 对象的 key,第二个 list 作为 map 对象的 value。两个 list 参数长度必须相等 |
+| zip | zip([arr1, arr2], ......) | 接受一组 list 对象来构造 map 对象,每个 list 元素的长度必须为 2,每个 list 元素内的第一个元素将作为 key,第二个元素将作为 value |
+| items | items(map[string]interface{}) | 根据给定的 map 参数构造一个 list 对象,每个元素都为一个长度为 2 的 list 对象,其中第一个元素为 key,第二个元素为 value |
 
 ## 分析函数
 

+ 94 - 1
internal/binder/function/funcs_obj.go

@@ -33,7 +33,100 @@ func registerObjectFunc() {
 				}
 				return list, true
 			}
-			return fmt.Errorf("the arg for keys should be map[string]interface{}"), false
+			return fmt.Errorf("the argument should be map[string]interface{}"), false
+		},
+		val: ValidateOneArg,
+	}
+	builtins["values"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			arg := args[0]
+			if arg, ok := arg.(map[string]interface{}); ok {
+				list := make([]interface{}, 0, len(arg))
+				for _, value := range arg {
+					list = append(list, value)
+				}
+				return list, true
+			}
+			return fmt.Errorf("the argument should be map[string]interface{}"), false
+		},
+		val: ValidateOneArg,
+	}
+	builtins["object"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			keys, ok := args[0].([]interface{})
+			if !ok {
+				return fmt.Errorf("first argument should be []string"), false
+			}
+			values, ok := args[1].([]interface{})
+			if !ok {
+				return fmt.Errorf("second argument should be []interface{}"), false
+			}
+			if len(keys) != len(values) {
+				return fmt.Errorf("the length of the arguments should be same"), false
+			}
+			if len(keys) == 0 {
+				return nil, true
+			}
+			m := make(map[string]interface{}, len(keys))
+			for i, k := range keys {
+				key, ok := k.(string)
+				if !ok {
+					return fmt.Errorf("first argument should be []string"), false
+				}
+				m[key] = values[i]
+			}
+			return m, true
+		},
+		val: func(ctx api.FunctionContext, args []ast.Expr) error {
+			return ValidateLen(2, len(args))
+		},
+	}
+	builtins["zip"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			lists, ok := args[0].([]interface{})
+			if !ok {
+				return fmt.Errorf("each argument should be [][2]interface{}"), false
+			}
+			if len(lists) == 0 {
+				return nil, true
+			}
+			m := make(map[string]interface{}, len(lists))
+			for _, item := range lists {
+				a, ok := item.([]interface{})
+				if !ok {
+					return fmt.Errorf("each argument should be [][2]interface{}"), false
+				}
+				if len(a) != 2 {
+					return fmt.Errorf("each argument should be [][2]interface{}"), false
+				}
+				key, ok := a[0].(string)
+				if !ok {
+					return fmt.Errorf("the first element in the list item should be string"), false
+				}
+				m[key] = a[1]
+			}
+			return m, true
+		},
+		val: ValidateOneArg,
+	}
+	builtins["items"] = builtinFunc{
+		fType: ast.FuncTypeScalar,
+		exec: func(ctx api.FunctionContext, args []interface{}) (interface{}, bool) {
+			m, ok := args[0].(map[string]interface{})
+			if !ok {
+				return fmt.Errorf("first argument should be map[string]interface{}"), false
+			}
+			if len(m) < 1 {
+				return nil, true
+			}
+			list := make([]interface{}, 0, len(m))
+			for k, v := range m {
+				list = append(list, []interface{}{k, v})
+			}
+			return list, true
 		},
 		val: ValidateOneArg,
 	}

+ 139 - 3
internal/binder/function/funcs_obj_test.go

@@ -49,7 +49,134 @@ func TestObjectFunctions(t *testing.T) {
 		{
 			name:   "keys",
 			args:   []interface{}{1, 2},
-			result: fmt.Errorf("the arg for keys should be map[string]interface{}"),
+			result: fmt.Errorf("the argument should be map[string]interface{}"),
+		},
+		{
+			name: "values",
+			args: []interface{}{
+				map[string]interface{}{
+					"a": "c",
+					"b": "d",
+				},
+			},
+			result: []interface{}{"c", "d"},
+		},
+		{
+			name:   "values",
+			args:   []interface{}{1, 2},
+			result: fmt.Errorf("the argument should be map[string]interface{}"),
+		},
+		{
+			name: "object",
+			args: []interface{}{
+				[]interface{}{"a", "b"},
+				[]interface{}{1, 2},
+			},
+			result: map[string]interface{}{
+				"a": 1,
+				"b": 2,
+			},
+		},
+		{
+			name: "object",
+			args: []interface{}{
+				1,
+				[]interface{}{1, 2},
+			},
+			result: fmt.Errorf("first argument should be []string"),
+		},
+		{
+			name: "object",
+			args: []interface{}{
+				[]interface{}{1, 2},
+				[]interface{}{1, 2},
+			},
+			result: fmt.Errorf("first argument should be []string"),
+		},
+		{
+			name: "object",
+			args: []interface{}{
+				[]interface{}{1, 2},
+				1,
+			},
+			result: fmt.Errorf("second argument should be []interface{}"),
+		},
+		{
+			name: "object",
+			args: []interface{}{
+				[]interface{}{"a", "b"},
+				[]interface{}{1, 2, 3},
+			},
+			result: fmt.Errorf("the length of the arguments should be same"),
+		},
+		{
+			name: "zip",
+			args: []interface{}{
+				[]interface{}{
+					[]interface{}{"a", 1},
+					[]interface{}{"b", 2},
+				},
+			},
+			result: map[string]interface{}{
+				"a": 1,
+				"b": 2,
+			},
+		},
+		{
+			name: "zip",
+			args: []interface{}{
+				1,
+			},
+			result: fmt.Errorf("each argument should be [][2]interface{}"),
+		},
+		{
+			name: "zip",
+			args: []interface{}{
+				[]interface{}{
+					1, 2,
+				},
+			},
+			result: fmt.Errorf("each argument should be [][2]interface{}"),
+		},
+		{
+			name: "zip",
+			args: []interface{}{
+				[]interface{}{
+					[]interface{}{"a", 1, 3},
+					[]interface{}{"b", 2, 4},
+				},
+			},
+			result: fmt.Errorf("each argument should be [][2]interface{}"),
+		},
+		{
+			name: "zip",
+			args: []interface{}{
+				[]interface{}{
+					[]interface{}{1, 3},
+					[]interface{}{2, 4},
+				},
+			},
+			result: fmt.Errorf("the first element in the list item should be string"),
+		},
+		{
+			name: "items",
+			args: []interface{}{
+				map[string]interface{}{
+					"a": 1,
+					"b": 2,
+				},
+			},
+			result: []interface{}{
+				[]interface{}{"a", 1},
+				[]interface{}{"b", 2},
+			},
+		},
+		{
+			name: "items",
+			args: []interface{}{
+				1,
+			},
+			result: fmt.Errorf("first argument should be map[string]interface{}"),
 		},
 		{
 			name: "element_at",
@@ -88,12 +215,21 @@ func TestObjectFunctions(t *testing.T) {
 			t.Fatal(fmt.Sprintf("builtin %v not found", tt.name))
 		}
 		result, _ := f.exec(fctx, tt.args)
-		if r, ok := result.([]string); ok {
+		switch r := result.(type) {
+		case []string:
 			sort.Strings(r)
 			if !reflect.DeepEqual(r, tt.result) {
 				t.Errorf("%d result mismatch,\ngot:\t%v \nwant:\t%v", i, r, tt.result)
 			}
-		} else {
+		case []interface{}:
+			rr := make([]interface{}, len(r))
+			copy(rr, r)
+			rr[0] = r[1]
+			rr[1] = r[0]
+			if !reflect.DeepEqual(r, tt.result) && !reflect.DeepEqual(rr, tt.result) {
+				t.Errorf("%d result mismatch,\ngot:\t%v \nwant:\t%v", i, r, tt.result)
+			}
+		default:
 			if !reflect.DeepEqual(result, tt.result) {
 				t.Errorf("%d result mismatch,\ngot:\t%v \nwant:\t%v", i, result, tt.result)
 			}