Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ContextEval support for Unknowns #1126

Merged
merged 2 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -442,6 +442,11 @@ func (a *ctxEvalActivation) Parent() interpreter.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