Skip to content

Commit

Permalink
object.subset: support array + set combination (#4865)
Browse files Browse the repository at this point in the history
Add support for checking if `super` array contains
every element of `sub` set to object.subset.
object.subset allows `super` to be array and `sub` to be set.

Fixes: #4858

Signed-off-by: x-color <36035885+x-color@users.noreply.github.com>
  • Loading branch information
x-color committed Jul 11, 2022
1 parent d2ca577 commit 3f1ed13
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 3 deletions.
4 changes: 3 additions & 1 deletion ast/builtins.go
Expand Up @@ -1431,12 +1431,14 @@ var ObjectSubset = &Builtin{
Description: "Determines if an object `sub` is a subset of another object `super`." +
"Object `sub` is a subset of object `super` if and only if every key in `sub` is also in `super`, " +
"**and** for all keys which `sub` and `super` share, they have the same value. " +
"This function works with objects, sets, and arrays. " +
"This function works with objects, sets, arrays and a set of array and set." +
"If both arguments are objects, then the operation is recursive, e.g. " +
"`{\"c\": {\"x\": {10, 15, 20}}` is a subset of `{\"a\": \"b\", \"c\": {\"x\": {10, 15, 20, 25}, \"y\": \"z\"}`. " +
"If both arguments are sets, then this function checks if every element of `sub` is a member of `super`, " +
"but does not attempt to recurse. If both arguments are arrays, " +
"then this function checks if `sub` appears contiguously in order within `super`, " +
"and also does not attempt to recurse. If `super` is array and `sub` is set, " +
"then this function checks if `super` contains every element of `sub` with no consideration of ordering, " +
"and also does not attempt to recurse.",
Decl: types.NewFunction(
types.Args(
Expand Down
2 changes: 1 addition & 1 deletion builtin_metadata.json
Expand Up @@ -8565,7 +8565,7 @@
"v0.42.1",
"edge"
],
"description": "Determines if an object `sub` is a subset of another object `super`.Object `sub` is a subset of object `super` if and only if every key in `sub` is also in `super`, **and** for all keys which `sub` and `super` share, they have the same value. This function works with objects, sets, and arrays. If both arguments are objects, then the operation is recursive, e.g. `{\"c\": {\"x\": {10, 15, 20}}` is a subset of `{\"a\": \"b\", \"c\": {\"x\": {10, 15, 20, 25}, \"y\": \"z\"}`. If both arguments are sets, then this function checks if every element of `sub` is a member of `super`, but does not attempt to recurse. If both arguments are arrays, then this function checks if `sub` appears contiguously in order within `super`, and also does not attempt to recurse.",
"description": "Determines if an object `sub` is a subset of another object `super`.Object `sub` is a subset of object `super` if and only if every key in `sub` is also in `super`, **and** for all keys which `sub` and `super` share, they have the same value. This function works with objects, sets, arrays and a set of array and set.If both arguments are objects, then the operation is recursive, e.g. `{\"c\": {\"x\": {10, 15, 20}}` is a subset of `{\"a\": \"b\", \"c\": {\"x\": {10, 15, 20, 25}, \"y\": \"z\"}`. If both arguments are sets, then this function checks if every element of `sub` is a member of `super`, but does not attempt to recurse. If both arguments are arrays, then this function checks if `sub` appears contiguously in order within `super`, and also does not attempt to recurse. If `super` is array and `sub` is set, then this function checks if `super` contains every element of `sub` with no consideration of ordering, and also does not attempt to recurse.",
"introduced": "v0.42.0",
"result": {
"description": "`true` if `sub` is a subset of `super`",
Expand Down
41 changes: 41 additions & 0 deletions test/cases/testdata/subset/test-subset.yaml
Expand Up @@ -345,3 +345,44 @@ cases:
want_result:
- x: true

- data:
modules:
- |
package test
A := [1,2,3,4,5,6]
B := {4,3,2}
AsubB := object.subset(A, B)
BsubA := object.subset(B, A)
test_result {
AsubB # B is a subset of A
not BsubA # It is invalid operands
}
note: subset/array and set 1
query: data.test.test_result = x
want_result:
- x: true

- data:
modules:
- |
package test
A := [1,2,3,4,5,6]
B := {9,8,7}
AsubB := object.subset(A, B)
BsubA := object.subset(B, A)
test_result {
not AsubB # B is not a subset of A
not BsubA # It is invalid operands
}
note: subset/array and set 2
query: data.test.test_result = x
want_result:
- x: true
44 changes: 43 additions & 1 deletion topdown/subset.go
Expand Up @@ -63,6 +63,24 @@ func bothArrays(t1, t2 *ast.Term) (bool, *ast.Array, *ast.Array) {
return true, array1, array2
}

func arraySet(t1, t2 *ast.Term) (bool, *ast.Array, ast.Set) {
if (t1 == nil) || (t2 == nil) {
return false, nil, nil
}

array, ok := t1.Value.(*ast.Array)
if !ok {
return false, nil, nil
}

set, ok := t2.Value.(ast.Set)
if !ok {
return false, nil, nil
}

return true, array, set
}

// objectSubset implements the subset operation on a pair of objects.
//
// This function will try to recursively apply the subset operation where it
Expand Down Expand Up @@ -194,6 +212,25 @@ func arraySubset(super, sub *ast.Array) bool {
}
}

// arraySetSubset implements the subset operation on array and set.
//
// This is defined to mean that the entire "sub" set must appear in
// the "super" array with no consideration of ordering.
// For the same rationale as setSubset(), we do not attempt
// to recurse into values.
func arraySetSubset(super *ast.Array, sub ast.Set) bool {
unmatched := sub.Len()
return super.Until(func(t *ast.Term) bool {
if sub.Contains(t) {
unmatched--
}
if unmatched == 0 {
return true
}
return false
})
}

func builtinObjectSubset(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
superTerm := operands[0]
subTerm := operands[1]
Expand All @@ -213,7 +250,12 @@ func builtinObjectSubset(_ BuiltinContext, operands []*ast.Term, iter func(*ast.
return iter(ast.BooleanTerm(arraySubset(superArray, subArray)))
}

return builtins.ErrOperand("both arguments object.subset must be of the same type")
if ok, superArray, subSet := arraySet(superTerm, subTerm); ok {
// Super operand is array and sub operand is set
return iter(ast.BooleanTerm(arraySetSubset(superArray, subSet)))
}

return builtins.ErrOperand("both arguments object.subset must be of the same type or array and set")
}

func init() {
Expand Down

0 comments on commit 3f1ed13

Please sign in to comment.