Rename storage.Bindings to ast.ValueMap

This commit is contained in:
Torin Sandall
2016-10-05 09:29:49 -07:00
parent 19afe35b84
commit 4ef25a6ad5
14 changed files with 227 additions and 133 deletions
+89
View File
@@ -0,0 +1,89 @@
// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package ast
import (
"github.com/open-policy-agent/opa/util"
)
// ValueMap represents a key/value map between AST term values. Any type of term
// can be used as a key in the map.
type ValueMap struct {
hashMap *util.HashMap
}
// NewValueMap returns a new ValueMap.
func NewValueMap() *ValueMap {
vs := &ValueMap{
hashMap: util.NewHashMap(valueEq, valueHash),
}
return vs
}
// Copy returns a shallow copy of the ValueMap.
func (vs *ValueMap) Copy() *ValueMap {
cpy := NewValueMap()
cpy.hashMap = vs.hashMap.Copy()
return cpy
}
// Equal returns true if this ValueMap equals the other.
func (vs *ValueMap) Equal(other *ValueMap) bool {
return vs.hashMap.Equal(other.hashMap)
}
// Get returns the value in the map for k.
func (vs *ValueMap) Get(k Value) Value {
if v, ok := vs.hashMap.Get(k); ok {
return v.(Value)
}
return nil
}
// Hash returns a hash code for this ValueMap.
func (vs *ValueMap) Hash() int {
return vs.hashMap.Hash()
}
// Iter calls the iter function for each key/value pair in the map. If the iter
// function returns true, iteration stops.
func (vs *ValueMap) Iter(iter func(Value, Value) bool) bool {
return vs.hashMap.Iter(func(kt, vt util.T) bool {
k := kt.(Value)
v := vt.(Value)
return iter(k, v)
})
}
// Put inserts a key k into the map with value v.
func (vs *ValueMap) Put(k, v Value) {
vs.hashMap.Put(k, v)
}
// Delete removes a key k from the map.
func (vs *ValueMap) Delete(k Value) {
vs.hashMap.Delete(k)
}
// Update returns a new ValueMap that contains the union of this ValueMap and
// the other. Overlapping keys are replaced with values from the other.
func (vs *ValueMap) Update(other *ValueMap) *ValueMap {
new := vs.hashMap.Update(other.hashMap)
return &ValueMap{new}
}
func (vs *ValueMap) String() string {
return vs.hashMap.String()
}
func valueHash(v util.T) int {
return v.(Value).Hash()
}
func valueEq(a, b util.T) bool {
av := a.(Value)
bv := b.(Value)
return av.Equal(bv)
}
+97
View File
@@ -0,0 +1,97 @@
// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package ast
import (
"reflect"
"sort"
"testing"
)
func TestValueMapOverwrite(t *testing.T) {
a := NewValueMap()
b := NewValueMap()
a.Put(String("x"), String("foo"))
a.Put(String("x"), String("bar"))
if a.Get(String("x")) != String("bar") {
t.Fatalf("Expected a['x'] = 'bar' but got: %v", a.Get(String("x")))
}
a.Put(String("z"), String("corge"))
b.Put(String("y"), String("baz"))
b.Put(String("x"), String("qux"))
c := a.Update(b)
if c.Get(String("x")) != String("qux") {
t.Fatalf("Expected c['x'] = 'qux' but got: %v", c.Get(String("x")))
}
}
func TestValueMapIter(t *testing.T) {
a := NewValueMap()
a.Put(String("x"), String("foo"))
a.Put(String("y"), String("bar"))
a.Put(String("z"), String("baz"))
values := []string{}
a.Iter(func(k, v Value) bool {
values = append(values, string(v.(String)))
return false
})
sort.Strings(values)
expected := []string{"bar", "baz", "foo"}
if !reflect.DeepEqual(values, expected) {
t.Fatalf("Unexpected value from iteration: %v", values)
}
}
func TestValueMapCopy(t *testing.T) {
a := NewValueMap()
a.Put(String("x"), String("foo"))
a.Put(String("y"), String("bar"))
b := a.Copy()
b.Delete(String("y"))
if a.Get(String("y")) != String("bar") {
t.Fatalf("Unexpected a['y'] value: %v", a.Get(String("y")))
}
}
func TestValueMapEqual(t *testing.T) {
a := NewValueMap()
a.Put(String("x"), String("foo"))
a.Put(String("y"), String("bar"))
b := a.Copy()
if !a.Equal(b) {
t.Fatalf("Expected a == b but not for: %v / %v", a, b)
}
if a.Hash() != b.Hash() {
t.Fatalf("Expected a.Hash() == b.Hash() but not for: %v / %v", a, b)
}
a.Delete(String("x"))
if a.Equal(b) {
t.Fatalf("Expected a != b but not for: %v / %v", a, b)
}
}
func TestValueMapGetMissing(t *testing.T) {
a := NewValueMap()
a.Put(String("x"), String("foo"))
a.Put(String("y"), String("bar"))
if a.Get(String("z")) != nil {
t.Fatalf("Expected a['z'] = nil but got: %v", a.Get(String("z")))
}
}
func TestValueMapString(t *testing.T) {
a := NewValueMap()
a.Put(MustParseRef("a.b.c[x]"), String("foo"))
a.Put(Var("x"), Number(1))
expected := `{a.b.c[x]: "foo", x: 1}`
result := a.String()
if expected != result {
t.Fatalf("Expected %v but got: %v", expected, result)
}
}
+2 -2
View File
@@ -722,8 +722,8 @@ func getPretty(p []string) bool {
return false
}
func parseGlobals(g []string) (*storage.Bindings, error) {
globals := storage.NewBindings()
func parseGlobals(g []string) (*ast.ValueMap, error) {
globals := ast.NewValueMap()
for _, g := range g {
vs := strings.SplitN(g, ":", 2)
k, err := ast.ParseTerm(vs[0])
+1 -1
View File
@@ -525,7 +525,7 @@ func TestGlobalParsing(t *testing.T) {
t.Errorf("%v (#%d): Unexpected error: %v", tc.note, i+1, err)
continue
}
exp := storage.NewBindings()
exp := ast.NewValueMap()
for _, i := range ast.MustParseTerm(e).Value.(ast.Object) {
exp.Put(i[0].Value, i[1].Value)
}
-93
View File
@@ -1,93 +0,0 @@
// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package storage
import (
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/util"
)
// Bindings represents a mapping between key/value pairs.
// The key/value pairs are AST values contained in expressions.
// Insertion of a key/value pair represents unification of the
// two values.
//
// TODO(tsandall): rename to ValueMap and move into ast package.
type Bindings struct {
hashMap *util.HashMap
}
// NewBindings returns a new empty set of bindings.
func NewBindings() *Bindings {
b := &Bindings{
hashMap: util.NewHashMap(bindingsEq, bindingsHash),
}
return b
}
// Copy returns a shallow copy of these bindings.
func (b *Bindings) Copy() *Bindings {
cpy := NewBindings()
cpy.hashMap = b.hashMap.Copy()
return cpy
}
// Equal returns true if these bindings equal the other bindings.
// Two bindings are equal if they contain the same key/value pairs.
func (b *Bindings) Equal(other *Bindings) bool {
return b.hashMap.Equal(other.hashMap)
}
// Get returns the binding for the given key.
func (b *Bindings) Get(k ast.Value) ast.Value {
if v, ok := b.hashMap.Get(k); ok {
return v.(ast.Value)
}
return nil
}
// Hash returns the hash code for the bindings.
func (b *Bindings) Hash() int {
return b.hashMap.Hash()
}
// Iter iterates the bindings and calls the "iter" function for each key/value pair.
func (b *Bindings) Iter(iter func(ast.Value, ast.Value) bool) bool {
return b.hashMap.Iter(func(kt, vt util.T) bool {
k := kt.(ast.Value)
v := vt.(ast.Value)
return iter(k, v)
})
}
// Put inserts a key/value pair.
func (b *Bindings) Put(k, v ast.Value) {
b.hashMap.Put(k, v)
}
// Delete removes a key/value pair.
func (b *Bindings) Delete(k ast.Value) {
b.hashMap.Delete(k)
}
// Update returns new bindings that are the union of these bindings and the other bindings.
func (b *Bindings) Update(other *Bindings) *Bindings {
new := b.hashMap.Update(other.hashMap)
return &Bindings{new}
}
func (b *Bindings) String() string {
return b.hashMap.String()
}
func bindingsHash(v util.T) int {
return v.(ast.Value).Hash()
}
func bindingsEq(a, b util.T) bool {
av := a.(ast.Value)
bv := b.(ast.Value)
return av.Equal(bv)
}
+9 -9
View File
@@ -53,7 +53,7 @@ func newIndices() *indices {
func (ind *indices) Build(store Store, txn Transaction, ref ast.Ref) error {
index := newBindingIndex()
ind.registerTriggers(store)
err := iterStorage(store, txn, ref, ast.EmptyRef(), NewBindings(), func(bindings *Bindings, val interface{}) {
err := iterStorage(store, txn, ref, ast.EmptyRef(), ast.NewValueMap(), func(bindings *ast.ValueMap, val interface{}) {
index.Add(val, bindings)
})
if err != nil {
@@ -163,7 +163,7 @@ func newBindingIndex() *bindingIndex {
// Add updates the index to include new bindings for the value.
// If the bindings already exist for the value, no change is made.
func (ind *bindingIndex) Add(val interface{}, bindings *Bindings) {
func (ind *bindingIndex) Add(val interface{}, bindings *ast.ValueMap) {
node := ind.getNode(val)
if node != nil {
@@ -185,7 +185,7 @@ func (ind *bindingIndex) Add(val interface{}, bindings *Bindings) {
}
// Iter calls the iter function for each set of bindings for the value.
func (ind *bindingIndex) Iter(val interface{}, iter func(*Bindings) error) error {
func (ind *bindingIndex) Iter(val interface{}, iter func(*ast.ValueMap) error) error {
node := ind.getNode(val)
if node == nil {
return nil
@@ -219,7 +219,7 @@ func (ind *bindingIndex) String() string {
}
type bindingSetNode struct {
val *Bindings
val *ast.ValueMap
next *bindingSetNode
}
@@ -233,7 +233,7 @@ func newBindingSet() *bindingSet {
}
}
func (set *bindingSet) Add(val *Bindings) {
func (set *bindingSet) Add(val *ast.ValueMap) {
node := set.getNode(val)
if node != nil {
return
@@ -243,7 +243,7 @@ func (set *bindingSet) Add(val *Bindings) {
set.table[hashCode] = &bindingSetNode{val, head}
}
func (set *bindingSet) Iter(iter func(*Bindings) error) error {
func (set *bindingSet) Iter(iter func(*ast.ValueMap) error) error {
for _, head := range set.table {
for entry := head; entry != nil; entry = entry.next {
if err := iter(entry.val); err != nil {
@@ -256,14 +256,14 @@ func (set *bindingSet) Iter(iter func(*Bindings) error) error {
func (set *bindingSet) String() string {
buf := []string{}
set.Iter(func(bindings *Bindings) error {
set.Iter(func(bindings *ast.ValueMap) error {
buf = append(buf, bindings.String())
return nil
})
return "{" + strings.Join(buf, ", ") + "}"
}
func (set *bindingSet) getNode(val *Bindings) *bindingSetNode {
func (set *bindingSet) getNode(val *ast.ValueMap) *bindingSetNode {
hashCode := val.Hash()
for entry := set.table[hashCode]; entry != nil; entry = entry.next {
if entry.val.Equal(val) {
@@ -304,7 +304,7 @@ func hash(v interface{}) int {
panic(fmt.Sprintf("illegal argument: %v (%T)", v, v))
}
func iterStorage(store Store, txn Transaction, ref ast.Ref, path ast.Ref, bindings *Bindings, iter func(*Bindings, interface{})) error {
func iterStorage(store Store, txn Transaction, ref ast.Ref, path ast.Ref, bindings *ast.ValueMap, iter func(*ast.ValueMap, interface{})) error {
if len(ref) == 0 {
node, err := store.Read(txn, path)
+4 -4
View File
@@ -105,7 +105,7 @@ func assertBindingsEqual(t *testing.T, note string, index *bindingIndex, value i
expected := loadExpectedBindings(expectedStr)
err := index.Iter(value, func(bindings *Bindings) error {
err := index.Iter(value, func(bindings *ast.ValueMap) error {
for j := range expected {
if expected[j].Equal(bindings) {
tmp := expected[:j]
@@ -127,14 +127,14 @@ func assertBindingsEqual(t *testing.T, note string, index *bindingIndex, value i
}
}
func loadExpectedBindings(input string) []*Bindings {
func loadExpectedBindings(input string) []*ast.ValueMap {
var data []map[string]interface{}
if err := json.Unmarshal([]byte(input), &data); err != nil {
panic(err)
}
var expected []*Bindings
var expected []*ast.ValueMap
for _, bindings := range data {
buf := NewBindings()
buf := ast.NewValueMap()
for k, v := range bindings {
switch v := v.(type) {
case string:
+1 -1
View File
@@ -303,7 +303,7 @@ func (s *Storage) IndexExists(ref ast.Ref) bool {
// Index invokes the iterator with bindings for each variable in the reference
// that if plugged into the reference, would locate a document with a matching
// value.
func (s *Storage) Index(txn Transaction, ref ast.Ref, value interface{}, iter func(*Bindings) error) error {
func (s *Storage) Index(txn Transaction, ref ast.Ref, value interface{}, iter func(*ast.ValueMap) error) error {
idx := s.indices.Get(ref)
if idx == nil {
+1 -1
View File
@@ -65,7 +65,7 @@ func setupBenchmark(nodes int, pods int) *topdown.QueryParams {
store := storage.New(storage.InMemoryConfig())
// parameter setup
globals := storage.NewBindings()
globals := ast.NewValueMap()
req := ast.MustParseTerm(requestedPod).Value
globals.Put(ast.Var("requested_pod"), req)
path := []interface{}{"opa", "test", "scheduler", "fit"}
+1 -1
View File
@@ -54,7 +54,7 @@ func setup(t *testing.T, filename string) *topdown.QueryParams {
})
// parameter setup
globals := storage.NewBindings()
globals := ast.NewValueMap()
req := ast.MustParseTerm(requestedPod).Value
globals.Put(ast.Var("requested_pod"), req)
path := []interface{}{"opa", "test", "scheduler", "fit"}
+1 -1
View File
@@ -104,7 +104,7 @@ func ExampleQuery() {
// Prepare the query parameters. Queries execute against the policy engine's storage and can
// accept additional documents (which are referred to as "globals"). In this case we have no
// additional documents.
globals := storage.NewBindings()
globals := ast.NewValueMap()
params := topdown.NewQueryParams(compiler, store, txn, globals, []interface{}{"opa", "example", "p"})
// Execute the query against "p".
+15 -15
View File
@@ -17,8 +17,8 @@ import (
type Context struct {
Query ast.Body
Compiler *ast.Compiler
Globals *storage.Bindings
Locals *storage.Bindings
Globals *ast.ValueMap
Locals *ast.ValueMap
Index int
Previous *Context
Store *storage.Storage
@@ -37,8 +37,8 @@ func NewContext(query ast.Body, compiler *ast.Compiler, store *storage.Storage,
return &Context{
Query: query,
Compiler: compiler,
Globals: storage.NewBindings(),
Locals: storage.NewBindings(),
Globals: ast.NewValueMap(),
Locals: ast.NewValueMap(),
Store: store,
txn: txn,
cache: newContextCache(),
@@ -85,7 +85,7 @@ func (ctx *Context) Unbind(undo *Undo) {
}
// Child returns a new context to evaluate a query that was referenced by this context.
func (ctx *Context) Child(query ast.Body, locals *storage.Bindings) *Context {
func (ctx *Context) Child(query ast.Body, locals *ast.ValueMap) *Context {
cpy := *ctx
cpy.Query = query
cpy.Locals = locals
@@ -352,13 +352,13 @@ type QueryParams struct {
Compiler *ast.Compiler
Store *storage.Storage
Transaction storage.Transaction
Globals *storage.Bindings
Globals *ast.ValueMap
Tracer Tracer
Path []interface{}
}
// NewQueryParams returns a new QueryParams.
func NewQueryParams(compiler *ast.Compiler, store *storage.Storage, txn storage.Transaction, globals *storage.Bindings, path []interface{}) *QueryParams {
func NewQueryParams(compiler *ast.Compiler, store *storage.Storage, txn storage.Transaction, globals *ast.ValueMap, path []interface{}) *QueryParams {
return &QueryParams{
Compiler: compiler,
Store: store,
@@ -374,7 +374,7 @@ func (q *QueryParams) NewContext(body ast.Body) *Context {
Query: body,
Compiler: q.Compiler,
Globals: q.Globals,
Locals: storage.NewBindings(),
Locals: ast.NewValueMap(),
Store: q.Store,
Tracer: q.Tracer,
txn: q.Transaction,
@@ -991,7 +991,7 @@ func evalRefRuleCompleteDoc(ctx *Context, ref ast.Ref, suffix ast.Ref, rules []*
for _, rule := range rules {
bindings := storage.NewBindings()
bindings := ast.NewValueMap()
child := ctx.Child(rule.Body, bindings)
err := Eval(child, func(child *Context) error {
@@ -1039,7 +1039,7 @@ func evalRefRulePartialObjectDoc(ctx *Context, ref ast.Ref, path ast.Ref, rule *
// a recursive binding if we unified "key" with "rule.Key.Value". If unification
// is improved to handle namespacing, this can be revisited.
if !key.IsGround() {
child := ctx.Child(rule.Body, storage.NewBindings())
child := ctx.Child(rule.Body, ast.NewValueMap())
return Eval(child, func(child *Context) error {
key := PlugValue(rule.Key.Value, child)
@@ -1093,7 +1093,7 @@ func evalRefRulePartialObjectDoc(ctx *Context, ref ast.Ref, path ast.Ref, rule *
}
}
child := ctx.Child(rule.Body, storage.NewBindings())
child := ctx.Child(rule.Body, ast.NewValueMap())
_, err := evalEqUnify(child, key, rule.Key.Value, nil, func(child *Context) error {
return Eval(child, func(child *Context) error {
value := PlugValue(rule.Value.Value, child)
@@ -1143,7 +1143,7 @@ func evalRefRulePartialObjectDocFull(ctx *Context, ref ast.Ref, rules []*ast.Rul
for _, rule := range rules {
bindings := storage.NewBindings()
bindings := ast.NewValueMap()
child := ctx.Child(rule.Body, bindings)
err := Eval(child, func(child *Context) error {
@@ -1188,7 +1188,7 @@ func evalRefRulePartialSetDoc(ctx *Context, ref ast.Ref, path ast.Ref, rule *ast
// See comment in evalRefRulePartialObjectDoc about the two branches below.
if !key.IsGround() {
child := ctx.Child(rule.Body, storage.NewBindings())
child := ctx.Child(rule.Body, ast.NewValueMap())
// TODO(tsandall): Currently this evaluates the child query without any
// bindings from the current context. In cases where the key is partially
@@ -1214,7 +1214,7 @@ func evalRefRulePartialSetDoc(ctx *Context, ref ast.Ref, path ast.Ref, rule *ast
})
}
child := ctx.Child(rule.Body, storage.NewBindings())
child := ctx.Child(rule.Body, ast.NewValueMap())
_, err := evalEqUnify(child, key, rule.Key.Value, nil, func(child *Context) error {
return Eval(child, func(child *Context) error {
@@ -1412,7 +1412,7 @@ func evalTermsIndexed(ctx *Context, iter Iterator, indexed ast.Ref, nonIndexed *
// Iterate the bindings for the indexed term that when applied to the reference
// would locate the non-indexed value obtained above.
return ctx.Store.Index(ctx.txn, indexed, nonIndexedValue, func(bindings *storage.Bindings) error {
return ctx.Store.Index(ctx.txn, indexed, nonIndexedValue, func(bindings *ast.ValueMap) error {
var prev *Undo
bindings.Iter(func(k, v ast.Value) bool {
prev = ctx.Bind(k, v, prev)
+4 -4
View File
@@ -1251,14 +1251,14 @@ func compileRules(imports []string, input []string) *ast.Compiler {
return c
}
func loadExpectedBindings(input string) []*storage.Bindings {
func loadExpectedBindings(input string) []*ast.ValueMap {
var data []map[string]interface{}
if err := json.Unmarshal([]byte(input), &data); err != nil {
panic(err)
}
var expected []*storage.Bindings
var expected []*ast.ValueMap
for _, bindings := range data {
buf := storage.NewBindings()
buf := ast.NewValueMap()
for k, v := range bindings {
switch v := v.(type) {
case string:
@@ -1380,7 +1380,7 @@ func runTopDownTestCase(t *testing.T, data map[string]interface{}, note string,
func assertTopDown(t *testing.T, compiler *ast.Compiler, store *storage.Storage, note string, path []string, globals string, expected interface{}) {
g := storage.NewBindings()
g := ast.NewValueMap()
for _, i := range ast.MustParseTerm(globals).Value.(ast.Object) {
g.Put(i[0].Value, i[1].Value)
}
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"testing"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/util"
)
@@ -35,7 +36,7 @@ func TestTracer(t *testing.T) {
tracer := &mockTracer{[]string{}}
params := NewQueryParams(compiler, store, txn, storage.NewBindings(), []interface{}{"p"})
params := NewQueryParams(compiler, store, txn, ast.NewValueMap(), []interface{}{"p"})
params.Tracer = tracer
result, err := Query(params)