diff --git a/ast/builtins.go b/ast/builtins.go
index d36fcc362d..5889064785 100644
--- a/ast/builtins.go
+++ b/ast/builtins.go
@@ -61,6 +61,8 @@ var DefaultBuiltins = [...]*Builtin{
Product,
Max,
Min,
+ Any,
+ All,
// Casting
ToNumber,
@@ -436,6 +438,36 @@ var Min = &Builtin{
),
}
+// All takes a list and returns true if all of the items
+// are true. A collection of length 0 returns true.
+var All = &Builtin{
+ Name: "all",
+ Decl: types.NewFunction(
+ types.Args(
+ types.NewAny(
+ types.NewSet(types.A),
+ types.NewArray(nil, types.A),
+ ),
+ ),
+ types.B,
+ ),
+}
+
+// Any takes a collection and returns true if any of the items
+// is true. A collection of length 0 returns false.
+var Any = &Builtin{
+ Name: "any",
+ Decl: types.NewFunction(
+ types.Args(
+ types.NewAny(
+ types.NewSet(types.A),
+ types.NewArray(nil, types.A),
+ ),
+ ),
+ types.B,
+ ),
+}
+
/**
* Casting
*/
diff --git a/docs/book/language-reference.md b/docs/book/language-reference.md
index 8450b1cbd7..adab283721 100644
--- a/docs/book/language-reference.md
+++ b/docs/book/language-reference.md
@@ -42,6 +42,8 @@ complex types.
| ``max(array_or_set, output)`` | 1 | ``output`` is the maximum value in ``array_or_set`` |
| ``min(array_or_set, output)`` | 1 | ``output`` is the minimum value in ``array_or_set`` |
| ``sort(array_or_set, output)`` | 1 | ``output`` is the sorted ``array`` containing elements from ``array_or_set``. |
+| ``all(array_or_set, output)`` | 1 | ``output`` is ``true`` if all of the values in ``array_or_set`` are ``true``. A collection of length 0 returns ``true``.|
+| ``any(array_or_set, output)`` | 1 | ``output`` is ``true`` if any of the values in ``array_or_set`` is ``true``. A collection of length 0 returns ``false``.|
### Sets
diff --git a/topdown/aggregates.go b/topdown/aggregates.go
index 68516201c2..0b59487b64 100644
--- a/topdown/aggregates.go
+++ b/topdown/aggregates.go
@@ -154,6 +154,56 @@ func builtinSort(a ast.Value) (ast.Value, error) {
return nil, builtins.NewOperandTypeErr(1, a, "set", "array")
}
+func builtinAll(a ast.Value) (ast.Value, error) {
+ switch val := a.(type) {
+ case ast.Set:
+ res := true
+ match := ast.BooleanTerm(true)
+ val.Foreach(func(term *ast.Term) {
+ if !term.Equal(match) {
+ res = false
+ }
+ })
+ return ast.Boolean(res), nil
+ case ast.Array:
+ res := true
+ match := ast.BooleanTerm(true)
+ for _, term := range val {
+ if !term.Equal(match) {
+ res = false
+ }
+ }
+ return ast.Boolean(res), nil
+ default:
+ return nil, builtins.NewOperandTypeErr(1, a, "array", "set")
+ }
+}
+
+func builtinAny(a ast.Value) (ast.Value, error) {
+ switch val := a.(type) {
+ case ast.Set:
+ res := false
+ match := ast.BooleanTerm(true)
+ val.Foreach(func(term *ast.Term) {
+ if term.Equal(match) {
+ res = true
+ }
+ })
+ return ast.Boolean(res), nil
+ case ast.Array:
+ res := false
+ match := ast.BooleanTerm(true)
+ for _, term := range val {
+ if term.Equal(match) {
+ res = true
+ }
+ }
+ return ast.Boolean(res), nil
+ default:
+ return nil, builtins.NewOperandTypeErr(1, a, "array", "set")
+ }
+}
+
func init() {
RegisterFunctionalBuiltin1(ast.Count.Name, builtinCount)
RegisterFunctionalBuiltin1(ast.Sum.Name, builtinSum)
@@ -161,4 +211,6 @@ func init() {
RegisterFunctionalBuiltin1(ast.Max.Name, builtinMax)
RegisterFunctionalBuiltin1(ast.Min.Name, builtinMin)
RegisterFunctionalBuiltin1(ast.Sort.Name, builtinSort)
+ RegisterFunctionalBuiltin1(ast.Any.Name, builtinAny)
+ RegisterFunctionalBuiltin1(ast.All.Name, builtinAll)
}
diff --git a/topdown/aggregates_test.go b/topdown/aggregates_test.go
new file mode 100644
index 0000000000..1c89e0c031
--- /dev/null
+++ b/topdown/aggregates_test.go
@@ -0,0 +1,92 @@
+package topdown
+
+import (
+ "testing"
+)
+
+func TestTopDownAggregates(t *testing.T) {
+
+ tests := []struct {
+ note string
+ rules []string
+ expected interface{}
+ }{
+ {"count", []string{`p[x] { count(a, x) }`}, "[4]"},
+ {"count virtual", []string{`p[x] { count([y | q[y]], x) }`, `q[x] { x = a[_] }`}, "[4]"},
+ {"count keys", []string{`p[x] { count(b, x) }`}, "[2]"},
+ {"count keys virtual", []string{`p[x] { count([k | q[k] = _], x) }`, `q[k] = v { b[k] = v }`}, "[2]"},
+ {"count set", []string{`p = x { count(q, x) }`, `q[x] { x = a[_] }`}, "4"},
+ {"sum", []string{`p[x] { sum([1, 2, 3, 4], x) }`}, "[10]"},
+ {"sum set", []string{`p = x { sum({1, 2, 3, 4}, x) }`}, "10"},
+ {"sum virtual", []string{`p[x] { sum([y | q[y]], x) }`, `q[x] { a[_] = x }`}, "[10]"},
+ {"sum virtual set", []string{`p = x { sum(q, x) }`, `q[x] { a[_] = x }`}, "10"},
+ {"product", []string{"p { product([1,2,3,4], 24) }"}, "true"},
+ {"product set", []string{`p = x { product({1, 2, 3, 4}, x) }`}, "24"},
+ {"max", []string{`p[x] { max([1, 2, 3, 4], x) }`}, "[4]"},
+ {"max set", []string{`p = x { max({1, 2, 3, 4}, x) }`}, "4"},
+ {"max virtual", []string{`p[x] { max([y | q[y]], x) }`, `q[x] { a[_] = x }`}, "[4]"},
+ {"max virtual set", []string{`p = x { max(q, x) }`, `q[x] { a[_] = x }`}, "4"},
+ {"min", []string{`p[x] { min([1, 2, 3, 4], x) }`}, "[1]"},
+ {"min dups", []string{`p[x] { min([1, 2, 1, 3, 4], x) }`}, "[1]"},
+ {"min out-of-order", []string{`p[x] { min([3, 2, 1, 4, 6, -7, 10], x) }`}, "[-7]"},
+ {"min set", []string{`p = x { min({1, 2, 3, 4}, x) }`}, "1"},
+ {"min virtual", []string{`p[x] { min([y | q[y]], x) }`, `q[x] { a[_] = x }`}, "[1]"},
+ {"min virtual set", []string{`p = x { min(q, x) }`, `q[x] { a[_] = x }`}, "1"},
+ {"reduce ref dest", []string{`p = true { max([1, 2, 3, 4], a[3]) }`}, "true"},
+ {"reduce ref dest (2)", []string{`p = true { not max([1, 2, 3, 4, 5], a[3]) }`}, "true"},
+ {"sort", []string{`p = x { sort([4, 3, 2, 1], x) }`}, "[1 ,2, 3, 4]"},
+ {"sort set", []string{`p = x { sort({4,3,2,1}, x) }`}, "[1,2,3,4]"},
+ }
+
+ data := loadSmallTestData()
+
+ for _, tc := range tests {
+ runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
+ }
+}
+
+func TestAll(t *testing.T) {
+
+ tests := []struct {
+ note string
+ rules []string
+ expected interface{}
+ }{
+ {"empty set", []string{`p = x { x := all(set()) }`}, "true"},
+ {"empty array", []string{`p = x { x := all([]) }`}, "true"},
+ {"set success", []string{`p = x { x := all({true, true, true}) }`}, "true"},
+ {"array success", []string{`p = x { x := all( [true, true, true] ) }`}, "true"},
+ {"set fail", []string{`p = x { x := all( {true, false, true} ) }`}, "false"},
+ {"array fail", []string{`p = x { x := all( [false, true, true] ) }`}, "false"},
+ {"other types", []string{`p = x { x := all( [{}, "", true, true, 123] ) }`}, "false"},
+ }
+
+ data := loadSmallTestData()
+
+ for _, tc := range tests {
+ runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
+ }
+}
+
+func TestAny(t *testing.T) {
+
+ tests := []struct {
+ note string
+ rules []string
+ expected interface{}
+ }{
+ {"empty set", []string{`p = x { x := any(set()) }`}, "false"},
+ {"empty array", []string{`p = x { x := any([]) }`}, "false"},
+ {"set success", []string{`p = x { x := any({false, false, true}) }`}, "true"},
+ {"array success", []string{`p = x { x := any( [true, true, true, false, false] ) }`}, "true"},
+ {"set fail", []string{`p = x { x := any( {false, false, false} ) }`}, "false"},
+ {"array fail", []string{`p = x { x := any( [false] ) }`}, "false"},
+ {"other types", []string{`p = x { x := any( [true, {}, "false"] ) }`}, "true"},
+ }
+
+ data := loadSmallTestData()
+
+ for _, tc := range tests {
+ runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
+ }
+}
diff --git a/topdown/topdown_test.go b/topdown/topdown_test.go
index 8f7b6b5348..03aa807538 100644
--- a/topdown/topdown_test.go
+++ b/topdown/topdown_test.go
@@ -983,47 +983,6 @@ func TestTopDownDefaultKeyword(t *testing.T) {
}
}
-func TestTopDownAggregates(t *testing.T) {
-
- tests := []struct {
- note string
- rules []string
- expected interface{}
- }{
- {"count", []string{`p[x] { count(a, x) }`}, "[4]"},
- {"count virtual", []string{`p[x] { count([y | q[y]], x) }`, `q[x] { x = a[_] }`}, "[4]"},
- {"count keys", []string{`p[x] { count(b, x) }`}, "[2]"},
- {"count keys virtual", []string{`p[x] { count([k | q[k] = _], x) }`, `q[k] = v { b[k] = v }`}, "[2]"},
- {"count set", []string{`p = x { count(q, x) }`, `q[x] { x = a[_] }`}, "4"},
- {"sum", []string{`p[x] { sum([1, 2, 3, 4], x) }`}, "[10]"},
- {"sum set", []string{`p = x { sum({1, 2, 3, 4}, x) }`}, "10"},
- {"sum virtual", []string{`p[x] { sum([y | q[y]], x) }`, `q[x] { a[_] = x }`}, "[10]"},
- {"sum virtual set", []string{`p = x { sum(q, x) }`, `q[x] { a[_] = x }`}, "10"},
- {"product", []string{"p { product([1,2,3,4], 24) }"}, "true"},
- {"product set", []string{`p = x { product({1, 2, 3, 4}, x) }`}, "24"},
- {"max", []string{`p[x] { max([1, 2, 3, 4], x) }`}, "[4]"},
- {"max set", []string{`p = x { max({1, 2, 3, 4}, x) }`}, "4"},
- {"max virtual", []string{`p[x] { max([y | q[y]], x) }`, `q[x] { a[_] = x }`}, "[4]"},
- {"max virtual set", []string{`p = x { max(q, x) }`, `q[x] { a[_] = x }`}, "4"},
- {"min", []string{`p[x] { min([1, 2, 3, 4], x) }`}, "[1]"},
- {"min dups", []string{`p[x] { min([1, 2, 1, 3, 4], x) }`}, "[1]"},
- {"min out-of-order", []string{`p[x] { min([3, 2, 1, 4, 6, -7, 10], x) }`}, "[-7]"},
- {"min set", []string{`p = x { min({1, 2, 3, 4}, x) }`}, "1"},
- {"min virtual", []string{`p[x] { min([y | q[y]], x) }`, `q[x] { a[_] = x }`}, "[1]"},
- {"min virtual set", []string{`p = x { min(q, x) }`, `q[x] { a[_] = x }`}, "1"},
- {"reduce ref dest", []string{`p = true { max([1, 2, 3, 4], a[3]) }`}, "true"},
- {"reduce ref dest (2)", []string{`p = true { not max([1, 2, 3, 4, 5], a[3]) }`}, "true"},
- {"sort", []string{`p = x { sort([4, 3, 2, 1], x) }`}, "[1 ,2, 3, 4]"},
- {"sort set", []string{`p = x { sort({4,3,2,1}, x) }`}, "[1,2,3,4]"},
- }
-
- data := loadSmallTestData()
-
- for _, tc := range tests {
- runTopDownTestCase(t, data, tc.note, tc.rules, tc.expected)
- }
-}
-
func TestTopDownArithmetic(t *testing.T) {
tests := []struct {
note string