diff --git a/interpreter/interpretable.go b/interpreter/interpretable.go index 74aef257..f9724d46 100644 --- a/interpreter/interpretable.go +++ b/interpreter/interpretable.go @@ -110,10 +110,11 @@ type InterpretableConstructor interface { // Core Interpretable implementations used during the program planning phase. type evalTestOnly struct { - id int64 - attr InterpretableAttribute - qual Qualifier - field types.String + id int64 + attr InterpretableAttribute + qual Qualifier + field types.String + isProtoField bool } // ID implements the Interpretable interface method. diff --git a/interpreter/planner.go b/interpreter/planner.go index 620f9aac..165a388b 100644 --- a/interpreter/planner.go +++ b/interpreter/planner.go @@ -220,11 +220,19 @@ func (p *planner) planSelect(expr *exprpb.Expr) (Interpretable, error) { // Return the test only eval expression. if sel.GetTestOnly() { + isProtoField := false + if opType.GetMessageType() != "" { + ft, found := p.provider.FindFieldType(opType.GetMessageType(), sel.GetField()) + if found && ft.IsSet != nil && ft.GetFrom != nil { + isProtoField = true + } + } return &evalTestOnly{ - id: expr.GetId(), - field: types.String(sel.GetField()), - attr: attr, - qual: qual, + id: expr.GetId(), + field: types.String(sel.GetField()), + attr: attr, + qual: qual, + isProtoField: isProtoField, }, nil } diff --git a/interpreter/runtimecost.go b/interpreter/runtimecost.go index 42e64a44..33a4ea56 100644 --- a/interpreter/runtimecost.go +++ b/interpreter/runtimecost.go @@ -38,6 +38,12 @@ type ActualCostEstimator interface { func CostObserver(tracker *CostTracker) EvalObserver { observer := func(id int64, programStep any, val ref.Val) { switch t := programStep.(type) { + case *evalTestOnly: + // Protobuf has() checks do not incur runtime cost due to not using Eval() + // in older versions of CEL; this keeps the runtime cost behavior consistent + if !t.isProtoField { + tracker.cost++ + } case ConstantQualifier: // TODO: Push identifiers on to the stack before observing constant qualifiers that apply to them // and enable the below pop. Once enabled this can case can be collapsed into the Qualifier case.