Skip to content

Commit

Permalink
- Added any/all builtins
Browse files Browse the repository at this point in the history
- Added tests
- Added docs

Signed-off-by: Varun Mathur <varun.mathur@live.com>
  • Loading branch information
vrnmthr authored and tsandall committed Jul 10, 2018
1 parent 1d601cd commit de828cd
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 41 deletions.
32 changes: 32 additions & 0 deletions ast/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ var DefaultBuiltins = [...]*Builtin{
Product,
Max,
Min,
Any,
All,

// Casting
ToNumber,
Expand Down Expand Up @@ -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
*/
Expand Down
2 changes: 2 additions & 0 deletions docs/book/language-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ complex types.
| <span class="opa-keep-it-together">``max(array_or_set, output)``</span> | 1 | ``output`` is the maximum value in ``array_or_set`` |
| <span class="opa-keep-it-together">``min(array_or_set, output)``</span> | 1 | ``output`` is the minimum value in ``array_or_set`` |
| <span class="opa-keep-it-together">``sort(array_or_set, output)``</span> | 1 | ``output`` is the sorted ``array`` containing elements from ``array_or_set``. |
| <span class="opa-keep-it-together">``all(array_or_set, output)``</span> | 1 | ``output`` is ``true`` if all of the values in ``array_or_set`` are ``true``. A collection of length 0 returns ``true``.|
| <span class="opa-keep-it-together">``any(array_or_set, output)``</span> | 1 | ``output`` is ``true`` if any of the values in ``array_or_set`` is ``true``. A collection of length 0 returns ``false``.|

### Sets

Expand Down
52 changes: 52 additions & 0 deletions topdown/aggregates.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,63 @@ 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)
RegisterFunctionalBuiltin1(ast.Product.Name, builtinProduct)
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)
}
92 changes: 92 additions & 0 deletions topdown/aggregates_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
41 changes: 0 additions & 41 deletions topdown/topdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit de828cd

Please sign in to comment.