Skip to content

Commit

Permalink
ContextEval support for Unknowns (#1126)
Browse files Browse the repository at this point in the history
* Fix for ContextEval with unknown attributes
  • Loading branch information
TristonianJones authored Feb 10, 2025
1 parent c053251 commit 9a4b48b
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 10 deletions.
42 changes: 42 additions & 0 deletions cel/cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,48 @@ func TestContextEval(t *testing.T) {
}
}

func TestContextEvalUnknowns(t *testing.T) {
env, err := NewEnv(
Variable("groups", ListType(IntType)),
Variable("id", IntType),
)
if err != nil {
t.Fatalf("NewEnv() failed: %v", err)
}

pvars, err := PartialVars(
map[string]any{
"groups": []int{1, 2, 3},
},
AttributePattern("id"),
)
if err != nil {
t.Fatalf("PartialVars() failed: %v", err)
}

ast, iss := env.Compile(`groups.exists(t, t == id)`)
if iss.Err() != nil {
t.Fatalf("env.Compile() failed: %v", iss.Err())
}

prg, err := env.Program(ast, EvalOptions(OptTrackState, OptPartialEval), InterruptCheckFrequency(100))
if err != nil {
t.Fatalf("env.Program() failed: %v", err)
}

out, _, err := prg.Eval(pvars)
if err != nil {
t.Fatalf("prg.Eval() failed: %v", err)
}
ctxOut, _, err := prg.ContextEval(context.Background(), pvars)
if err != nil {
t.Fatalf("prg.ContextEval() failed: %v", err)
}
if !reflect.DeepEqual(out, ctxOut) {
t.Errorf("got %v, wanted %v", out, ctxOut)
}
}

func BenchmarkContextEval(b *testing.B) {
env := testEnv(b,
Variable("items", ListType(IntType)),
Expand Down
5 changes: 5 additions & 0 deletions cel/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,11 @@ func (a *ctxEvalActivation) Parent() Activation {
return a.parent
}

func (a *ctxEvalActivation) AsPartialActivation() (interpreter.PartialActivation, bool) {
pa, ok := a.parent.(interpreter.PartialActivation)
return pa, ok
}

func newCtxEvalActivationPool() *ctxEvalActivationPool {
return &ctxEvalActivationPool{
Pool: sync.Pool{
Expand Down
13 changes: 7 additions & 6 deletions interpreter/activation.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ type PartialActivation interface {

// partialActivationConverter indicates whether an Activation implementation supports conversion to a PartialActivation
type partialActivationConverter interface {
asPartialActivation() (PartialActivation, bool)
// AsPartialActivation converts the current activation to a PartialActivation
AsPartialActivation() (PartialActivation, bool)
}

// partActivation is the default implementations of the PartialActivation interface.
Expand All @@ -172,19 +173,19 @@ func (a *partActivation) UnknownAttributePatterns() []*AttributePattern {
return a.unknowns
}

// asPartialActivation returns the partActivation as a PartialActivation interface.
func (a *partActivation) asPartialActivation() (PartialActivation, bool) {
// AsPartialActivation returns the partActivation as a PartialActivation interface.
func (a *partActivation) AsPartialActivation() (PartialActivation, bool) {
return a, true
}

func asPartialActivation(vars Activation) (PartialActivation, bool) {
func AsPartialActivation(vars Activation) (PartialActivation, bool) {
// Only internal activation instances may implement this interface
if pv, ok := vars.(partialActivationConverter); ok {
return pv.asPartialActivation()
return pv.AsPartialActivation()
}
// Since Activations may be hierarchical, test whether a parent converts to a PartialActivation
if vars.Parent() != nil {
return asPartialActivation(vars.Parent())
return AsPartialActivation(vars.Parent())
}
return nil, false
}
2 changes: 1 addition & 1 deletion interpreter/attribute_patterns.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ func (m *attributeMatcher) AddQualifier(qual Qualifier) (Attribute, error) {
func (m *attributeMatcher) Resolve(vars Activation) (any, error) {
id := m.NamespacedAttribute.ID()
// Bug in how partial activation is resolved, should search parents as well.
partial, isPartial := asPartialActivation(vars)
partial, isPartial := AsPartialActivation(vars)
if isPartial {
unk, err := m.fac.matchesUnknownPatterns(
partial,
Expand Down
6 changes: 3 additions & 3 deletions interpreter/interpretable.go
Original file line number Diff line number Diff line change
Expand Up @@ -1370,16 +1370,16 @@ func (f *folder) Parent() Activation {
// if they were provided to the input activation, or an empty set if the proxied activation is not partial.
func (f *folder) UnknownAttributePatterns() []*AttributePattern {
if pv, ok := f.activation.(partialActivationConverter); ok {
if partial, isPartial := pv.asPartialActivation(); isPartial {
if partial, isPartial := pv.AsPartialActivation(); isPartial {
return partial.UnknownAttributePatterns()
}
}
return []*AttributePattern{}
}

func (f *folder) asPartialActivation() (PartialActivation, bool) {
func (f *folder) AsPartialActivation() (PartialActivation, bool) {
if pv, ok := f.activation.(partialActivationConverter); ok {
if _, isPartial := pv.asPartialActivation(); isPartial {
if _, isPartial := pv.AsPartialActivation(); isPartial {
return f, true
}
}
Expand Down

0 comments on commit 9a4b48b

Please sign in to comment.