From f3284cfc7bf4bf39c96a1f676ada690bed8e1f48 Mon Sep 17 00:00:00 2001 From: Stephan Renatus Date: Wed, 14 Jul 2021 10:09:05 +0200 Subject: [PATCH] topdown: memoize partial set/object results (#3492) We can either cache individual elements (`data.foo.p["bar"]`), or the full extent of a partial set/object. A cached full extent of the partial would be used when evaluating individual elements of the partial. If the first encounter with a partial set/object has to materialize the full extent with a variable key, like `data.foo.p[x]`, then we cache the fully-evaluated result for `data.foo.p`. Fixes #822. Co-authored-by: Torin Sandall Signed-off-by: Stephan Renatus --- ast/term.go | 2 +- topdown/eval.go | 103 ++++++++++----- topdown/eval_test.go | 91 ++++++++++++- topdown/topdown_partial_test.go | 4 +- topdown/trace_test.go | 228 ++++++++++++++++---------------- 5 files changed, 280 insertions(+), 148 deletions(-) diff --git a/ast/term.go b/ast/term.go index c470a1a26f..c9bb9f4fc8 100644 --- a/ast/term.go +++ b/ast/term.go @@ -399,7 +399,7 @@ func (term *Term) Hash() int { return term.Value.Hash() } -// IsGround returns true if this terms' Value is ground. +// IsGround returns true if this term's Value is ground. func (term *Term) IsGround() bool { return term.Value.IsGround() } diff --git a/topdown/eval.go b/topdown/eval.go index 762297c276..5c917ee895 100644 --- a/topdown/eval.go +++ b/topdown/eval.go @@ -2066,6 +2066,12 @@ type evalVirtualPartial struct { empty *ast.Term } +type evalVirtualPartialCacheHint struct { + key ast.Ref + hit bool + full bool +} + func (e evalVirtualPartial) eval(iter unifyIterator) error { unknown := e.e.unknown(e.ref[:e.pos+1], e.bindings) @@ -2095,17 +2101,25 @@ func (e evalVirtualPartial) evalEachRule(iter unifyIterator, unknown bool) error return nil } - key, hit, err := e.evalCache(iter) + hint, err := e.evalCache(iter) if err != nil { return err - } else if hit { + } else if hint.hit { return nil } - result := e.empty + if hint.full { + result, err := e.evalAllRulesNoCache(e.ir.Rules) + if err != nil { + return err + } + e.e.virtualCache.Put(hint.key, result) + return e.evalTerm(iter, e.pos+1, result, e.bindings) + } + result := e.empty for _, rule := range e.ir.Rules { - if err := e.evalOneRulePreUnify(iter, rule, key, result, unknown); err != nil { + if err := e.evalOneRulePreUnify(iter, rule, hint, result, unknown); err != nil { return err } } @@ -2115,12 +2129,33 @@ func (e evalVirtualPartial) evalEachRule(iter unifyIterator, unknown bool) error func (e evalVirtualPartial) evalAllRules(iter unifyIterator, rules []*ast.Rule) error { + cacheKey := e.plugged[:e.pos+1] + result := e.e.virtualCache.Get(cacheKey) + if result != nil { + e.e.instr.counterIncr(evalOpVirtualCacheHit) + return e.e.biunify(result, e.rterm, e.bindings, e.rbindings, iter) + } + + e.e.instr.counterIncr(evalOpVirtualCacheMiss) + + result, err := e.evalAllRulesNoCache(rules) + if err != nil { + return err + } + + if cacheKey != nil { + e.e.virtualCache.Put(cacheKey, result) + } + + return e.e.biunify(result, e.rterm, e.bindings, e.rbindings, iter) +} + +func (e evalVirtualPartial) evalAllRulesNoCache(rules []*ast.Rule) (*ast.Term, error) { result := e.empty for _, rule := range rules { child := e.e.child(rule.Body) child.traceEnter(rule) - err := child.eval(func(*eval) error { child.traceExit(rule) var err error @@ -2134,14 +2169,14 @@ func (e evalVirtualPartial) evalAllRules(iter unifyIterator, rules []*ast.Rule) }) if err != nil { - return err + return nil, err } } - return e.e.biunify(result, e.rterm, e.bindings, e.rbindings, iter) + return result, nil } -func (e evalVirtualPartial) evalOneRulePreUnify(iter unifyIterator, rule *ast.Rule, cacheKey ast.Ref, result *ast.Term, unknown bool) error { +func (e evalVirtualPartial) evalOneRulePreUnify(iter unifyIterator, rule *ast.Rule, hint evalVirtualPartialCacheHint, result *ast.Term, unknown bool) error { key := e.ref[e.pos+1] child := e.e.child(rule.Body) @@ -2158,9 +2193,9 @@ func (e evalVirtualPartial) evalOneRulePreUnify(iter unifyIterator, rule *ast.Ru term = rule.Head.Key } - if cacheKey != nil { + if hint.key != nil { result := child.bindings.Plug(term) - e.e.virtualCache.Put(cacheKey, result) + e.e.virtualCache.Put(hint.key, result) } // NOTE(tsandall): if the rule set depends on any unknowns then do @@ -2181,7 +2216,7 @@ func (e evalVirtualPartial) evalOneRulePreUnify(iter unifyIterator, rule *ast.Ru child.traceExit(rule) term, termbindings := child.bindings.apply(term) - err := e.evalTerm(iter, term, termbindings) + err := e.evalTerm(iter, e.pos+2, term, termbindings) if err != nil { return err } @@ -2239,7 +2274,7 @@ func (e evalVirtualPartial) evalOneRuleContinue(iter unifyIterator, rule *ast.Ru } term, termbindings := child.bindings.apply(term) - err := e.evalTerm(iter, term, termbindings) + err := e.evalTerm(iter, e.pos+2, term, termbindings) if err != nil { return err } @@ -2330,11 +2365,11 @@ func (e evalVirtualPartial) partialEvalSupportRule(rule *ast.Rule, path ast.Ref) return defined, err } -func (e evalVirtualPartial) evalTerm(iter unifyIterator, term *ast.Term, termbindings *bindings) error { +func (e evalVirtualPartial) evalTerm(iter unifyIterator, pos int, term *ast.Term, termbindings *bindings) error { eval := evalTerm{ e: e.e, ref: e.ref, - pos: e.pos + 2, + pos: pos, bindings: e.bindings, term: term, termbindings: termbindings, @@ -2344,34 +2379,38 @@ func (e evalVirtualPartial) evalTerm(iter unifyIterator, term *ast.Term, termbin return eval.eval(iter) } -func (e evalVirtualPartial) evalCache(iter unifyIterator) (ast.Ref, bool, error) { +func (e evalVirtualPartial) evalCache(iter unifyIterator) (evalVirtualPartialCacheHint, error) { + + var hint evalVirtualPartialCacheHint if e.e.unknown(e.ref[:e.pos+1], e.bindings) { - return nil, false, nil + return hint, nil } - var cacheKey ast.Ref - - if e.ir.Kind == ast.PartialObjectDoc { - - plugged := e.bindings.Plug(e.ref[e.pos+1]) + if cached := e.e.virtualCache.Get(e.plugged[:e.pos+1]); cached != nil { // have full extent cached + e.e.instr.counterIncr(evalOpVirtualCacheHit) + hint.hit = true + return hint, e.evalTerm(iter, e.pos+1, cached, e.bindings) + } - if plugged.IsGround() { - path := e.plugged[:e.pos+2] - path[len(path)-1] = plugged - cached := e.e.virtualCache.Get(path) + plugged := e.bindings.Plug(e.ref[e.pos+1]) - if cached != nil { - e.e.instr.counterIncr(evalOpVirtualCacheHit) - return nil, true, e.evalTerm(iter, cached, e.bindings) - } + if plugged.IsGround() { + hint.key = append(e.plugged[:e.pos+1], plugged) - e.e.instr.counterIncr(evalOpVirtualCacheMiss) - cacheKey = path + if cached := e.e.virtualCache.Get(hint.key); cached != nil { + e.e.instr.counterIncr(evalOpVirtualCacheHit) + hint.hit = true + return hint, e.evalTerm(iter, e.pos+2, cached, e.bindings) } + } else if _, ok := plugged.Value.(ast.Var); ok { + hint.full = true + hint.key = e.plugged[:e.pos+1] } - return cacheKey, false, nil + e.e.instr.counterIncr(evalOpVirtualCacheMiss) + + return hint, nil } func (e evalVirtualPartial) reduce(head *ast.Head, b *bindings, result *ast.Term) (*ast.Term, bool, error) { diff --git a/topdown/eval_test.go b/topdown/eval_test.go index 63928309bc..70de42e78f 100644 --- a/topdown/eval_test.go +++ b/topdown/eval_test.go @@ -244,7 +244,7 @@ func TestContainsNestedRefOrCall(t *testing.T) { } } -func TestTopdownVirtualCacheFunctions(t *testing.T) { +func TestTopdownVirtualCache(t *testing.T) { ctx := context.Background() store := inmem.New() @@ -316,6 +316,95 @@ func TestTopdownVirtualCacheFunctions(t *testing.T) { hit: 0, miss: 2, }, + { + note: "partial object: simple", + module: `package p + s["foo"] = true { true } + s["bar"] = true { true }`, + query: `data.p.s["foo"]; data.p.s["foo"]`, + hit: 1, + miss: 1, + }, + { + note: "partial set: simple", + module: `package p + s["foo"] { true } + s["bar"] { true }`, + query: `data.p.s["foo"]; data.p.s["foo"]`, + hit: 1, + miss: 1, + }, + { + note: "partial set: object", + module: `package p + s[z] { z := {"foo": "bar"} }`, + query: `x = {"foo": "bar"}; data.p.s[x]; data.p.s[x]`, + hit: 1, + miss: 1, + }, + { + note: "partial set: miss", + module: `package p + s[z] { z = true }`, + query: `data.p.s[true]; not data.p.s[false]`, + hit: 0, + miss: 2, + }, + { + note: "partial set: full extent cached", + module: `package test + p[x] { x = 1 } + p[x] { x = 2 } + `, + query: "data.test.p = x; data.test.p = y", + hit: 1, + miss: 1, + }, + { + note: "partial set: all rules + each rule (non-ground var) cached", + module: `package test + p { data.test.q = x; data.test.q[y] = z; data.test.q[a] = b } + q[x] { x = 1 } + q[x] { x = 2 } + `, + query: "data.test.p = true", + hit: 3, // 'data.test.q[y] = z' + 2x 'data.test.q[a] = b' + miss: 2, // 'data.test.p = true' + 'data.test.q = x' + }, + { + note: "partial set: all rules + each rule (non-ground composite) cached", + module: `package test + p { data.test.q = x; data.test.q[[y, 1]] = z; data.test.q[[a, 2]] = b } + q[[x, x]] { x = 1 } + q[[x, x]] { x = 2 } + `, + query: "data.test.p = true", + hit: 2, // 'data.test.q[[y,1]] = z' + 'data.test.q[[a, 2]] = b' + miss: 2, // 'data.test.p = true' + 'data.test.q = x' + }, + { + note: "partial set: each rule (non-ground var), full extent cached", + module: `package test + p { data.test.q[y] = z; data.test.q = x } + q[x] { x = 1 } + q[x] { x = 2 } + `, + query: "data.test.p = x", + hit: 2, // 2x 'data.test.q = x' + miss: 2, // 'data.test.p = true' + 'data.test.q[y] = z' + }, + { + note: "partial set: each rule (non-ground composite), full extent cached", + module: `package test + p = y { data.test.q[[y, 1]] = z; data.test.q = x } + q[[x, x]] { x = 1 } + q[[x, x]] { x = 2 } + `, + query: "data.test.p = x", + hit: 0, + miss: 3, // 'data.test.p = true' + 'data.test.q[[y, 1]] = z' + 'data.test.q = x' + exp: 1, + }, } for _, tc := range tests { diff --git a/topdown/topdown_partial_test.go b/topdown/topdown_partial_test.go index 5b26f2f119..e3a27b1259 100644 --- a/topdown/topdown_partial_test.go +++ b/topdown/topdown_partial_test.go @@ -2246,13 +2246,13 @@ func TestTopDownPartialEval(t *testing.T) { ` package partial.test - p { not data.partial.__not1_1_3__ } + p { not data.partial.__not1_1_4__ } p { not data.partial.__not1_1_5__ } `, ` package partial - __not1_1_3__ { input[1] = x_term_3_01; x_term_3_01 } + __not1_1_4__ { input[1] = x_term_4_01; x_term_4_01 } __not1_1_5__ { input[2] = x_term_5_01; x_term_5_01 } `, }, diff --git a/topdown/trace_test.go b/topdown/trace_test.go index 67ed29b7c3..e6da4cfd66 100644 --- a/topdown/trace_test.go +++ b/topdown/trace_test.go @@ -87,6 +87,17 @@ func TestPrettyTrace(t *testing.T) { | | Enter data.test.q | | | Eval x = data.a[_] | | | Exit data.test.q +| | Redo data.test.q +| | | Redo x = data.a[_] +| | | Exit data.test.q +| | Redo data.test.q +| | | Redo x = data.a[_] +| | | Exit data.test.q +| | Redo data.test.q +| | | Redo x = data.a[_] +| | | Exit data.test.q +| | Redo data.test.q +| | | Redo x = data.a[_] | | Eval plus(x, 1, n) | | Exit data.test.p | Exit data.test.p = _ @@ -95,32 +106,21 @@ Redo data.test.p = _ | Redo data.test.p | | Redo plus(x, 1, n) | | Redo data.test.q[x] -| | Redo data.test.q -| | | Redo x = data.a[_] -| | | Exit data.test.q | | Eval plus(x, 1, n) | | Exit data.test.p | Redo data.test.p | | Redo plus(x, 1, n) | | Redo data.test.q[x] -| | Redo data.test.q -| | | Redo x = data.a[_] -| | | Exit data.test.q | | Eval plus(x, 1, n) | | Exit data.test.p | Redo data.test.p | | Redo plus(x, 1, n) | | Redo data.test.q[x] -| | Redo data.test.q -| | | Redo x = data.a[_] -| | | Exit data.test.q | | Eval plus(x, 1, n) | | Exit data.test.p | Redo data.test.p | | Redo plus(x, 1, n) | | Redo data.test.q[x] -| | Redo data.test.q -| | | Redo x = data.a[_] ` a := strings.Split(expected, "\n") @@ -180,6 +180,17 @@ query:3 | | Index data.test.q (matched 1 rule) query:4 | | Enter data.test.q query:4 | | | Eval x = data.a[_] query:4 | | | Exit data.test.q +query:4 | | Redo data.test.q +query:4 | | | Redo x = data.a[_] +query:4 | | | Exit data.test.q +query:4 | | Redo data.test.q +query:4 | | | Redo x = data.a[_] +query:4 | | | Exit data.test.q +query:4 | | Redo data.test.q +query:4 | | | Redo x = data.a[_] +query:4 | | | Exit data.test.q +query:4 | | Redo data.test.q +query:4 | | | Redo x = data.a[_] query:3 | | Eval plus(x, 1, n) query:3 | | Exit data.test.p query:1 | Exit data.test.p = _ @@ -188,32 +199,21 @@ query:1 | Redo data.test.p = _ query:3 | Redo data.test.p query:3 | | Redo plus(x, 1, n) query:3 | | Redo data.test.q[x] -query:4 | | Redo data.test.q -query:4 | | | Redo x = data.a[_] -query:4 | | | Exit data.test.q query:3 | | Eval plus(x, 1, n) query:3 | | Exit data.test.p query:3 | Redo data.test.p query:3 | | Redo plus(x, 1, n) query:3 | | Redo data.test.q[x] -query:4 | | Redo data.test.q -query:4 | | | Redo x = data.a[_] -query:4 | | | Exit data.test.q query:3 | | Eval plus(x, 1, n) query:3 | | Exit data.test.p query:3 | Redo data.test.p query:3 | | Redo plus(x, 1, n) query:3 | | Redo data.test.q[x] -query:4 | | Redo data.test.q -query:4 | | | Redo x = data.a[_] -query:4 | | | Exit data.test.q query:3 | | Eval plus(x, 1, n) query:3 | | Exit data.test.p query:3 | Redo data.test.p query:3 | | Redo plus(x, 1, n) query:3 | | Redo data.test.q[x] -query:4 | | Redo data.test.q -query:4 | | | Redo x = data.a[_] ` a := strings.Split(expected, "\n") @@ -243,15 +243,13 @@ func TestPrettyTraceWithLocationTruncatedPaths(t *testing.T) { ctx := context.Background() compiler := ast.MustCompileModules(map[string]string{ - "authz_bundle/com/foo/bar/baz/qux/acme/corp/internal/authz/policies/abac/v1/beta/policy.rego": ` - package test + "authz_bundle/com/foo/bar/baz/qux/acme/corp/internal/authz/policies/abac/v1/beta/policy.rego": `package test import data.utils.q p = true { q[x]; plus(x, 1, n) } `, - "authz_bundle/com/foo/bar/baz/qux/acme/corp/internal/authz/policies/utils/utils.rego": ` - package utils + "authz_bundle/com/foo/bar/baz/qux/acme/corp/internal/authz/policies/utils/utils.rego": `package utils q[x] { x = data.a[_] } `, @@ -276,46 +274,46 @@ func TestPrettyTraceWithLocationTruncatedPaths(t *testing.T) { expected := `query:1 Enter data.test.p = _ query:1 | Eval data.test.p = _ query:1 | Index data.test.p (matched 1 rule) -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | Enter data.test.p -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Eval data.utils.q[x] -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Index data.utils.q (matched 1 rule) -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | Enter data.utils.q -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | | Eval x = data.a[_] -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | | Exit data.utils.q -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Eval plus(x, 1, n) -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Exit data.test.p +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | Enter data.test.p +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Eval data.utils.q[x] +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Index data.utils.q (matched 1 rule) +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | Enter data.utils.q +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | | Eval x = data.a[_] +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | | Exit data.utils.q +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | Redo data.utils.q +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | | Redo x = data.a[_] +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | | Exit data.utils.q +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | Redo data.utils.q +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | | Redo x = data.a[_] +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | | Exit data.utils.q +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | Redo data.utils.q +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | | Redo x = data.a[_] +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | | Exit data.utils.q +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | Redo data.utils.q +authz_bundle/...ternal/authz/policies/utils/utils.rego:3 | | | Redo x = data.a[_] +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Eval plus(x, 1, n) +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Exit data.test.p query:1 | Exit data.test.p = _ query:1 Redo data.test.p = _ query:1 | Redo data.test.p = _ -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | Redo data.test.p -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Redo plus(x, 1, n) -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Redo data.utils.q[x] -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | Redo data.utils.q -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | | Redo x = data.a[_] -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | | Exit data.utils.q -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Eval plus(x, 1, n) -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Exit data.test.p -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | Redo data.test.p -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Redo plus(x, 1, n) -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Redo data.utils.q[x] -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | Redo data.utils.q -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | | Redo x = data.a[_] -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | | Exit data.utils.q -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Eval plus(x, 1, n) -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Exit data.test.p -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | Redo data.test.p -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Redo plus(x, 1, n) -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Redo data.utils.q[x] -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | Redo data.utils.q -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | | Redo x = data.a[_] -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | | Exit data.utils.q -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Eval plus(x, 1, n) -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Exit data.test.p -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | Redo data.test.p -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Redo plus(x, 1, n) -authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:6 | | Redo data.utils.q[x] -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | Redo data.utils.q -authz_bundle/...ternal/authz/policies/utils/utils.rego:4 | | | Redo x = data.a[_] +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | Redo data.test.p +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Redo plus(x, 1, n) +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Redo data.utils.q[x] +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Eval plus(x, 1, n) +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Exit data.test.p +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | Redo data.test.p +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Redo plus(x, 1, n) +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Redo data.utils.q[x] +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Eval plus(x, 1, n) +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Exit data.test.p +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | Redo data.test.p +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Redo plus(x, 1, n) +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Redo data.utils.q[x] +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Eval plus(x, 1, n) +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Exit data.test.p +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | Redo data.test.p +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Redo plus(x, 1, n) +authz_bundle/...ternal/authz/policies/abac/v1/beta/policy.rego:5 | | Redo data.utils.q[x] ` a := strings.Split(expected, "\n") @@ -530,6 +528,12 @@ query:1 | Fail data } func TestTraceDuplicate(t *testing.T) { + // NOTE(sr): We're explicitly bypassing a caching optimization here: + // When the first query for a partial is `p[x]`, and `x` is not ground, + // we'll have the evaluation eval the full extent of the partial and + // cache that. Thus the second `p[1]` here will not trigger a duplicate + // event, because the query eval uses a different code path. + // Having `p[1]` queried first will side-step the caching optimization. module := `package test p[1] @@ -545,7 +549,7 @@ func TestTraceDuplicate(t *testing.T) { defer store.Abort(ctx, txn) tracer := NewBufferTracer() - query := NewQuery(ast.MustParseBody("data.test.p[x] = _")). + query := NewQuery(ast.MustParseBody("data.test.p[1]; data.test.p[x] = _")). WithCompiler(compiler). WithStore(store). WithTransaction(txn). @@ -571,7 +575,7 @@ func TestTraceDuplicate(t *testing.T) { func TestTraceNote(t *testing.T) { module := `package test - p = true { q[x]; plus(x, 1, n); trace(sprintf("n= %v", [n])) } + p = true { q[x]; plus(x, 1, n); trace(sprintf("n=%v", [n])) } q[x] { x = data.a[_] }` ctx := context.Background() @@ -602,60 +606,60 @@ func TestTraceNote(t *testing.T) { | | Enter data.test.q | | | Eval x = data.a[_] | | | Exit data.test.q +| | Redo data.test.q +| | | Redo x = data.a[_] +| | | Exit data.test.q +| | Redo data.test.q +| | | Redo x = data.a[_] +| | | Exit data.test.q +| | Redo data.test.q +| | | Redo x = data.a[_] +| | | Exit data.test.q +| | Redo data.test.q +| | | Redo x = data.a[_] | | Eval plus(x, 1, n) -| | Eval sprintf("n= %v", [n], __local0__) +| | Eval sprintf("n=%v", [n], __local0__) | | Eval trace(__local0__) -| | Note "n= 2" +| | Note "n=2" | | Exit data.test.p | Exit data.test.p = _ Redo data.test.p = _ | Redo data.test.p = _ | Redo data.test.p | | Redo trace(__local0__) -| | Redo sprintf("n= %v", [n], __local0__) +| | Redo sprintf("n=%v", [n], __local0__) | | Redo plus(x, 1, n) | | Redo data.test.q[x] -| | Redo data.test.q -| | | Redo x = data.a[_] -| | | Exit data.test.q | | Eval plus(x, 1, n) -| | Eval sprintf("n= %v", [n], __local0__) +| | Eval sprintf("n=%v", [n], __local0__) | | Eval trace(__local0__) -| | Note "n= 3" +| | Note "n=3" | | Exit data.test.p | Redo data.test.p | | Redo trace(__local0__) -| | Redo sprintf("n= %v", [n], __local0__) +| | Redo sprintf("n=%v", [n], __local0__) | | Redo plus(x, 1, n) | | Redo data.test.q[x] -| | Redo data.test.q -| | | Redo x = data.a[_] -| | | Exit data.test.q | | Eval plus(x, 1, n) -| | Eval sprintf("n= %v", [n], __local0__) +| | Eval sprintf("n=%v", [n], __local0__) | | Eval trace(__local0__) -| | Note "n= 4" +| | Note "n=4" | | Exit data.test.p | Redo data.test.p | | Redo trace(__local0__) -| | Redo sprintf("n= %v", [n], __local0__) +| | Redo sprintf("n=%v", [n], __local0__) | | Redo plus(x, 1, n) | | Redo data.test.q[x] -| | Redo data.test.q -| | | Redo x = data.a[_] -| | | Exit data.test.q | | Eval plus(x, 1, n) -| | Eval sprintf("n= %v", [n], __local0__) +| | Eval sprintf("n=%v", [n], __local0__) | | Eval trace(__local0__) -| | Note "n= 5" +| | Note "n=5" | | Exit data.test.p | Redo data.test.p | | Redo trace(__local0__) -| | Redo sprintf("n= %v", [n], __local0__) +| | Redo sprintf("n=%v", [n], __local0__) | | Redo plus(x, 1, n) | | Redo data.test.q[x] -| | Redo data.test.q -| | | Redo x = data.a[_] ` a := strings.Split(expected, "\n") @@ -684,7 +688,7 @@ Redo data.test.p = _ func TestTraceNoteWithLocation(t *testing.T) { module := `package test - p = true { q[x]; plus(x, 1, n); trace(sprintf("n= %v", [n])) } + p = true { q[x]; plus(x, 1, n); trace(sprintf("n=%v", [n])) } q[x] { x = data.a[_] }` ctx := context.Background() @@ -715,60 +719,60 @@ query:3 | | Index data.test.q (matched 1 rule) query:4 | | Enter data.test.q query:4 | | | Eval x = data.a[_] query:4 | | | Exit data.test.q +query:4 | | Redo data.test.q +query:4 | | | Redo x = data.a[_] +query:4 | | | Exit data.test.q +query:4 | | Redo data.test.q +query:4 | | | Redo x = data.a[_] +query:4 | | | Exit data.test.q +query:4 | | Redo data.test.q +query:4 | | | Redo x = data.a[_] +query:4 | | | Exit data.test.q +query:4 | | Redo data.test.q +query:4 | | | Redo x = data.a[_] query:3 | | Eval plus(x, 1, n) -query:3 | | Eval sprintf("n= %v", [n], __local0__) +query:3 | | Eval sprintf("n=%v", [n], __local0__) query:3 | | Eval trace(__local0__) -query:3 | | Note "n= 2" +query:3 | | Note "n=2" query:3 | | Exit data.test.p query:1 | Exit data.test.p = _ query:1 Redo data.test.p = _ query:1 | Redo data.test.p = _ query:3 | Redo data.test.p query:3 | | Redo trace(__local0__) -query:3 | | Redo sprintf("n= %v", [n], __local0__) +query:3 | | Redo sprintf("n=%v", [n], __local0__) query:3 | | Redo plus(x, 1, n) query:3 | | Redo data.test.q[x] -query:4 | | Redo data.test.q -query:4 | | | Redo x = data.a[_] -query:4 | | | Exit data.test.q query:3 | | Eval plus(x, 1, n) -query:3 | | Eval sprintf("n= %v", [n], __local0__) +query:3 | | Eval sprintf("n=%v", [n], __local0__) query:3 | | Eval trace(__local0__) -query:3 | | Note "n= 3" +query:3 | | Note "n=3" query:3 | | Exit data.test.p query:3 | Redo data.test.p query:3 | | Redo trace(__local0__) -query:3 | | Redo sprintf("n= %v", [n], __local0__) +query:3 | | Redo sprintf("n=%v", [n], __local0__) query:3 | | Redo plus(x, 1, n) query:3 | | Redo data.test.q[x] -query:4 | | Redo data.test.q -query:4 | | | Redo x = data.a[_] -query:4 | | | Exit data.test.q query:3 | | Eval plus(x, 1, n) -query:3 | | Eval sprintf("n= %v", [n], __local0__) +query:3 | | Eval sprintf("n=%v", [n], __local0__) query:3 | | Eval trace(__local0__) -query:3 | | Note "n= 4" +query:3 | | Note "n=4" query:3 | | Exit data.test.p query:3 | Redo data.test.p query:3 | | Redo trace(__local0__) -query:3 | | Redo sprintf("n= %v", [n], __local0__) +query:3 | | Redo sprintf("n=%v", [n], __local0__) query:3 | | Redo plus(x, 1, n) query:3 | | Redo data.test.q[x] -query:4 | | Redo data.test.q -query:4 | | | Redo x = data.a[_] -query:4 | | | Exit data.test.q query:3 | | Eval plus(x, 1, n) -query:3 | | Eval sprintf("n= %v", [n], __local0__) +query:3 | | Eval sprintf("n=%v", [n], __local0__) query:3 | | Eval trace(__local0__) -query:3 | | Note "n= 5" +query:3 | | Note "n=5" query:3 | | Exit data.test.p query:3 | Redo data.test.p query:3 | | Redo trace(__local0__) -query:3 | | Redo sprintf("n= %v", [n], __local0__) +query:3 | | Redo sprintf("n=%v", [n], __local0__) query:3 | | Redo plus(x, 1, n) query:3 | | Redo data.test.q[x] -query:4 | | Redo data.test.q -query:4 | | | Redo x = data.a[_] ` a := strings.Split(expected, "\n")