mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
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:
+8
-1
@@ -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
@@ -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
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user