Skip to content

Commit f498b27

Browse files
authored
Function call cache (#222)
* Add import-data command to start-dev workflow * Implement equality and hashCode methods for Literal expressions and add unit tests for expression comparison * Implement equals and hashCode methods for StringLiteral and add unit tests for equality comparison * Implement equals and hashCode methods for Variable class and add unit tests for variable equality comparison * Implement equals and hashCode methods for MergeFieldOrScopeVariable and add unit tests for equality comparison * Implement equals and hashCode methods for Logical expressions and add unit tests for equality comparison * Implement equals and hashCode methods for Binary expressions and add unit tests for equality comparison * Implement equals and hashCode methods for Grouping expressions and add unit tests for equality comparison * Implement equals and hashCode methods for FunctionCall and add unit tests for equality comparison * Implement equals and hashCode methods for various expression types and add unit tests for equality comparison * Implement caching for function calls and update hashCode method for Expr class * Remove debug statements from function execution in FunctionCaller and add a TODO comment for handling line spreads in array literals in Interpreter * Implement caching for query results * Implement caching for function calls and query results in EnvironmentCache class * `nocache` keyword deprecation * Revert "`nocache` keyword deprecation" This reverts commit cb839b8. * Add caching configuration and disable function result caching option * Rename caching configuration for clarity and update related methods * Implement caching control for custom functions and update related classes * Add caching documentation and update navigation * Releasing a new package version
1 parent cacc022 commit f498b27

37 files changed

Lines changed: 1026 additions & 107 deletions

docs/public/packages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"packageId": "04tRb000003tVPdIAM",
2+
"packageId": "04tRb000003xe4XIAQ",
33
"componentPackageId": "04tRb0000012Mv8IAE"
44
}

docs/src/app/docs/caching/page.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
title: Caching
3+
nextjs:
4+
metadata:
5+
title: Caching
6+
description: Learn how caching works and how to control it.
7+
---
8+
9+
By default, all calls to Expression built-in functions are cached for performance.
10+
This means that if you call the same function with the same arguments multiple times,
11+
the result will be retrieved from the cache instead of being recalculated.
12+
13+
For example, if you have an Expression that for a given reason executes the same `Query`
14+
twice with the same parameters, the second execution will return the cached result of the first execution,
15+
preventing the Salesforce governor limits from being hit.
16+
17+
This aggressive caching is generally safe, because all Expression functions are pure (give the same
18+
output for the same input, without side effects). Caching is also contextual, meaning that if you
19+
call the `expression.Evaluator.run` method multiple times with different contexts, the caches will not
20+
interfere with each other, even if you have the same function calls in different expressions.
21+
22+
But there are situations where you might want to disable caching, for example, if you have a custom
23+
function that performs non-deterministic operations (like generating random numbers) or side effects, like
24+
DML operations. For these cases, you can control the caching behavior in the following ways:
25+
26+
## Global Caching
27+
28+
Global caching is enabled by default for all Expression built-in (standard) functions. You can
29+
disable it by calling the `disableStandardFunctionResultCaching` method of the `Configuration` object:
30+
31+
```apex
32+
expression.Configuration config = new expression.Configuration()
33+
.disableStandardFunctionResultCaching();
34+
expression.Evaluator.run('your expression here', config);
35+
```
36+
37+
## User-defined Function Caching
38+
39+
You can control cache independently for any function you declare yourself, by using the `nocache` keyword.
40+
See [Declaring Functions](./declaring-functions) for more information.
41+
42+
But be aware that if global caching is enabled, the `nocache` keyword will have no effect on built-in functions
43+
called from within your custom function. These will still be cached.
44+
45+
## Custom Metadata Type Functions
46+
47+
If you are using [Custom Metadata Type Functions](./custom-functions), you can control caching by setting
48+
the `Cache Result` field on the Custom Metadata record to `false`. This flag is independent of the global caching
49+
setting,
50+
so regardless of whether global caching is enabled or disabled, setting this flag will enable/disable caching for that
51+
specific function.

