Skip to content

Commit 79f4d1d

Browse files
l46kokcopybara-github
authored andcommitted
Evaluate cel.@Attribute and cel.@Hasfield in the planner runtime
PiperOrigin-RevId: 976836703
1 parent b81e271 commit 79f4d1d

26 files changed

Lines changed: 4568 additions & 154 deletions

‎common/src/main/java/dev/cel/common/values/BUILD.bazel‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,14 +317,18 @@ java_library(
317317
srcs = [
318318
"ProtoLiteCelValueConverter.java",
319319
"ProtoMessageLiteValue.java",
320+
"RawProtoMessageLiteValue.java",
320321
],
321322
tags = [
322323
],
323324
deps = [
324325
":base_proto_cel_value_converter",
326+
":optimized_selectable",
327+
":select_field",
325328
":values",
326329
"//:auto_value",
327330
"//common/annotations",
331+
"//common/exceptions:attribute_not_found",
328332
"//common/internal:cel_lite_descriptor_pool",
329333
"//common/internal:well_known_proto",
330334
"//common/types",
@@ -333,6 +337,7 @@ java_library(
333337
"//protobuf:cel_lite_descriptor",
334338
"@maven//:com_google_errorprone_error_prone_annotations",
335339
"@maven//:com_google_guava_guava",
340+
"@maven//:org_jspecify_jspecify",
336341
"@maven_android//:com_google_protobuf_protobuf_javalite",
337342
],
338343
)
@@ -342,14 +347,18 @@ cel_android_library(
342347
srcs = [
343348
"ProtoLiteCelValueConverter.java",
344349
"ProtoMessageLiteValue.java",
350+
"RawProtoMessageLiteValue.java",
345351
],
346352
tags = [
347353
],
348354
deps = [
349355
":base_proto_cel_value_converter_android",
356+
":optimized_selectable_android",
357+
":select_field_android",
350358
":values_android",
351359
"//:auto_value",
352360
"//common/annotations",
361+
"//common/exceptions:attribute_not_found",
353362
"//common/internal:cel_lite_descriptor_pool_android",
354363
"//common/internal:well_known_proto_android",
355364
"//common/types:type_providers_android",
@@ -358,6 +367,7 @@ cel_android_library(
358367
"//protobuf:cel_lite_descriptor",
359368
"@maven//:com_google_errorprone_error_prone_annotations",
360369
"@maven//:com_google_guava_guava",
370+
"@maven//:org_jspecify_jspecify",
361371
"@maven_android//:com_google_guava_guava",
362372
"@maven_android//:com_google_protobuf_protobuf_javalite",
363373
],
@@ -428,3 +438,85 @@ cel_android_library(
428438
"@maven//:com_google_errorprone_error_prone_annotations",
429439
],
430440
)
441+
442+
java_library(
443+
name = "select_field",
444+
srcs = ["SelectField.java"],
445+
tags = [
446+
],
447+
deps = [
448+
"//:auto_value",
449+
"//common/annotations",
450+
"@maven//:com_google_errorprone_error_prone_annotations",
451+
"@maven//:com_google_guava_guava",
452+
"@maven//:org_jspecify_jspecify",
453+
],
454+
)
455+
456+
cel_android_library(
457+
name = "select_field_android",
458+
srcs = ["SelectField.java"],
459+
tags = [
460+
],
461+
deps = [
462+
"//:auto_value",
463+
"//common/annotations",
464+
"@maven//:com_google_errorprone_error_prone_annotations",
465+
"@maven//:org_jspecify_jspecify",
466+
"@maven_android//:com_google_guava_guava",
467+
],
468+
)
469+
470+
java_library(
471+
name = "optimized_selectable",
472+
srcs = ["OptimizedSelectable.java"],
473+
tags = [
474+
],
475+
deps = [
476+
":select_field",
477+
"//common/annotations",
478+
"@maven//:com_google_errorprone_error_prone_annotations",
479+
],
480+
)
481+
482+
cel_android_library(
483+
name = "optimized_selectable_android",
484+
srcs = ["OptimizedSelectable.java"],
485+
tags = [
486+
],
487+
deps = [
488+
":select_field_android",
489+
"//common/annotations",
490+
"@maven//:com_google_errorprone_error_prone_annotations",
491+
],
492+
)
493+
494+
java_library(
495+
name = "optimized_select_traversal",
496+
srcs = ["OptimizedSelectTraversal.java"],
497+
tags = [
498+
],
499+
deps = [
500+
":optimized_selectable",
501+
":select_field",
502+
":values",
503+
"//common/annotations",
504+
"//common/exceptions:attribute_not_found",
505+
"@maven//:com_google_guava_guava",
506+
],
507+
)
508+
509+
cel_android_library(
510+
name = "optimized_select_traversal_android",
511+
srcs = ["OptimizedSelectTraversal.java"],
512+
tags = [
513+
],
514+
deps = [
515+
":optimized_selectable_android",
516+
":select_field_android",
517+
":values_android",
518+
"//common/annotations",
519+
"//common/exceptions:attribute_not_found",
520+
"@maven_android//:com_google_guava_guava",
521+
],
522+
)
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.common.values;
16+
17+
import com.google.common.collect.ImmutableList;
18+
import dev.cel.common.annotations.Internal;
19+
import dev.cel.common.exceptions.CelAttributeNotFoundException;
20+
import java.util.Map;
21+
import java.util.Optional;
22+
23+
/**
24+
* Walks a sequence of {@link SelectField} selections, dispatching each field over {@link
25+
* OptimizedSelectable}, {@link SelectableValue}, or {@link Map}.
26+
*
27+
* <p>CEL Library Internals. Do Not Use.
28+
*/
29+
@Internal
30+
public final class OptimizedSelectTraversal {
31+
32+
/**
33+
* Qualifies {@code target} through every field in {@code fields} and returns the terminal value.
34+
*
35+
* @param celValueConverter Converter for unadapted entries encountered in a root {@link Map}.
36+
*/
37+
public static Object qualify(
38+
Object target, ImmutableList<SelectField> fields, CelValueConverter celValueConverter) {
39+
Object current = target;
40+
for (int i = 0; i < fields.size(); i++) {
41+
current = qualifyField(current, fields.get(i), celValueConverter);
42+
}
43+
return current;
44+
}
45+
46+
/**
47+
* Presence tests the terminal field of {@code fields}, navigating through all preceding fields.
48+
*
49+
* <p>Absence of any intermediate field short-circuits to {@code false}.
50+
*/
51+
public static boolean hasField(
52+
Object target, ImmutableList<SelectField> fields, CelValueConverter celValueConverter) {
53+
if (fields.isEmpty()) {
54+
return false;
55+
}
56+
Object current = target;
57+
int terminalIndex = fields.size() - 1;
58+
for (int i = 0; i < terminalIndex; i++) {
59+
Optional<Object> next = navigateField(current, fields.get(i), celValueConverter);
60+
if (!next.isPresent()) {
61+
return false;
62+
}
63+
current = next.get();
64+
}
65+
return hasTerminalField(current, fields.get(terminalIndex));
66+
}
67+
68+
// SelectableValue is only ever instantiated with String keys in the select path.
69+
@SuppressWarnings("unchecked")
70+
private static Object qualifyField(
71+
Object target, SelectField field, CelValueConverter celValueConverter) {
72+
if (target instanceof ErrorValue) {
73+
return target;
74+
}
75+
if (target instanceof OptimizedSelectable) {
76+
return ((OptimizedSelectable) target).selectByFieldNumber(field);
77+
}
78+
if (target instanceof SelectableValue) {
79+
SelectableValue<String> selectable = (SelectableValue<String>) target;
80+
if (field.defaultValue() != null) {
81+
return selectable
82+
.find(field.fieldName())
83+
.map(Object.class::cast)
84+
.orElse(field.defaultValue());
85+
}
86+
return selectable.select(field.fieldName());
87+
}
88+
if (target instanceof Map) {
89+
return getMapEntry((Map<?, ?>) target, field.fieldName(), celValueConverter);
90+
}
91+
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
92+
}
93+
94+
// SelectableValue is only ever instantiated with String keys in the select path.
95+
@SuppressWarnings("unchecked")
96+
private static Optional<Object> navigateField(
97+
Object target, SelectField field, CelValueConverter celValueConverter) {
98+
if (target instanceof ErrorValue) {
99+
return Optional.of(target);
100+
}
101+
if (target instanceof OptimizedSelectable) {
102+
return ((OptimizedSelectable) target).findByFieldNumber(field);
103+
}
104+
if (target instanceof SelectableValue) {
105+
return ((SelectableValue<String>) target).find(field.fieldName()).map(Object.class::cast);
106+
}
107+
if (target instanceof Map) {
108+
return findMapEntry((Map<?, ?>) target, field.fieldName(), celValueConverter);
109+
}
110+
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
111+
}
112+
113+
// SelectableValue is only ever instantiated with String keys in the select path.
114+
@SuppressWarnings("unchecked")
115+
private static boolean hasTerminalField(Object target, SelectField field) {
116+
if (target instanceof ErrorValue) {
117+
return false;
118+
}
119+
if (target instanceof OptimizedSelectable) {
120+
return ((OptimizedSelectable) target).hasFieldByNumber(field);
121+
}
122+
if (target instanceof SelectableValue) {
123+
return ((SelectableValue<String>) target).find(field.fieldName()).isPresent();
124+
}
125+
if (target instanceof Map) {
126+
return ((Map<?, ?>) target).containsKey(field.fieldName());
127+
}
128+
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
129+
}
130+
131+
private static Object getMapEntry(
132+
Map<?, ?> map, String key, CelValueConverter celValueConverter) {
133+
return findMapEntry(map, key, celValueConverter)
134+
.orElseThrow(() -> CelAttributeNotFoundException.forMissingMapKey(key));
135+
}
136+
137+
private static Optional<Object> findMapEntry(
138+
Map<?, ?> map, String key, CelValueConverter celValueConverter) {
139+
Object mapValue = map.get(key);
140+
if (mapValue != null) {
141+
return Optional.of(toStepTarget(mapValue, celValueConverter));
142+
}
143+
if (!map.containsKey(key)) {
144+
return Optional.empty();
145+
}
146+
throw CelAttributeNotFoundException.of(
147+
String.format("Map value cannot be null for key: %s", key));
148+
}
149+
150+
static Object toStepTarget(Object value, CelValueConverter celValueConverter) {
151+
if (value instanceof Map) {
152+
return value;
153+
}
154+
return celValueConverter.toRuntimeValue(value);
155+
}
156+
157+
private OptimizedSelectTraversal() {}
158+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.common.values;
16+
17+
import com.google.errorprone.annotations.Immutable;
18+
import dev.cel.common.annotations.Internal;
19+
import java.util.Optional;
20+
21+
/**
22+
* Resolves an optimized field selection within a selection chain rewritten by the select optimizer.
23+
*
24+
* <p>Implementations resolve individual field selections against themselves by protobuf field
25+
* number. Walking the chain across multiple fields and heterogeneous values belongs to {@link
26+
* OptimizedSelectTraversal}.
27+
*
28+
* <p>CEL Library Internals. Do Not Use.
29+
*/
30+
@Internal
31+
@Immutable
32+
public interface OptimizedSelectable {
33+
34+
/** Selects {@code field}, falling back to its default value or an empty submessage if absent. */
35+
Object selectByFieldNumber(SelectField field);
36+
37+
/** Returns whether {@code field} is present. */
38+
boolean hasFieldByNumber(SelectField field);
39+
40+
/**
41+
* Returns the value of the field at {@code field} (a scalar or submessage) for an intermediate
42+
* step of a presence test, or empty if absent.
43+
*/
44+
Optional<Object> findByFieldNumber(SelectField field);
45+
}

0 commit comments

Comments
 (0)