Skip to content

Commit 1071395

Browse files
authored
Context resolution improvements (#232)
* Improve context resolution and add integration tests for query expressions * Update apexdocs dependency to stable version 3.17.0 * Refactor context resolution logic and add integration tests for child relationships
1 parent e1afe4d commit 1071395

10 files changed

Lines changed: 86 additions & 15 deletions

File tree

.idea/formula-evaluator.iml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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": "04tRb0000044fYYIA",
2+
"packageId": "04tRb0000045WPxIAM",
33
"componentPackageId": "04tRb0000012Mv8IAE"
44
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ public with sharing class ContextResolver implements Visitor {
272272
// References to @Context fields will always go to the top level query
273273
// as they are part of the global contextual context tied to the record Id
274274
// from which the Evaluation was started.
275-
addFieldToQuery(this.topLevelQuery, referenceName.toLowerCase());
275+
return addFieldToQuery(this.topLevelQuery, referenceName.toLowerCase());
276276
} else {
277277
this.queryContext.queryBuilder.selectField(referenceName);
278278
}
@@ -320,7 +320,7 @@ public with sharing class ContextResolver implements Visitor {
320320
// If context is being accessed, then we always want to run the query.
321321
this.shouldExecuteQuery = true;
322322

323-
// recordId migh be null when this is being run from within a subquery.
323+
// recordId might be null when this is being run from within a subquery.
324324
// If so, return early.
325325
if (this.queryContext.recordId == null) {
326326
return null;

expression-src/main/src/resolver/EvaluatorResolver.cls

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,12 @@ public with sharing abstract class EvaluatorResolver {
270270
ContextResolver ctxInterpreter = new ContextResolver(contextsForType, contextPrefix, customFunctionDeclarations);
271271
List<SObject> queriedRecords = ctxInterpreter.build(expressions);
272272
if (queriedRecords == null || queriedRecords.isEmpty()) {
273-
continue;
273+
queriedRecords = new List<SObject>();
274+
for (Id recordId : contextByRecordId.keySet()) {
275+
SObject emptyRecord = currentType.newSObject();
276+
emptyRecord.Id = recordId;
277+
queriedRecords.add(emptyRecord);
278+
}
274279
}
275280
Map<Id, SObject> recordById = new Map<Id, SObject>(queriedRecords);
276281
for (Id recordId : recordById.keySet()) {

package-lock.json

Lines changed: 11 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"homepage": "https://github.com/cesarParra/formula-evaluator#readme",
2626
"devDependencies": {
2727
"@cparra/apex-reflection": "^2.4.1",
28-
"@cparra/apexdocs": "3.16.1",
28+
"@cparra/apexdocs": "^3.17.0",
2929
"@tailwindcss/forms": "^0.5.6",
3030
"@types/node": "^20.10.2",
3131
"js-yaml": "^4.1.0",

sfdx-project.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
{
1212
"path": "src-pull",
1313
"default": true
14+
},
15+
{
16+
"path": "unpackaged/integration-tests",
17+
"default": false
1418
}
1519
],
1620
"name": "Expression",

sfdx-project_packaging.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{
44
"package": "Expression",
55
"versionName": "Version 1.36",
6-
"versionNumber": "1.47.0.NEXT",
6+
"versionNumber": "1.48.0.NEXT",
77
"path": "expression-src",
88
"default": false,
99
"versionDescription": "Expression core language",
@@ -76,6 +76,7 @@
7676
"Expression@1.44.0-1": "04tRb000003z0hJIAQ",
7777
"Expression@1.45.0-1": "04tRb0000042CNlIAM",
7878
"Expression@1.46.0-1": "04tRb000004400jIAA",
79-
"Expression@1.47.0-1": "04tRb0000044fYYIAY"
79+
"Expression@1.47.0-1": "04tRb0000044fYYIAY",
80+
"Expression@1.48.0-1": "04tRb0000045WPxIAM"
8081
}
8182
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
@IsTest
2+
private class IntegrationTest {
3+
@IsTest
4+
static void usingChildRelationships() {
5+
Account anyAccount = new Account(Name = 'Sample Account');
6+
insert anyAccount;
7+
Contact firstContact = new Contact(FirstName = 'First', LastName = 'Contact', AccountId = anyAccount.Id);
8+
Contact secondContact = new Contact(FirstName = 'Second', LastName = 'Contact', AccountId = anyAccount.Id);
9+
insert new List<Contact> { firstContact, secondContact };
10+
11+
String expressionPiped = '@Context.Contacts -> WHERE(FirstName = "First") -> SIZE() = 1';
12+
Boolean pipedResult = (Boolean)Evaluator.run(expressionPiped, anyAccount.Id);
13+
Assert.isTrue(pipedResult, 'There should be exactly one contact with the first name "First".');
14+
15+
String expressionNested = 'SIZE(WHERE(@Context.Contacts, FirstName = "First")) = 1';
16+
Boolean nestedResult = (Boolean)Evaluator.run(expressionNested, anyAccount.Id);
17+
Assert.isTrue(nestedResult, 'There should be exactly one contact with the first name "First".');
18+
}
19+
20+
@IsTest
21+
static void hasPurchasedSomethingInThePast() {
22+
List<CustomRecordContext> contexts = getContexts();
23+
24+
String pipedExpression = '@Customer.Assets -> WHERE(Product2Id = @Product.Id && Status = "Purchased") -> SIZE() > 0';
25+
Boolean pipedResult = (Boolean)Evaluator.run(pipedExpression, contexts, new Configuration());
26+
Assert.isTrue(pipedResult, 'The customer should have purchased the product in the past.');
27+
28+
String nestedExpression = 'SIZE(WHERE(@Customer.Assets, Product2Id = @Product.Id && Status = "Purchased")) > 0';
29+
Boolean nestedResult = (Boolean)Evaluator.run(nestedExpression, contexts, new Configuration());
30+
Assert.isTrue(nestedResult, 'The customer should have purchased the product in the past.');
31+
}
32+
33+
private static List<CustomRecordContext> getContexts() {
34+
Account someAccount = new Account(Name = 'Test Account');
35+
insert someAccount;
36+
Contact someone = new Contact(FirstName = 'Test', LastName = 'User', AccountId = someAccount.Id);
37+
insert someone;
38+
Product2 sampleProduct = new Product2(Name = 'Test Product', IsActive = true);
39+
insert sampleProduct;
40+
41+
Asset sampleAsset = new Asset(
42+
Name = 'Test Asset',
43+
ContactId = someone.Id, Status = 'Purchased',
44+
Product2Id = sampleProduct.Id);
45+
insert sampleAsset;
46+
47+
CustomRecordContext customer = new CustomRecordContext('Customer', someone.Id);
48+
CustomRecordContext product = new CustomRecordContext('Product', sampleProduct.Id);
49+
List<CustomRecordContext> contexts = new List<CustomRecordContext> { customer, product };
50+
return contexts;
51+
}
52+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>65.0</apiVersion>
4+
<status>Active</status>
5+
</ApexClass>

0 commit comments

Comments
 (0)