Skip to content

Commit 0c7b10f

Browse files
l46kokcopybara-github
authored andcommitted
Enhance ConstantFoldingOptimizer to fold arithmetics involving timestamps and durations
Note: `evaluateCanonicalTypesToNativeValues` must be enabled for optimization to work PiperOrigin-RevId: 813922645
1 parent 30bd91f commit 0c7b10f

File tree

4 files changed

+63
-15
lines changed

4 files changed

+63
-15
lines changed

checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1488,7 +1488,7 @@ public CelFunctionDecl functionDecl() {
14881488
return celFunctionDecl;
14891489
}
14901490

1491-
String functionName() {
1491+
public String functionName() {
14921492
return functionName;
14931493
}
14941494

optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ java_library(
1919
":default_optimizer_constants",
2020
"//:auto_value",
2121
"//bundle:cel",
22+
"//checker:standard_decl",
2223
"//common:cel_ast",
2324
"//common:cel_source",
2425
"//common:compiler_common",
2526
"//common:mutable_ast",
2627
"//common/ast",
2728
"//common/ast:mutable_expr",
29+
"//common/internal:date_time_helpers",
2830
"//common/navigation:mutable_navigation",
2931
"//common/types",
3032
"//extensions:optional_library",

optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import static com.google.common.base.Preconditions.checkNotNull;
1717
import static com.google.common.collect.ImmutableList.toImmutableList;
1818
import static com.google.common.collect.MoreCollectors.onlyElement;
19+
import static dev.cel.checker.CelStandardDeclarations.StandardFunction.DURATION;
20+
import static dev.cel.checker.CelStandardDeclarations.StandardFunction.TIMESTAMP;
1921

2022
import com.google.auto.value.AutoValue;
2123
import com.google.common.collect.ImmutableList;
@@ -36,6 +38,7 @@
3638
import dev.cel.common.ast.CelMutableExpr.CelMutableMap;
3739
import dev.cel.common.ast.CelMutableExpr.CelMutableStruct;
3840
import dev.cel.common.ast.CelMutableExprConverter;
41+
import dev.cel.common.internal.DateTimeHelpers;
3942
import dev.cel.common.navigation.CelNavigableMutableAst;
4043
import dev.cel.common.navigation.CelNavigableMutableExpr;
4144
import dev.cel.common.types.SimpleType;
@@ -45,6 +48,8 @@
4548
import dev.cel.optimizer.CelOptimizationException;
4649
import dev.cel.parser.Operator;
4750
import dev.cel.runtime.CelEvaluationException;
51+
import java.time.Duration;
52+
import java.time.Instant;
4853
import java.util.ArrayList;
4954
import java.util.Arrays;
5055
import java.util.Collection;
@@ -142,6 +147,14 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) {
142147
return false;
143148
}
144149

150+
// Timestamps/durations in CEL are calls, but they are effectively treated as literals.
151+
// Expressions like timestamp(123) cannot be folded directly, but arithmetics involving
152+
// timestamps can be optimized.
153+
// Ex: timestamp(123) - timestamp(100) = duration("23s")
154+
if (isCallTimestampOrDuration(navigableExpr.expr().call())) {
155+
return false;
156+
}
157+
145158
CelMutableCall mutableCall = navigableExpr.expr().call();
146159
String functionName = mutableCall.function();
147160

@@ -197,14 +210,14 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) {
197210
private boolean containsFoldableFunctionOnly(CelNavigableMutableExpr navigableExpr) {
198211
return navigableExpr
199212
.allNodes()
200-
.allMatch(
201-
node -> {
202-
if (node.getKind().equals(Kind.CALL)) {
203-
return foldableFunctions.contains(node.expr().call().function());
204-
}
205-
206-
return true;
207-
});
213+
.filter(node -> node.getKind().equals(Kind.CALL))
214+
.map(node -> node.expr().call())
215+
.allMatch(call -> foldableFunctions.contains(call.function()));
216+
}
217+
218+
private static boolean isCallTimestampOrDuration(CelMutableCall call) {
219+
return call.function().equals(TIMESTAMP.functionName())
220+
|| call.function().equals(DURATION.functionName());
208221
}
209222

210223
private static boolean canFoldInOperator(CelNavigableMutableExpr navigableExpr) {
@@ -318,6 +331,22 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
318331
}
319332

320333
return Optional.of(CelMutableExpr.ofMap(CelMutableMap.create(mapEntries)));
334+
} else if (result instanceof Duration) {
335+
String durationStrArg = DateTimeHelpers.toString((Duration) result);
336+
CelMutableCall durationCall =
337+
CelMutableCall.create(
338+
DURATION.functionName(),
339+
CelMutableExpr.ofConstant(CelConstant.ofValue(durationStrArg)));
340+
341+
return Optional.of(CelMutableExpr.ofCall(durationCall));
342+
} else if (result instanceof Instant) {
343+
String timestampStrArg = result.toString();
344+
CelMutableCall timestampCall =
345+
CelMutableCall.create(
346+
TIMESTAMP.functionName(),
347+
CelMutableExpr.ofConstant(CelConstant.ofValue(timestampStrArg)));
348+
349+
return Optional.of(CelMutableExpr.ofCall(timestampCall));
321350
}
322351

