Skip to content

Commit 2136a89

Browse files
l46kokcopybara-github
authored andcommitted
Walk select chains through in-memory proto messages without wrapping each one
PiperOrigin-RevId: 997975385
1 parent 6e0a799 commit 2136a89

11 files changed

Lines changed: 430 additions & 9 deletions

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ java_library(
280280
],
281281
deps = [
282282
":base_proto_cel_value_converter",
283+
":optimized_select_traversal",
283284
":optimized_selectable",
284285
":preadapted_list",
285286
":select_field",
@@ -328,6 +329,7 @@ java_library(
328329
],
329330
deps = [
330331
":base_proto_cel_value_converter",
332+
":optimized_select_traversal",
331333
":optimized_selectable",
332334
":preadapted_list",
333335
":select_field",
@@ -360,6 +362,7 @@ cel_android_library(
360362
],
361363
deps = [
362364
":base_proto_cel_value_converter_android",
365+
":optimized_select_traversal_android",
363366
":optimized_selectable_android",
364367
":preadapted_list_android",
365368
":select_field_android",
@@ -483,6 +486,7 @@ java_library(
483486
":select_field",
484487
"//common/annotations",
485488
"@maven//:com_google_errorprone_error_prone_annotations",
489+
"@maven//:com_google_guava_guava",
486490
],
487491
)
488492

@@ -495,6 +499,7 @@ cel_android_library(
495499
":select_field_android",
496500
"//common/annotations",
497501
"@maven//:com_google_errorprone_error_prone_annotations",
502+
"@maven_android//:com_google_guava_guava",
498503
],
499504
)
500505

‎common/src/main/java/dev/cel/common/values/OptimizedSelectTraversal.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,17 @@ public final class OptimizedSelectTraversal {
3535
* Qualifies {@code target} through every field in {@code fields} and returns the terminal value.
3636
*/
3737
public static Object qualify(Object target, ImmutableList<SelectField> fields) {
38+
return qualify(target, fields, 0);
39+
}
40+
41+
/** Qualifies {@code target} through the fields of {@code fields} from index {@code from} on. */
42+
static Object qualify(Object target, ImmutableList<SelectField> fields, int from) {
3843
Object current = target;
39-
for (int i = 0; i < fields.size(); i++) {
44+
for (int i = from; i < fields.size(); i++) {
45+
// With only the last field left, there's nothing to walk.
46+
if (i < fields.size() - 1 && current instanceof OptimizedSelectable) {
47+
return ((OptimizedSelectable) current).selectPath(fields, i);
48+
}
4049
current = qualifyField(current, fields.get(i));
4150
}
4251
return current;

‎common/src/main/java/dev/cel/common/values/OptimizedSelectable.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package dev.cel.common.values;
1616

17+
import com.google.common.collect.ImmutableList;
1718
import com.google.errorprone.annotations.Immutable;
1819
import dev.cel.common.annotations.Internal;
1920
import java.util.Optional;
@@ -23,7 +24,8 @@
2324
*
2425
* <p>Implementations resolve individual field selections against themselves by protobuf field
2526
* number. Walking the chain across multiple fields and heterogeneous values belongs to {@link
26-
* OptimizedSelectTraversal}.
27+
* OptimizedSelectTraversal}, though an implementation may walk the part of it that it can read
28+
* itself through {@link #selectPath}.
2729
*
2830
* <p>CEL Library Internals. Do Not Use.
2931
*/
@@ -42,4 +44,11 @@ public interface OptimizedSelectable {
4244
* step of a presence test, or empty if absent.
4345
*/
4446
Optional<Object> findByFieldNumber(SelectField field);
47+
48+
/**
49+
* Selects {@code fields} from index {@code from} on, starting at this value, and returns the last
50+
* one's value. Implementations may walk the nested values they can read without materializing
51+
* each one, and hand the rest back to {@link OptimizedSelectTraversal}.
52+
*/
53+
Object selectPath(ImmutableList<SelectField> fields, int from);
4554
}

‎common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,46 @@ boolean hasFieldByNumber(
506506
"Unexpected encoding type: " + fieldDescriptor.getEncodingType());
507507
}
508508

509+
/**
510+
* Like {@link OptimizedSelectable#selectPath}: walks {@code message}'s in-memory submessages
511+
* through their getters without wrapping them, then selects the rest from the last one reached.
512+
*/
513+
Object selectPath(ProtoMessageLiteValue message, ImmutableList<SelectField> fields, int from) {
514+
MessageLite current = message.rawValue();
515+
MessageLiteDescriptor descriptor = message.descriptor();
516+
if (current == null || !descriptor.hasGetters()) {
517+
// Only in-memory messages read through generated getters can be walked.
518+
return OptimizedSelectTraversal.qualify(
519+
message.selectByFieldNumber(fields.get(from)), fields, from + 1);
520+
}
521+
int i = from;
522+
// Walks submessages up to the one holding the last field, stopping at one that's unset or has
523+
// no descriptor with getters, such as well-known types.
524+
while (i < fields.size() - 1) {
525+
Object submessage = descriptor.getFieldValue(current, fields.get(i).fieldNumber());
526+
if (!(submessage instanceof MessageLite)) {
527+
break;
528+
}
529+
Optional<MessageLiteDescriptor> submessageDescriptor =
530+
descriptorPool
531+
.findDescriptor((MessageLite) submessage)
532+
.filter(MessageLiteDescriptor::hasGetters);
533+
if (!submessageDescriptor.isPresent()) {
534+
break;
535+
}
536+
current = (MessageLite) submessage;
537+
descriptor = submessageDescriptor.get();
538+
i++;
539+
}
540+
// Selects the next field from the last message reached, or from this one if none was.
541+
ProtoMessageLiteValue reached = message;
542+
if (i > from) {
543+
reached = ProtoMessageLiteValue.create(current, descriptor, this);
544+
}
545+
return OptimizedSelectTraversal.qualify(
546+
reached.selectByFieldNumber(fields.get(i)), fields, i + 1);
547+
}
548+
509549
/** Like {@link #hasFieldByNumber}, through the field's generated getter. */
510550
boolean hasByGetter(ProtoMessageLiteValue message, SelectField field) {
511551
return readGetter(message, field.fieldNumber()) != null;

‎common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.auto.value.AutoValue;
2020
import com.google.auto.value.extension.memoized.Memoized;
21+
import com.google.common.collect.ImmutableList;
2122
import com.google.errorprone.annotations.Immutable;
2223
import com.google.protobuf.ByteString;
2324
import com.google.protobuf.MessageLite;
@@ -147,6 +148,11 @@ public Optional<Object> find(String field) {
147148
return Optional.ofNullable(descriptor().findByFieldName(field)).map(this::readField);
148149
}
149150

151+
@Override
152+
public Object selectPath(ImmutableList<SelectField> fields, int from) {
153+
return protoLiteCelValueConverter().selectPath(this, fields, from);
154+
}
155+
150156
@Override
151157
public Object selectByFieldNumber(SelectField field) {
152158
FieldLiteDescriptor fd = descriptor().findByFieldNumber(field.fieldNumber());

‎common/src/main/java/dev/cel/common/values/ProtoMessageValue.java‎

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717
import static com.google.common.base.Preconditions.checkNotNull;
1818

1919
import com.google.auto.value.AutoValue;
20+
import com.google.common.collect.ImmutableList;
2021
import com.google.errorprone.annotations.Immutable;
2122
import com.google.protobuf.Descriptors.Descriptor;
2223
import com.google.protobuf.Descriptors.FieldDescriptor;
24+
import com.google.protobuf.DynamicMessage;
2325
import com.google.protobuf.Message;
2426
import dev.cel.common.internal.CelDescriptorPool;
27+
import dev.cel.common.internal.WellKnownProto;
2528
import dev.cel.common.types.CelType;
2629
import dev.cel.common.types.StructTypeReference;
2730
import java.util.Optional;
@@ -76,9 +79,7 @@ public Optional<Object> find(String field) {
7679

7780
@Override
7881
public Object selectByFieldNumber(SelectField field) {
79-
FieldDescriptor fieldDescriptor = findFieldByNumber(value().getDescriptorForType(), field);
80-
81-
return protoCelValueConverter().fromProtoMessageFieldToCelValue(value(), fieldDescriptor);
82+
return selectField(value(), field);
8283
}
8384

8485
@Override
@@ -95,6 +96,42 @@ public Optional<Object> findByFieldNumber(SelectField field) {
9596
return findFieldValue(fieldDescriptor);
9697
}
9798

99+
@Override
100+
public Object selectPath(ImmutableList<SelectField> fields, int from) {
101+
Message message = value();
102+
if (message instanceof DynamicMessage) {
103+
// Each hop adapts a dynamic message into its generated class, if it has one.
104+
return OptimizedSelectTraversal.qualify(
105+
selectByFieldNumber(fields.get(from)), fields, from + 1);
106+
}
107+
int i = from;
108+
// Walks nested messages without wrapping them, up to the one holding the last field.
109+
while (i < fields.size() - 1) {
110+
FieldDescriptor fieldDescriptor =
111+
findFieldByNumber(message.getDescriptorForType(), fields.get(i));
112+
if (!isWalkable(fieldDescriptor)) {
113+
break;
114+
}
115+
message = (Message) message.getField(fieldDescriptor);
116+
i++;
117+
}
118+
return OptimizedSelectTraversal.qualify(selectField(message, fields.get(i)), fields, i + 1);
119+
}
120+
121+
// A singular message field that isn't of a well-known type, which the converter would adapt.
122+
private static boolean isWalkable(FieldDescriptor fieldDescriptor) {
123+
return fieldDescriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE
124+
&& !fieldDescriptor.isRepeated()
125+
&& !WellKnownProto.getByTypeName(fieldDescriptor.getMessageType().getFullName())
126+
.isPresent();
127+
}
128+
129+
private Object selectField(Message message, SelectField field) {
130+
FieldDescriptor fieldDescriptor = findFieldByNumber(message.getDescriptorForType(), field);
131+
132+
return protoCelValueConverter().fromProtoMessageFieldToCelValue(message, fieldDescriptor);
133+
}
134+
98135
public static ProtoMessageValue create(
99136
Message value,
100137
CelDescriptorPool celDescriptorPool,

‎common/src/main/java/dev/cel/common/values/RawProtoMessageLiteValue.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.common.base.Preconditions.checkNotNull;
1818

1919
import com.google.auto.value.AutoValue;
20+
import com.google.common.collect.ImmutableList;
2021
import com.google.errorprone.annotations.Immutable;
2122
import com.google.protobuf.ByteString;
2223
import com.google.protobuf.CodedInputStream;
@@ -136,6 +137,19 @@ public Optional<Object> findByFieldNumber(SelectField field) {
136137
return protoLiteCelValueConverter().findByFieldNumber(toByteString(), protoTypeName(), field);
137138
}
138139

140+
@Override
141+
public Object selectPath(ImmutableList<SelectField> fields, int from) {
142+
// Wire bytes have no submessages in memory to walk. Consecutive hops through wire bytes are
143+
// selected here, rather than each going back through the traversal.
144+
Object current = this;
145+
int i = from;
146+
while (i < fields.size() - 1 && current instanceof RawProtoMessageLiteValue) {
147+
current = ((RawProtoMessageLiteValue) current).selectByFieldNumber(fields.get(i));
148+
i++;
149+
}
150+
return OptimizedSelectTraversal.qualify(current, fields, i);
151+
}
152+
139153
static RawProtoMessageLiteValue create(
140154
ByteString rawWireBytes, ProtoLiteCelValueConverter protoLiteCelValueConverter) {
141155
return create(rawWireBytes, "", protoLiteCelValueConverter);

‎common/src/test/java/dev/cel/common/values/OptimizedSelectTraversalTest.java‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,27 @@ public void hasField_intermediateUnsupportedTarget_throwsException() {
237237
assertThat(thrown).hasMessageThat().contains("child_key");
238238
}
239239

240+
@Test
241+
public void qualify_valueWalkingRestOfChain_returnsItsResult() {
242+
FakeOptimizedSelectable walking =
243+
new FakeOptimizedSelectable(ImmutableMap.of()) {
244+
@Override
245+
public Object selectPath(ImmutableList<SelectField> fields, int from) {
246+
return "walked from " + fields.get(from).fieldName();
247+
}
248+
};
249+
FakeOptimizedSelectable root = new FakeOptimizedSelectable(ImmutableMap.of("outer", walking));
250+
ImmutableList<SelectField> fields =
251+
ImmutableList.of(
252+
SelectField.create(1L, "outer", 11),
253+
SelectField.create(2L, "middle", 11),
254+
SelectField.create(3L, "leaf", 9, ""));
255+
256+
Object result = OptimizedSelectTraversal.qualify(root, fields);
257+
258+
assertThat(result).isEqualTo("walked from middle");
259+
}
260+
240261
@Test
241262
public void hasField_unsupportedTarget_throwsException() {
242263
ImmutableList<SelectField> fields = ImmutableList.of(SelectField.create(1L, "invalid_field"));
@@ -272,7 +293,7 @@ public void hasField_errorValue_returnsFalse() {
272293
}
273294

274295
@SuppressWarnings("Immutable")
275-
private static final class FakeOptimizedSelectable implements OptimizedSelectable {
296+
private static class FakeOptimizedSelectable implements OptimizedSelectable {
276297
private final ImmutableMap<String, Object> values;
277298

278299
@Override
@@ -297,6 +318,12 @@ public Optional<Object> findByFieldNumber(SelectField field) {
297318
return Optional.ofNullable(values.get(field.fieldName()));
298319
}
299320

321+
@Override
322+
public Object selectPath(ImmutableList<SelectField> fields, int from) {
323+
return OptimizedSelectTraversal.qualify(
324+
selectByFieldNumber(fields.get(from)), fields, from + 1);
325+
}
326+
300327
FakeOptimizedSelectable(Map<String, Object> values) {
301328
this.values = ImmutableMap.copyOf(values);
302329
}

0 commit comments

Comments
 (0)