Skip to content

Commit

Permalink
v1.5.86 - OR clause bugfix and full recalc update (#503)
Browse files Browse the repository at this point in the history
* Fixes #494 by updating where clause query parsing to prevent batch apex from running unexpectedly for nested OR clauses
* Making some notes on WEEK_IN_YEAR date literal to come back to later
* Optimizations, added a test on full recalc path to ensure parent reset processor only runs when it's supposed to
* Fixes #500 by properly handling the stateful maintenance of previously reset parents when children span across multiple batches
  • Loading branch information
jamessimone authored Sep 25, 2023
1 parent 4fb5d71 commit 0a6567a
Show file tree
Hide file tree
Showing 19 changed files with 222 additions and 112 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ As well, don't miss [the Wiki](../../wiki), which includes even more info for co

## Deployment & Setup

<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008C6zWAAS">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008C71XAAS">
<img alt="Deploy to Salesforce"
src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008C6zWAAS">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008C71XAAS">
<img alt="Deploy to Salesforce Sandbox"
src="./media/deploy-package-to-sandbox.png">
</a>
Expand Down
1 change: 1 addition & 0 deletions extra-tests/classes/RollupDateLiteralTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ private class RollupDateLiteralTests {
comparisonDate = Datetime.newInstance(2020, 12, 22);
oneWeekBefore = comparisonDate.addDays(-7);
oneWeekAfter = comparisonDate.addDays(7);
// TODO according to SOQL, the below should actually be 51
weekInYear = RollupDateLiteral.getFunction('WEEK_IN_YEAR(CreatedDate)', '52');

System.assertEquals(true, weekInYear.matches(comparisonDate, '='), weekInYear);
Expand Down
49 changes: 40 additions & 9 deletions extra-tests/classes/RollupFullRecalcTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -1663,12 +1663,21 @@ private class RollupFullRecalcTests {
IsRollupLoggingEnabled__c = true,
BatchChunkSize__c = 100
);
Account acc = [SELECT Id FROM Account];
Account parentA = [SELECT Id FROM Account];
Account parentB = new Account(Name = 'Parent B');
Account parentC = new Account(Name = 'Parent C');
insert new List<SObject>{ parentB, parentC };

List<ContactPointAddress> cpas = new List<ContactPointAddress>{
new ContactPointAddress(ParentId = acc.Id, PreferenceRank = 1, Name = 'One'),
new ContactPointAddress(ParentId = acc.Id, PreferenceRank = 2, Name = 'Two'),
new ContactPointAddress(ParentId = acc.Id, PreferenceRank = 3, Name = 'Three')
new ContactPointAddress(ParentId = parentA.Id, PreferenceRank = 1, Name = 'One A', UsageType = '1'),
new ContactPointAddress(ParentId = parentB.Id, PreferenceRank = 1, Name = 'One B', UsageType = '1'),
new ContactPointAddress(ParentId = parentC.Id, PreferenceRank = 1, Name = 'One C', UsageType = '1'),
new ContactPointAddress(ParentId = parentA.Id, PreferenceRank = 2, Name = 'Two A', UsageType = '1'),
new ContactPointAddress(ParentId = parentB.Id, PreferenceRank = 2, Name = 'Two B', UsageType = '1'),
new ContactPointAddress(ParentId = parentC.Id, PreferenceRank = 2, Name = 'Two C', UsageType = '1'),
new ContactPointAddress(ParentId = parentA.Id, PreferenceRank = 3, Name = 'Three A', UsageType = '2'),
new ContactPointAddress(ParentId = parentB.Id, PreferenceRank = 3, Name = 'Three B', UsageType = '2'),
new ContactPointAddress(ParentId = parentC.Id, PreferenceRank = 3, Name = 'Three C', UsageType = '2')
};
insert cpas;

Expand All @@ -1684,6 +1693,24 @@ private class RollupFullRecalcTests {
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AnnualRevenue',
RollupOperation__c = 'SUM'
),
new Rollup__mdt(
CalcItem__c = 'ContactPointAddress',
RollupFieldOnCalcItem__c = 'PreferenceRank',
LookupFieldOnCalcItem__c = 'ParentId',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'NumberOfEmployees',
RollupOperation__c = 'FIRST'
),
new Rollup__mdt(
CalcItem__c = 'ContactPointAddress',
RollupFieldOnCalcItem__c = 'UsageType',
LookupFieldOnCalcItem__c = 'ParentId',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'Description',
RollupOperation__c = 'MOST'
)
},
ContactPointAddress.SObjectType,
Expand All @@ -1692,13 +1719,17 @@ private class RollupFullRecalcTests {
);

Test.startTest();
fullRecalc.execute(null, new List<ContactPointAddress>{ cpas[0] });
fullRecalc.execute(null, new List<ContactPointAddress>{ cpas[1] });
fullRecalc.execute(null, new List<ContactPointAddress>{ cpas[2] });
fullRecalc.execute(null, new List<ContactPointAddress>{ cpas[0], cpas[1], cpas[2] });
fullRecalc.execute(null, new List<ContactPointAddress>{ cpas[3], cpas[4], cpas[5] });
fullRecalc.execute(null, new List<ContactPointAddress>{ cpas[6], cpas[7], cpas[8] });
Test.stopTest();

acc = [SELECT AnnualRevenue FROM Account];
System.assertEquals(6, acc.AnnualRevenue, 'Should have included all calc items without double-counting or incorrectly summing');
for (Account parent : [SELECT AnnualRevenue, Description, NumberOfEmployees, Name FROM Account]) {
System.assertEquals(6, parent.AnnualRevenue, parent);
// TODO - add functionality for FIRST/LAST/MOST etc rollups
// System.assertEquals(1, parent.NumberOfEmployees, parent);
// System.assertEquals('1', parent.Description, parent);
}
System.assertEquals(true, Rollup.CACHED_ROLLUPS.isEmpty(), 'all deferrals should have been run');
}

