diff --git a/llx/builtin.go b/llx/builtin.go index 94ca757c3c..535ff5ffeb 100644 --- a/llx/builtin.go +++ b/llx/builtin.go @@ -526,7 +526,8 @@ func init() { "map": {f: dictMapV2}, "flat": {f: dictFlat}, "difference": {f: dictDifferenceV2}, - "containsNone": {f: dictContainsNoneV2}, + "containsAll": {f: dictContainsAll}, + "containsNone": {f: dictContainsNone}, string("contains" + types.String): {f: dictContainsStringV2, Label: "contains"}, string("contains" + types.Array(types.String)): {f: dictContainsArrayStringV2, Label: "contains"}, string("contains" + types.Int): {f: dictContainsIntV2, Label: "contains"}, @@ -557,7 +558,8 @@ func init() { "fieldDuplicates": {f: arrayFieldDuplicatesV2}, "unique": {f: arrayUniqueV2}, "difference": {f: arrayDifferenceV2}, - "containsNone": {f: arrayContainsNoneV2}, + "containsAll": {f: arrayContainsAll}, + "containsNone": {f: arrayContainsNone}, "==": {Compiler: compileArrayOpArray("=="), f: tarrayCmpTarrayV2, Label: "=="}, "!=": {Compiler: compileArrayOpArray("!="), f: tarrayNotTarrayV2, Label: "!="}, "==" + string(types.Nil): {f: arrayCmpNilV2}, diff --git a/llx/builtin_array.go b/llx/builtin_array.go index 15b044a157..5c7ed01574 100644 --- a/llx/builtin_array.go +++ b/llx/builtin_array.go @@ -739,7 +739,58 @@ func arrayDifferenceV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64 return &RawData{Type: bind.Type, Value: res}, 0, nil } -func arrayContainsNoneV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) { +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 + } + + args := chunk.Function.Args + // TODO: all this needs to go into the compile phase + if len(args) < 1 { + return nil, 0, errors.New("Called `arrayContainsNone` with " + strconv.Itoa(len(args)) + " arguments, only 1 supported.") + } + if len(args) > 1 { + return nil, 0, errors.New("called `arrayContainsNone` with " + strconv.Itoa(len(args)) + " arguments, only 1 supported.") + } + // ^^ TODO + + argRef := args[0] + arg, rref, err := e.resolveValue(argRef, ref) + if err != nil || rref > 0 { + return nil, rref, err + } + + t := types.Type(arg.Type) + if t != bind.Type { + return nil, 0, errors.New("called `arrayNone` with wrong type (got: " + t.Label() + ", expected:" + bind.Type.Label() + ")") + } + + ct := bind.Type.Child() + equalFunc, ok := types.Equal[ct] + if !ok { + return nil, 0, errors.New("cannot compare array entries") + } + + org := bind.Value.([]interface{}) + filters := arg.Value.([]interface{}) + + for i := range org { + for j := range filters { + if equalFunc(org[i], filters[j]) { + filters = append(filters[0:j], filters[j+1:]...) + break + } + } + if len(filters) == 0 { + filters = nil + break + } + } + + return &RawData{Type: bind.Type, Value: filters}, 0, nil +} + +func arrayContainsNone(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 8b1c38c0fe..a7bdbf63f7 100644 --- a/llx/builtin_map.go +++ b/llx/builtin_map.go @@ -993,7 +993,55 @@ func dictDifferenceV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) return &RawData{Type: bind.Type, Value: res}, 0, nil } -func dictContainsNoneV2(e *blockExecutor, bind *RawData, chunk *Chunk, ref uint64) (*RawData, uint64, error) { +func dictContainsAll(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 + } + + args := chunk.Function.Args + // TODO: all this needs to go into the compile phase + if len(args) < 1 { + return nil, 0, errors.New("Called `difference` with " + strconv.Itoa(len(args)) + " arguments, only 1 supported.") + } + if len(args) > 1 { + return nil, 0, errors.New("called `difference` with " + strconv.Itoa(len(args)) + " arguments, only 1 supported.") + } + // ^^ TODO + + argRef := args[0] + arg, rref, err := e.resolveValue(argRef, ref) + if err != nil || rref > 0 { + return nil, rref, err + } + + org, ok := bind.Value.([]interface{}) + if !ok { + return &RawData{Type: bind.Type, Error: errors.New("cannot compute difference of lists, argument is not a list")}, 0, nil + } + + filters, ok := arg.Value.([]interface{}) + if !ok { + return &RawData{Type: bind.Type, Error: errors.New("tried to call function with a non-array, please make sure the argument is an array")}, 0, nil + // filters = []interface{}{arg.Value} + } + + for i := range org { + for j := range filters { + if org[i] == filters[j] { + filters = append(filters[0:j], filters[j+1:]...) + break + } + } + if len(filters) == 0 { + filters = nil + break + } + } + + return &RawData{Type: bind.Type, Value: filters}, 0, nil +} + +func dictContainsNone(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/mqlc/builtin.go b/mqlc/builtin.go index 0533feeea2..c2a5274649 100644 --- a/mqlc/builtin.go +++ b/mqlc/builtin.go @@ -70,6 +70,7 @@ func init() { "where": {compile: compileDictWhere, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, "contains": {compile: compileDictContains, typ: boolType, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, "containsOnly": {compile: compileDictContainsOnly, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, + "containsAll": {compile: compileDictContainsAll, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, "containsNone": {compile: compileDictContainsNone, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, "all": {compile: compileDictAll, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, "any": {compile: compileDictAny, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, @@ -92,6 +93,7 @@ func init() { "unique": {compile: compileArrayUnique, signature: FunctionSignature{Required: 0}}, "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}}}, "containsNone": {compile: compileArrayContainsNone, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, "all": {compile: compileArrayAll, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, "any": {compile: compileArrayAny, signature: FunctionSignature{Required: 1, Args: []types.Type{types.FunctionLike}}}, diff --git a/mqlc/builtin_array.go b/mqlc/builtin_array.go index 837da8a71f..bb81f42626 100644 --- a/mqlc/builtin_array.go +++ b/mqlc/builtin_array.go @@ -274,7 +274,7 @@ func compileArrayContainsOnly(c *compiler, typ types.Type, ref uint64, id string return types.Bool, nil } -func compileArrayContainsNone(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { +func compileArrayContainsEq(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call, method string) (types.Type, error) { if call == nil || len(call.Function) != 1 { return types.Nil, errors.New("function " + id + " needs one argument (array)") } @@ -301,7 +301,7 @@ func compileArrayContainsNone(c *compiler, typ types.Type, ref uint64, id string // .containsNone c.addChunk(&llx.Chunk{ Call: llx.Chunk_FUNCTION, - Id: "containsNone", + Id: method, Function: &llx.Function{ Type: string(typ), Binding: ref, @@ -325,11 +325,19 @@ func compileArrayContainsNone(c *compiler, typ types.Type, ref uint64, id string }) checksum := c.Result.CodeV2.Checksums[c.tailRef()] - c.Result.Labels.Labels[checksum] = "[].containsNone()" + c.Result.Labels.Labels[checksum] = "[]." + method + "()" return types.Bool, nil } +func compileArrayContainsAll(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { + return compileArrayContainsEq(c, typ, ref, id, call, "containsAll") +} + +func compileArrayContainsNone(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { + return compileArrayContainsEq(c, typ, ref, id, call, "containsNone") +} + func compileArrayAll(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { _, err := compileArrayWhere(c, typ, ref, "$whereNot", call) if err != nil { diff --git a/mqlc/builtin_map.go b/mqlc/builtin_map.go index 1d447b3be3..4d07f064ea 100644 --- a/mqlc/builtin_map.go +++ b/mqlc/builtin_map.go @@ -218,7 +218,7 @@ func compileDictContainsOnly(c *compiler, typ types.Type, ref uint64, id string, return types.Bool, nil } -func compileDictContainsNone(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { +func compileDictContainsEq(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call, method string) (types.Type, error) { if call == nil || len(call.Function) != 1 { return types.Nil, errors.New("function " + id + " needs one argument (dict)") } @@ -250,7 +250,7 @@ func compileDictContainsNone(c *compiler, typ types.Type, ref uint64, id string, // .containsNone c.addChunk(&llx.Chunk{ Call: llx.Chunk_FUNCTION, - Id: "containsNone", + Id: method, Function: &llx.Function{ Type: string(typ), Binding: ref, @@ -274,11 +274,19 @@ func compileDictContainsNone(c *compiler, typ types.Type, ref uint64, id string, }) checksum := c.Result.CodeV2.Checksums[c.tailRef()] - c.Result.Labels.Labels[checksum] = "[].containsNone()" + c.Result.Labels.Labels[checksum] = "[]." + method + "()" return types.Bool, nil } +func compileDictContainsAll(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { + return compileDictContainsEq(c, typ, ref, id, call, "containsAll") +} + +func compileDictContainsNone(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { + return compileDictContainsEq(c, typ, ref, id, call, "containsNone") +} + func compileDictAll(c *compiler, typ types.Type, ref uint64, id string, call *parser.Call) (types.Type, error) { _, err := compileDictWhere(c, typ, ref, "$whereNot", call) if err != nil { diff --git a/providers/core/resources/mql_test.go b/providers/core/resources/mql_test.go index d50d75d5f7..ed54b6af34 100644 --- a/providers/core/resources/mql_test.go +++ b/providers/core/resources/mql_test.go @@ -643,6 +643,18 @@ func TestArray(t *testing.T) { Code: "a = [1]; [2,1,2,1].containsOnly(a)", Expectation: []interface{}{int64(2), int64(2)}, }, + { + Code: "[3,3,2,2].containsAll([1,2])", + Expectation: []interface{}{int64(1)}, + }, + { + Code: "[2,1,2,1].containsAll([1,2])", + ResultIndex: 0, Expectation: []interface{}(nil), + }, + { + Code: "a = [1,3]; [2,1,2,1].containsAll(a)", + Expectation: []interface{}{int64(3)}, + }, { Code: "[2,1,2,2].containsNone([1])", Expectation: []interface{}{int64(1)},