diff --git a/llx/builtin.go b/llx/builtin.go index 169fbd1ea2..9105915f6e 100644 --- a/llx/builtin.go +++ b/llx/builtin.go @@ -596,6 +596,7 @@ func init() { "fieldDuplicates": {f: arrayFieldDuplicatesV2}, "unique": {f: arrayUniqueV2}, "difference": {f: arrayDifferenceV2}, + "in": {f: anyArrayInStringArray}, "containsAll": {f: arrayContainsAll}, "containsNone": {f: arrayContainsNone}, "==": {Compiler: compileArrayOpArray("=="), f: tarrayCmpTarrayV2, Label: "=="}, diff --git a/llx/builtin_array.go b/llx/builtin_array.go index a7a4d6d0c4..044f9934f5 100644 --- a/llx/builtin_array.go +++ b/llx/builtin_array.go @@ -743,6 +743,45 @@ func arrayDifferenceV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64 return &RawData{Type: bind.Type, Value: res}, 0, nil } +func anyArrayInStringArray(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) { + if bind.Value == nil { + return &RawData{Type: bind.Type, Error: bind.Error}, 0, nil + } + + anyArray := bind.Value.([]any) + argRef := chunk.Function.Args[0] + arg, rref, err := e.resolveValue(argRef, ref) + if err != nil || rref > 0 { + return nil, rref, err + } + + if arg.Value == nil { + return BoolFalse, 0, nil + } + + for b := range anyArray { + binds, ok := anyArray[b].(string) + if !ok { + return BoolFalse, 0, nil + } + + arr := arg.Value.([]any) + found := false + for i := range arr { + v := arr[i].(string) + if binds == v { + found = true + break + } + } + if !found { + return BoolFalse, 0, nil + } + } + + return BoolTrue, 0, nil +} + func arrayContainsAll(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) { if bind.Value == nil { return &RawData{Type: bind.Type, Error: bind.Error}, 0, nil diff --git a/llx/builtin_map.go b/llx/builtin_map.go index ba2925ca9a..acf909e278 100644 --- a/llx/builtin_map.go +++ b/llx/builtin_map.go @@ -532,7 +532,7 @@ func dictBlockCallV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) func dictCamelcaseV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) { _, ok := bind.Value.(string) if !ok { - return nil, 0, errors.New("dict value does not support field `downcase`") + return nil, 0, errors.New("dict value does not support field `camelcase`") } return stringCamelcaseV2(e, bind, chunk, ref) @@ -1283,6 +1283,8 @@ func dictIn(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData switch bind.Value.(type) { case string: return stringInArray(e, bind, chunk, ref) + case []any: + return anyArrayInStringArray(e, bind, chunk, ref) default: return nil, 0, errors.New("dict value does not support field `in`") } diff --git a/mqlc/builtin.go b/mqlc/builtin.go index 0edd485cd6..02110ef0b7 100644 --- a/mqlc/builtin.go +++ b/mqlc/builtin.go @@ -102,6 +102,7 @@ func init() { "where": {compile: compileArrayWhere, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, "duplicates": {compile: compileArrayDuplicates, signature: FunctionSignature{Required: 0, Args: []types.Type{types.String}}}, "unique": {compile: compileArrayUnique, signature: FunctionSignature{Required: 0}}, + "in": {typ: boolType, compile: compileStringIn, signature: FunctionSignature{Required: 1, Args: []types.Type{types.Array(types.String)}}}, "contains": {compile: compileArrayContains, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, "containsOnly": {compile: compileArrayContainsOnly, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, "containsAll": {compile: compileArrayContainsAll, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, diff --git a/providers/core/resources/mql_test.go b/providers/core/resources/mql_test.go index 39d3d5c1dc..0c01efe8db 100644 --- a/providers/core/resources/mql_test.go +++ b/providers/core/resources/mql_test.go @@ -609,6 +609,14 @@ func TestArray(t *testing.T) { ResultIndex: 1, Expectation: true, }, + { + Code: "['hi'].in(['one','hi','five'])", + Expectation: true, + }, + { + Code: "['hi', 'bob'].in(['one','hi','five'])", + Expectation: false, + }, { Code: "[1,2,3].all(_ < 9)", ResultIndex: 1, diff --git a/providers/os/resources/mql_test.go b/providers/os/resources/mql_test.go index ee085a51d6..452f9587c3 100644 --- a/providers/os/resources/mql_test.go +++ b/providers/os/resources/mql_test.go @@ -383,6 +383,14 @@ func TestDict_Methods_Map(t *testing.T) { Code: p + "params['string-array'].where(_ == 'a')", Expectation: []interface{}{"a"}, }, + { + Code: p + "params['string-array'].in(['a', 'b', 'c'])", + Expectation: true, + }, + { + Code: p + "params['string-array'].in(['z', 'b'])", + Expectation: false, + }, { Code: p + "params['string-array'].one(_ == 'a')", ResultIndex: 1,