docs/src/lib/navigation.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
"title": "Configuring the Evaluation",
3232
"href": "/docs/configuring-the-evaluation"
3333
},
34+
{
35+
"title": "Caching",
36+
"href": "/docs/caching"
37+
},
3438
{
3539
"title": "Debugging",
3640
"href": "/docs/debugging"
@@ -214,4 +218,4 @@
214218
}
215219
]
216220
}
217-
]
221+
]

expression-src/main/api/Configuration.cls

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ global with sharing class Configuration {
1111
global SharingMode sharing = SharingMode.WITH;
1212
global Boolean printAst = false;
1313
global Map<String, Object> customContext = new Map<String, Object>();
14+
global Boolean cacheStandardFunctionResults = true;
1415
public Boolean withDiagnostics = false;
1516

1617
global Configuration respectSharing(Boolean respect) {
@@ -28,6 +29,11 @@ global with sharing class Configuration {
2829
return this;
2930
}
3031

32+
global Configuration disableStandardFunctionResultCaching() {
33+
cacheStandardFunctionResults = false;
34+
return this;
35+
}
36+
3137
global Configuration withCustomContext(Map<String, Object> objectsByStrings) {
3238
Map<String, Object> objectToSet = objectsByStrings ?? new Map<String, Object>();
3339
this.customContext = objectToSet;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3-
<apiVersion>60.0</apiVersion>
3+
<apiVersion>65.0</apiVersion>
44
<status>Active</status>
55
</ApexClass>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3-
<apiVersion>60.0</apiVersion>
3+
<apiVersion>65.0</apiVersion>
44
<status>Active</status>
55
</ApexClass>

expression-src/main/schema/layouts/Expression_Function__mdt-Expression Function Layout.layout-meta.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
<behavior>Edit</behavior>
1919
<field>Apex_Class__c</field>
2020
</layoutItems>
21+
<layoutItems>
22+
<behavior>Edit</behavior>
23+
<field>CacheResult__c</field>
24+
</layoutItems>
2125
</layoutColumns>
2226
<layoutColumns>
2327
<layoutItems>
@@ -55,6 +59,8 @@
5559
<detailHeading>false</detailHeading>
5660
<editHeading>false</editHeading>
5761
<layoutColumns/>
62+
<layoutColumns/>
63+
<layoutColumns/>
5864
<style>CustomLinks</style>
5965
</layoutSections>
6066
<showEmailCheckbox>false</showEmailCheckbox>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<fullName>CacheResult__c</fullName>
4+
<defaultValue>true</defaultValue>
5+
<description>Whether the result of evaluating this function should be cached or not. If cached, after the function is called the first time, the first result will be returned for any subsequent calls.</description>
6+
<fieldManageability>DeveloperControlled</fieldManageability>
7+
<inlineHelpText>Whether the result of evaluating this function should be cached or not. If cached, after the function is called the first time, the first result will be returned for any subsequent calls.</inlineHelpText>
8+
<label>Cache Result</label>
9+
<type>Checkbox</type>
10+
</CustomField>

expression-src/main/src/interpreter/ContextResolver.cls

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ public with sharing class ContextResolver implements Visitor {
252252
}
253253

254254
// See if the function exists as a Custom Metadata function
255-
IExpressionFunction customMetadataFunction = CustomFunctionRepository.getInstance().getByName(function.functionName);
255+
CustomMetadataExpressionFunction customMetadataFunction =
256+
CustomFunctionRepository.getInstance().getByName(function.functionName);
256257
if (customMetadataFunction != null) {
257258
// Resolve arguments
258259
for (Expr argument : function.arguments) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3-
<apiVersion>60.0</apiVersion>
3+
<apiVersion>65.0</apiVersion>
44
<status>Active</status>
55
</ApexClass>

0 commit comments

Comments
 (0)