323352
// Evaluated result cannot be folded (e.g: unknowns)

optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@
4646

4747
@RunWith(TestParameterInjector.class)
4848
public class ConstantFoldingOptimizerTest {
49+
private static final CelOptions CEL_OPTIONS =
50+
CelOptions.current()
51+
.enableTimestampEpoch(true)
52+
.evaluateCanonicalTypesToNativeValues(true)
53+
.build();
4954
private static final Cel CEL =
5055
CelFactory.standardCelBuilder()
5156
.addVar("x", SimpleType.DYN)
@@ -60,19 +65,20 @@ public class ConstantFoldingOptimizerTest {
6065
CelFunctionBinding.from("get_true_overload", ImmutableList.of(), unused -> true))
6166
.addMessageTypes(TestAllTypes.getDescriptor())
6267
.setContainer(CelContainer.ofName("cel.expr.conformance.proto3"))
68+
.setOptions(CEL_OPTIONS)
6369
.addCompilerLibraries(
6470
CelExtensions.bindings(),
6571
CelOptionalLibrary.INSTANCE,
66-
CelExtensions.math(CelOptions.DEFAULT),
72+
CelExtensions.math(CEL_OPTIONS),
6773
CelExtensions.strings(),
68-
CelExtensions.sets(CelOptions.DEFAULT),
69-
CelExtensions.encoders(CelOptions.DEFAULT))
74+
CelExtensions.sets(CEL_OPTIONS),
75+
CelExtensions.encoders(CEL_OPTIONS))
7076
.addRuntimeLibraries(
7177
CelOptionalLibrary.INSTANCE,
72-
CelExtensions.math(CelOptions.DEFAULT),
78+
CelExtensions.math(CEL_OPTIONS),
7379
CelExtensions.strings(),
74-
CelExtensions.sets(CelOptions.DEFAULT),
75-
CelExtensions.encoders(CelOptions.DEFAULT))
80+
CelExtensions.sets(CEL_OPTIONS),
81+
CelExtensions.encoders(CEL_OPTIONS))
7682
.build();
7783

7884
private static final CelOptimizer CEL_OPTIMIZER =
@@ -211,6 +217,15 @@ public class ConstantFoldingOptimizerTest {
211217
@TestParameters("{source: '42 != 42', expected: 'false'}")
212218
@TestParameters("{source: '[\"foo\",\"bar\"] == [\"foo\",\"bar\"]', expected: 'true'}")
213219
@TestParameters("{source: '[\"bar\",\"foo\"] == [\"foo\",\"bar\"]', expected: 'false'}")
220+
@TestParameters("{source: 'duration(\"1h\") - duration(\"60m\")', expected: 'duration(\"0s\")'}")
221+
@TestParameters(
222+
"{source: 'duration(\"2h23m42s12ms42us92ns\") + duration(\"129481231298125ns\")', expected:"
223+
+ " 'duration(\"138103.243340217s\")'}")
224+
@TestParameters(
225+
"{source: 'timestamp(900000) - timestamp(100)', expected: 'duration(\"899900s\")'}")
226+
@TestParameters(
227+
"{source: 'timestamp(\"2000-01-01T00:02:03.2123Z\") + duration(\"25h2m32s42ms53us29ns\")',"
228+
+ " expected: 'timestamp(\"2000-01-02T01:04:35.254353029Z\")'}")
214229
// TODO: Support folding lists with mixed types. This requires mutable lists.
215230
// @TestParameters("{source: 'dyn([1]) + [1.0]'}")
216231
public void constantFold_success(String source, String expected) throws Exception {
@@ -348,6 +363,8 @@ public void constantFold_macros_withoutMacroCallMetadata(String source) throws E
348363
@TestParameters("{source: 'get_true() == true'}")
349364
@TestParameters("{source: 'x == x'}")
350365
@TestParameters("{source: 'x == 42'}")
366+
@TestParameters("{source: 'timestamp(100)'}")
367+
@TestParameters("{source: 'duration(\"1h\")'}")
351368
public void constantFold_noOp(String source) throws Exception {
352369
CelAbstractSyntaxTree ast = CEL.compile(source).getAst();
353370

0 commit comments

Comments
 (0)