Expand Down
31 changes: 31 additions & 0 deletions extra-tests/classes/RollupIntegrationTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,37 @@ private class RollupIntegrationTests {
System.assert(true, 'Should make it here');
}

@IsTest
static void shouldWorkMultipleOrInWhereClause() {
Account acc = [SELECT Id, Name FROM Account];

insert new List<Opportunity>{
new Opportunity(StageName = 'b', CloseDate = System.today(), AccountId = acc.Id, Name = 'a', Amount = 2),
new Opportunity(StageName = 'a', CloseDate = System.today(), AccountId = acc.Id, Name = 'b', Amount = 2),
new Opportunity(StageName = 'c', CloseDate = System.today().addDays(-2), AccountId = acc.Id, Name = 'c', Amount = 2),
new Opportunity(StageName = 'd', CloseDate = System.today().addDays(2), AccountId = acc.Id, Name = 'd', Amount = 2)
};

Test.startTest();
Rollup__mdt mdt = new Rollup__mdt(
CalcItem__c = 'Opportunity',
LookupObject__c = 'Account',
RollupFieldOnCalcItem__c = 'StageName',
LookupFieldOnCalcItem__c = 'AccountId',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'SicDesc',
RollupOperation__c = 'LAST',
CalcItemWhereClause__c = '(StageName IN (\'a\', \'b\') AND CloseDate = TODAY) OR (StageName = \'d\' AND CloseDate = TODAY)'
);
Rollup.performFullRecalculation(mdt);
Test.stopTest();

acc = [SELECT SicDesc FROM Account];
System.assertEquals('b', acc.SicDesc);
// Validate that job ran as queueable - that the where clause was formatted correctly, in other words
System.assertEquals('Completed', [SELECT Status FROM AsyncApexJob WHERE JobType = 'Queueable' LIMIT 1]?.Status);
}

