Skip to content

Commit

Permalink
✨ support containsAll (#2186)
Browse files Browse the repository at this point in the history
This is the logical counterpart to `containsNone` and `containsOnly` and
complements them nicely:

```coffee
[1,2,3].containsAll([2,3])
```

This is great to test e.g.:

```coffee
machine.tags.keys.containsAll( props.mandatoryTags )
```

Signed-off-by: Dominik Richter <[email protected]>
  • Loading branch information
arlimus authored Oct 12, 2023
1 parent 4b596e0 commit 0659d30
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 10 deletions.
6 changes: 4 additions & 2 deletions llx/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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},
Expand Down
53 changes: 52 additions & 1 deletion llx/builtin_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
50 changes: 49 additions & 1 deletion llx/builtin_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions mqlc/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}}},
Expand All @@ -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}}},
Expand Down
14 changes: 11 additions & 3 deletions mqlc/builtin_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
Expand All @@ -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,
Expand All @@ -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 {
Expand Down
14 changes: 11 additions & 3 deletions mqlc/builtin_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
12 changes: 12 additions & 0 deletions providers/core/resources/mql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)},
Expand Down

0 comments on commit 0659d30

Please sign in to comment.