Add max built-in

Also, refactored aggregate/reduce built-ins along the same lines as arithmetic
built-ins to avoid boilerplate.
This commit is contained in:
Torin Sandall
2016-06-30 14:59:46 -07:00
parent 75c41b702a
commit 878f42b131
4 changed files with 81 additions and 46 deletions
+8 -1
View File
@@ -22,7 +22,7 @@ var DefaultBuiltins = [...]*Builtin{
Equality,
GreaterThan, GreaterThanEq, LessThan, LessThanEq, NotEqual,
Plus, Minus, Multiply, Divide, Round, Abs,
Count, Sum,
Count, Sum, Max,
ToNumber,
}
@@ -145,6 +145,13 @@ var Sum = &Builtin{
TargetPos: []int{1},
}
// Max returns the maximum value in a collection.
var Max = &Builtin{
Name: Var("max"),
NumArgs: 2,
TargetPos: []int{1},
}
/**
* Casting
*/
+68 -43
View File
@@ -8,61 +8,86 @@ import (
"fmt"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/util"
"github.com/pkg/errors"
)
func evalCount(ctx *Context, expr *ast.Expr, iter Iterator) error {
ops := expr.Terms.([]*ast.Term)
src, dst := ops[1].Value, ops[2].Value
s, err := ValueToInterface(src, ctx)
if err != nil {
return errors.Wrapf(err, "count")
type reduceFunc func(x interface{}) (ast.Value, error)
type empty struct{}
func (e empty) Error() string {
return "empty"
}
func evalReduce(f reduceFunc) BuiltinFunc {
return func(ctx *Context, expr *ast.Expr, iter Iterator) error {
ops := expr.Terms.([]*ast.Term)
src, dst := ops[1].Value, ops[2].Value
x, err := ValueToInterface(src, ctx)
if err != nil {
return errors.Wrapf(err, "aggregate")
}
y, err := f(x)
if err != nil {
switch err.(type) {
case empty:
return nil
}
return err
}
switch dst := dst.(type) {
case ast.Var:
ctx = ctx.BindVar(dst, y)
return iter(ctx)
default:
if dst.Equal(y) {
return iter(ctx)
}
return nil
}
}
}
var count ast.Number
func reduceSum(x interface{}) (ast.Value, error) {
if s, ok := x.([]interface{}); ok {
sum := ast.Number(0)
for _, x := range s {
sum += ast.Number(x.(float64))
}
return sum, nil
}
return nil, fmt.Errorf("sum: source must be array")
}
switch s := s.(type) {
func reduceCount(x interface{}) (ast.Value, error) {
switch x := x.(type) {
case []interface{}:
count = ast.Number(len(s))
return ast.Number(len(x)), nil
case map[string]interface{}:
count = ast.Number(len(s))
return ast.Number(len(x)), nil
case string:
return ast.Number(len(x)), nil
default:
return fmt.Errorf("count: source must be a collection: %v", src)
}
switch dst := dst.(type) {
case ast.Var:
ctx = ctx.BindVar(dst, count)
return iter(ctx)
default:
if dst.Equal(count) {
return iter(ctx)
}
return nil
return nil, fmt.Errorf("count: source must be array, object, or string")
}
}
func evalSum(ctx *Context, expr *ast.Expr, iter Iterator) error {
ops := expr.Terms.([]*ast.Term)
src, dst := ops[1].Value, ops[2].Value
s, err := ValueToSlice(src, ctx)
if err != nil {
return errors.Wrapf(err, "sum")
}
sum := ast.Number(0)
for _, x := range s {
sum += ast.Number(x.(float64))
}
switch dst := dst.(type) {
case ast.Var:
ctx = ctx.BindVar(dst, sum)
return iter(ctx)
default:
if dst.Equal(sum) {
return iter(ctx)
func reduceMax(x interface{}) (ast.Value, error) {
switch x := x.(type) {
case []interface{}:
if len(x) == 0 {
return nil, empty{}
}
return nil
var max interface{}
for i := range x {
if util.Compare(max, x[i]) <= 0 {
max = x[i]
}
}
return ast.InterfaceToValue(max)
}
return nil, fmt.Errorf("max: source must be array")
}
+3 -2
View File
@@ -37,8 +37,9 @@ var defaultBuiltinFuncs = map[ast.Var]BuiltinFunc{
ast.Divide.Name: evalArithArity2(arithDivide),
ast.Round.Name: evalArithArity1(arithRound),
ast.Abs.Name: evalArithArity1(arithAbs),
ast.Count.Name: evalCount,
ast.Sum.Name: evalSum,
ast.Count.Name: evalReduce(reduceCount),
ast.Sum.Name: evalReduce(reduceSum),
ast.Max.Name: evalReduce(reduceMax),
ast.ToNumber.Name: evalToNumber,
}
+2
View File
@@ -699,6 +699,8 @@ func TestTopDownAggregates(t *testing.T) {
{"count keys virtual", []string{"p[x] :- count([k | q[k] = _], x)", "q[k] = v :- b[k] = v"}, "[2]"},
{"sum", []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]"},
{"max", []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]"},
}
data := loadSmallTestData()