/** Schedulable tests */
@IsTest
static void shouldThrowExceptionForBadQuery() {
Expand Down
57 changes: 54 additions & 3 deletions extra-tests/classes/RollupParentResetProcessorTests.cls
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
@IsTest
private class RollupParentResetProcessorTests {
@TestSetup
static void setup() {
upsert new RollupSettings__c(IsEnabled__c = true);
}

@IsTest
static void shouldNotFailWhenRollupFieldNotFilterable() {
RollupParentResetProcessor processor = new RollupParentResetProcessor(
Expand Down Expand Up @@ -117,8 +122,18 @@ private class RollupParentResetProcessorTests {
insert new List<SObject>{ new Account(Name = 'Account With Null'), new Contact(LastName = 'Contact With Null') };
RollupParentResetProcessor processor = new RollupParentResetProcessor(
new List<Rollup__mdt>{
new Rollup__mdt(RollupFieldOnLookupObject__c = 'AnnualRevenue', LookupObject__c = 'Account', FullRecalculationDefaultNumberValue__c = 0),
new Rollup__mdt(RollupFieldOnLookupObject__c = 'AccountNumber', LookupObject__c = 'Account', FullRecalculationDefaultStringValue__c = 'a')
new Rollup__mdt(
RollupFieldOnLookupObject__c = 'AnnualRevenue',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
FullRecalculationDefaultNumberValue__c = 0
),
new Rollup__mdt(
RollupFieldOnLookupObject__c = 'AccountNumber',
LookupObject__c = 'Account',
LookupFieldOnLookupObject__c = 'Id',
FullRecalculationDefaultStringValue__c = 'a'
)
},
Account.SObjectType,
'SELECT Id\nFROM Account WHERE Id != null',
Expand All @@ -135,7 +150,6 @@ private class RollupParentResetProcessorTests {

@IsTest
static void skipsTransformWhenDisabled() {
upsert new RollupSettings__c(IsEnabled__c = true);
Rollup.defaultControl = Rollup.getDefaultControl();
Rollup.defaultControl.ShouldSkipResettingParentFields__c = true;
Rollup.defaultControl.ShouldRunAs__c = RollupMetaPicklists.ShouldRunAs.Synchronous;
Expand All @@ -154,4 +168,41 @@ private class RollupParentResetProcessorTests {

System.assertEquals(0, [SELECT COUNT() FROM AsyncApexJob WHERE ApexClass.Name = :RollupParentResetProcessor.class.getName()]);
}

@IsTest
static void shouldNotResetWhenParentAlreadyMatchesDuringFullRecalc() {
Decimal originalValue = 3;
Account acc = new Account(AnnualRevenue = originalValue, Name = 'Should Not Reset');
insert acc;

Opportunity opp = new Opportunity(AccountId = acc.Id, Amount = 100, Name = 'Will Not Match', CloseDate = System.today().addDays(2), StageName = 'A');
insert new List<Opportunity>{
new Opportunity(AccountId = acc.Id, Amount = originalValue - 1, Name = 'First Match', CloseDate = System.today().addDays(-2), StageName = 'A'),
new Opportunity(AccountId = acc.Id, Amount = originalValue - 2, Name = 'Second Match', CloseDate = System.today().addDays(-2), StageName = 'A'),
opp
};

Test.startTest();
Rollup.performBulkFullRecalc(
new List<Rollup__mdt>{
new Rollup__mdt(
RollupFieldOnCalcItem__c = 'Amount',
LookupObject__c = 'Account',
LookupFieldOnCalcItem__c = 'AccountId',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AnnualRevenue',
RollupOperation__c = 'SUM',
CalcItem__c = 'Opportunity',
IsFullRecordSet__c = true,
CalcItemWhereClause__c = 'CloseDate <= TODAY'
)
},
Rollup.InvocationPoint.FROM_FULL_RECALC_LWC.name()
);

Test.stopTest();

Account updatedAcc = [SELECT AnnualRevenue FROM Account WHERE Id = :acc.Id];
System.assertEquals(originalValue, updatedAcc.AnnualRevenue);
}
}
13 changes: 13 additions & 0 deletions extra-tests/classes/RollupQueryBuilderTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,17 @@ private class RollupQueryBuilderTests {

System.assertEquals(true, tasks.isEmpty(), 'Should have made it here and query should have been run');
}

@IsTest
static void properlyFormatsOrWithAndClause() {
String whereClause = '(StageName = \'one\' AND CloseDate = TODAY) OR (StageName = \'four\' AND CloseDate = TODAY)';

String actualQuery = RollupQueryBuilder.Current.getQuery(Opportunity.SObjectType, new List<String>{ 'COUNT()' }, 'Id', '!=', whereClause);

System.assertEquals(0, Database.countQuery(actualQuery), 'Validation that query is syntactically valid');
System.assertEquals(
'SELECT count()\nFROM Opportunity\nWHERE Id != :objIds\nAND ((StageName = \'one\' AND CloseDate = TODAY) OR (StageName = \'four\' AND CloseDate = TODAY))',
actualQuery
);
}
}
6 changes: 1 addition & 5 deletions extra-tests/classes/RollupSObjectUpdaterTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,14 @@ public class RollupSObjectUpdaterTests {
System.assertEquals(String.valueOf(blobValue), opp.Description);
}

@SuppressWarnings('PMD.UnusedLocalVariable')
@IsTest
static void onlyUpdatesRecordsWithRollupChanges() {
Account acc = new Account(Name = 'Should Not Be Updated');
insert acc;
acc = [SELECT Id, LastModifiedDate FROM Account];
waitSeconds(1);

List<String> fieldNames = new List<String>{ 'Id' };

Id accId = acc.Id;
new RollupSObjectUpdater().doUpdate(Database.query('SELECT ' + String.join(fieldNames, ',') + ' FROM Account WHERE Id = :accId'));
new RollupSObjectUpdater().doUpdate([SELECT Id FROM Account WHERE Id = :acc.Id]);

Account updatedAccount = [SELECT Id, LastModifiedDate FROM Account];
System.assertEquals(acc, updatedAccount, 'Last modified date should not have updated if only Id was passed');
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apex-rollup",
"version": "1.5.85",
"version": "1.5.86",
"description": "Fast, configurable, elastically scaling custom rollup solution. Apex Invocable action, one-liner Apex trigger/CMDT-driven logic, and scheduled Apex-ready.",
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions rollup-namespaced/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ For more info, see the base `README`.

## Deployment & Setup

<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008C6zbAAC">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008C71cAAC">
<img alt="Deploy to Salesforce"
src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008C6zbAAC">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008C71cAAC">
<img alt="Deploy to Salesforce Sandbox"
src="./media/deploy-package-to-sandbox.png">
</a>
7 changes: 4 additions & 3 deletions rollup-namespaced/sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"default": true,
"package": "apex-rollup-namespaced",
"path": "rollup-namespaced/source/rollup",
"versionName": "COUNT-based rollup bugfixes",
"versionNumber": "1.0.50.0",
"versionName": "Fixes where clause parsing for nested OR statements",
"versionNumber": "1.0.51.0",
"versionDescription": "Fast, configurable, elastically scaling custom rollup solution. Apex Invocable action, one-liner Apex trigger/CMDT-driven logic, and scheduled Apex-ready.",
"releaseNotesUrl": "https://github.com/jamessimone/apex-rollup/releases/latest",
"unpackagedMetadata": {
Expand All @@ -30,6 +30,7 @@
"apex-rollup-namespaced@1.0.47-0": "04t6g000008C6lcAAC",
"apex-rollup-namespaced@1.0.48-0": "04t6g000008C6rMAAS",
"apex-rollup-namespaced@1.0.49-0": "04t6g000008C6stAAC",
"apex-rollup-namespaced@1.0.50-0": "04t6g000008C6zbAAC"
"apex-rollup-namespaced@1.0.50-0": "04t6g000008C6zbAAC",
"apex-rollup-namespaced@1.0.51-0": "04t6g000008C71cAAC"
}
}
Loading

0 comments on commit 0a6567a

Please sign in to comment.