Skip to content

Commit

Permalink
v1.7.1 - Scheduled Rollup Updates & Date literal comparisons (#641)
Browse files Browse the repository at this point in the history
* Fixes #640 by properly ensuring RollupDateLiteral comparisons occur in local time since they are always based off of the running user and their time zone
* Fixes an issue brought up in #639 where scheduling a rollup can inadvertently exceed the max query rows
  • Loading branch information
jamessimone authored Dec 2, 2024
1 parent 63ed889 commit d89e615
Show file tree
Hide file tree
Showing 14 changed files with 172 additions and 40 deletions.
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
{
"files": "*.{cls, apex}",
"options": { "parser": "apex" }
},
{
"files": "**/*.xml",
"options": { "parser": "xml" }
}
],
"plugins": ["prettier-plugin-apex", "@prettier/plugin-xml"],
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ 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=04t6g000008OfTvAAK">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OfWQAA0">
<img alt="Deploy to Salesforce" src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OfTvAAK">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OfWQAA0">
<img alt="Deploy to Salesforce Sandbox" src="./media/deploy-package-to-sandbox.png">
</a>
<br/>
Expand Down Expand Up @@ -358,7 +358,7 @@ Rollup.schedule(
'My example job name',
'my cron expression, like 0 0 0 * * ?',
'my SOQL query, like SELECT Id, Amount FROM Opportunity WHERE CreatedDate > YESTERDAY',
'The API name of the SObject associated with Rollup__mdt records configuring the rollup operation',
'The API name of the Child SObject associated with Rollup__mdt records for this schedule',
null
);
```
Expand Down
34 changes: 29 additions & 5 deletions extra-tests/classes/RollupDateLiteralTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ private class RollupDateLiteralTests {
System.assertNotEquals(true, matchingCpcs.isEmpty());
for (ContactPointConsent cpc : matchingCpcs) {
assert(literalUnderTest, cpc.CaptureDate, '=');
assert(literalUnderTest, cpc.CaptureDate.dateGmt(), '=');
}
if (sentinelPostCpc != null) {
assert(literalUnderTest, sentinelPostCpc.CaptureDate, '>');
Expand All @@ -191,6 +190,12 @@ private class RollupDateLiteralTests {
);
}

static User australiaUser {
get {
return [SELECT Id FROM User WHERE LastName = 'Australia User'];
}
}

@IsTest
static void shouldWorkForYesterday() {
runTestForLiteral('YESTERDAY');
Expand All @@ -199,32 +204,46 @@ private class RollupDateLiteralTests {
@IsTest
static void shouldWorkForToday() {
runTestForLiteral('TODAY');
System.runAs(australiaUser) {
runTestForLiteral('TODAY');
}
}

@IsTest
static void shouldWorkForTomorrow() {
runTestForLiteral('TOMORROW');
System.runAs(australiaUser) {
runTestForLiteral('TOMORROW');
}
}

@IsTest
static void shouldWorkForLastWeek() {
runTestForLiteral('LAST_WEEK');
System.runAs(australiaUser) {
runTestForLiteral('LAST_WEEK');
}
}

@IsTest
static void shouldWorkForThisWeek() {
runTestForLiteral('THIS_WEEK');
System.runAs(australiaUser) {
runTestForLiteral('THIS_WEEK');
}
}

@IsTest
static void shouldWorkForNextWeek() {
runTestForLiteral('NEXT_WEEK');
System.runAs(australiaUser) {
runTestForLiteral('NEXT_WEEK');
}
}

@IsTest
static void shouldWorkForLastMonth() {
runTestForLiteral('LAST_MONTH');
User australiaUser = [SELECT Id FROM User WHERE LastName = 'Australia User'];
System.runAs(australiaUser) {
runTestForLiteral('LAST_MONTH');
}
Expand All @@ -233,7 +252,6 @@ private class RollupDateLiteralTests {
@IsTest
static void shouldWorkForThisMonth() {
runTestForLiteral('THIS_MONTH');
User australiaUser = [SELECT Id FROM User WHERE LastName = 'Australia User'];
System.runAs(australiaUser) {
runTestForLiteral('THIS_MONTH');
}
Expand All @@ -242,7 +260,6 @@ private class RollupDateLiteralTests {
@IsTest
static void shouldWorkForNextMonth() {
runTestForLiteral('NEXT_MONTH');
User australiaUser = [SELECT Id FROM User WHERE LastName = 'Australia User'];
System.runAs(australiaUser) {
runTestForLiteral('NEXT_MONTH');
}
Expand Down Expand Up @@ -392,7 +409,14 @@ private class RollupDateLiteralTests {

@IsTest
static void shouldWorkForNextNFiscalQuarters() {
runTestForLiteral('NEXT_N_FISCAL_QUARTERS: 1');
runTestForLiteral('NEXT_N_FISCAL_QUARTERS: 2');
runTestForLiteral('NEXT_N_FISCAL_QUARTERS: 3');
runTestForLiteral('NEXT_N_FISCAL_QUARTERS: 4');
runTestForLiteral('NEXT_N_FISCAL_QUARTERS: 5');
runTestForLiteral('NEXT_N_FISCAL_QUARTERS: 6');
runTestForLiteral('NEXT_N_FISCAL_QUARTERS: 7');
runTestForLiteral('NEXT_N_FISCAL_QUARTERS: 8');
}

@IsTest
Expand Down Expand Up @@ -592,4 +616,4 @@ private class RollupDateLiteralTests {
System.assertEquals(false, weekInYear.matches(oneWeekAfter, '='));
System.assertEquals(false, weekInYear.matches(comparisonDate, '!='));
}
}
}
72 changes: 71 additions & 1 deletion extra-tests/classes/RollupIntegrationTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -1956,7 +1956,7 @@ private class RollupIntegrationTests {
Rollup.schedule('Test bad query', '0 0 0 0 0', veryBadQuery, 'Account', null);
Assert.fail('Exception should be thrown above');
} catch (Exception ex) {
Assert.isTrue(ex.getMessage().contains('field ActivityDate does not support aggregate operator MAX'));
Assert.isTrue(ex.getMessage().containsIgnoreCase('field ActivityDate does not support aggregate operator MAX'), ex.getMessage());
}
}

Expand All @@ -1968,4 +1968,74 @@ private class RollupIntegrationTests {

System.assertNotEquals(null, jobId);
}

@IsTest
static void usesFullBatchRecalculatorForLargerQueries() {
List<Account> accounts = [SELECT Id FROM Account];
RollupAsyncProcessor.stubParentRecords = accounts;
Rollup.defaultControl = new RollupControl__mdt(MaxQueryRows__c = 0);
Rollup.onlyUseMockMetadata = true;
Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
RollupFieldOnCalcItem__c = 'Id',
LookupObject__c = 'Account',
LookupFieldOnCalcItem__c = 'AccountId',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AnnualRevenue',
RollupOperation__c = 'COUNT',
CalcItem__c = 'Contact'
)
};
insert new Contact(AccountId = accounts[0].Id, LastName = 'Does Not Reset');

String jobId = Rollup.schedule('Does not Reset ' + System.now(), '0 0 0 * * ?', 'SELECT Id, AccountId FROM Contact', 'Account', null);

CronTrigger job = [SELECT CronJobDetail.Name FROM CronTrigger WHERE Id = :jobId];
Assert.isTrue(job.CronJobDetail.Name.contains(RollupFullBatchRecalculator.class.getName()));
}

@IsTest
static void doesNotResetParentRecordValuesForLargeScheduledQueries() {
Account acc = [SELECT Id FROM Account];
acc.AnnualRevenue = 1;
update acc;

Rollup.onlyUseMockMetadata = true;
Rollup.rollupMetadata = new List<Rollup__mdt>{
new Rollup__mdt(
RollupFieldOnCalcItem__c = 'Id',
LookupObject__c = 'Account',
LookupFieldOnCalcItem__c = 'AccountId',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AnnualRevenue',
RollupOperation__c = 'COUNT',
CalcItem__c = 'Contact'
)
};
insert new Contact(AccountId = acc.Id, LastName = 'Does Not Reset');

Test.startTest();
new RollupFullBatchRecalculator.NonResettingBulkFullRecalc(
'SELECT Id, AccountId\nFROM Contact',
Rollup.InvocationPoint.FROM_SCHEDULED,
new List<Rollup__mdt>{
new Rollup__mdt(
RollupFieldOnCalcItem__c = 'Id',
LookupObject__c = 'Account',
LookupFieldOnCalcItem__c = 'AccountId',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'AnnualRevenue',
RollupOperation__c = 'COUNT',
CalcItem__c = 'Contact',
CalcItemWhereClause__c = RollupCurrencyInfo.isMultiCurrency() ? RollupCurrencyInfo.CURRENCY_ISO_CODE_FIELD_NAME + ' != null' : null
)
},
Contact.SObjectType
)
.runCalc();
Test.stopTest();

acc = [SELECT AnnualRevenue FROM Account WHERE Id = :acc.Id LIMIT 1];
Assert.areEqual(2, acc.AnnualRevenue);
}
}
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.7.0",
"version": "1.7.1",
"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=04t6g000008OfU0AAK">
<a href="https://login.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OfWVAA0">
<img alt="Deploy to Salesforce"
src="./media/deploy-package-to-prod.png">
</a>

<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OfU0AAK">
<a href="https://test.salesforce.com/packaging/installPackage.apexp?p0=04t6g000008OfWVAA0">
<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": "New RollupState implementation to stop running out of memory on large full recalcs",
"versionNumber": "1.2.0.0",
"versionName": "Scheduled Rollup updates, RollupDateLiteral updates",
"versionNumber": "1.2.1.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 @@ -25,6 +25,7 @@
"[email protected]": "04t6g000008OfKnAAK",
"[email protected]": "04t6g000008OfMjAAK",
"[email protected]": "04t6g000008OfSJAA0",
"[email protected]": "04t6g000008OfU0AAK"
"[email protected]": "04t6g000008OfU0AAK",
"[email protected]": "04t6g000008OfWVAA0"
}
}
41 changes: 28 additions & 13 deletions rollup/core/classes/Rollup.cls
Original file line number Diff line number Diff line change
Expand Up @@ -917,23 +917,38 @@ global without sharing virtual class Rollup implements RollupLogger.ToStringObje
}

global static Id schedule(String jobName, String cronExp, String query, String rollupObjectName, Evaluator eval) {
List<SObject> localCalcItems;
Rollup rollToSchedule;
query = query.toUpperCase();
if (query.contains('\nFROM ') == false) {
query = query.substringBeforeLast(' FROM ') + '\nFROM ' + query.substringAfterLast(' FROM ');
}
try {
localCalcItems = new RollupRepository(RollupRepository.RunAsMode.SYSTEM_LEVEL).setQuery(query).get();
RollupRepository repo = new RollupRepository(RollupRepository.RunAsMode.SYSTEM_LEVEL).setQuery(query);
Integer queryCount = repo.getCount();
if (queryCount < CACHED_DEFAULT.MaxQueryRows__c) {
List<SObject> localCalcItems = repo.get();
rollToSchedule = getRollup(
getRollupMetadataBySObject(localCalcItems.getSObjectType()),
localCalcItems.getSObjectType(),
localCalcItems,
new Map<Id, SObject>(),
eval,
InvocationPoint.FROM_SCHEDULED
);
} else {
Schema.SObjectType childType = RollupFieldInitializer.Current.getDescribeFromName(rollupObjectName).getSObjectType();
rollToSchedule = new RollupFullBatchRecalculator.NonResettingBulkFullRecalc(
query,
InvocationPoint.FROM_SCHEDULED,
getRollupMetadataBySObject(childType),
childType
);
}
} catch (QueryException ex) {
throw new QueryException('There\'s a problem with your query: ' + ex.getMessage() + '\n' + ex.getStackTraceString());
}
SObjectType calcItemType = localCalcItems.getSObjectType();
Rollup roll = getRollup(
getRollupMetadataBySObject(calcItemType),
calcItemType,
localCalcItems,
new Map<Id, SObject>(),
eval,
InvocationPoint.FROM_SCHEDULED
);
RollupSchedulable scheduledRollup = new RollupSchedulable(roll);
return System.schedule(jobName, cronExp, scheduledRollup);
RollupSchedulable scheduledRollup = new RollupSchedulable(rollToSchedule);
return System.schedule(jobName + ' for ' + rollToSchedule.getTypeName(), cronExp, scheduledRollup);
}

global static void batch(Rollup rollup, Rollup secondRollup) {
Expand Down
12 changes: 6 additions & 6 deletions rollup/core/classes/RollupDateLiteral.cls
Original file line number Diff line number Diff line change
Expand Up @@ -258,23 +258,23 @@ public without sharing abstract class RollupDateLiteral {
protected virtual Boolean isEqualTo(Object val) {
if (val instanceof Date) {
Date dateVal = (Date) val;
return this.ref.dateGmt() <= dateVal && dateVal <= this.bound.dateGmt();
return this.ref.date() <= dateVal && dateVal <= this.bound.date();
}
Datetime datetimeVal = (Datetime) val;
return this.ref <= datetimeVal && datetimeVal <= this.bound;
}
protected virtual Boolean isGreaterThan(Object val) {
if (val instanceof Date) {
Date dateVal = (Date) val;
return dateVal > this.bound.dateGmt() && dateVal > this.ref.dateGmt();
return dateVal > this.bound.date() && dateVal > this.ref.date();
}
Datetime datetimeVal = (Datetime) val;
return datetimeVal > this.bound && datetimeVal > this.ref;
}
protected virtual Boolean isLessThan(Object val) {
if (val instanceof Date) {
Date dateVal = (Date) val;
return dateVal < this.bound.dateGmt() && dateVal < this.ref.dateGmt();
return dateVal < this.bound.date() && dateVal < this.ref.date();
}
Datetime datetimeVal = (Datetime) val;
return datetimeVal < this.bound && datetimeVal < this.ref;
Expand Down Expand Up @@ -333,7 +333,7 @@ public without sharing abstract class RollupDateLiteral {
private class ThisWeekLiteral extends RollupDateLiteral {
public ThisWeekLiteral() {
this.ref = getRelativeDatetime(System.today().toStartOfWeek(), START_TIME);
this.bound = getRelativeDatetime(this.ref.addDays(6).dateGmt(), END_TIME);
this.bound = getRelativeDatetime(this.ref.addDays(6).date(), END_TIME);
}
}

Expand All @@ -344,7 +344,7 @@ public without sharing abstract class RollupDateLiteral {
private class NextWeekLiteral extends RollupDateLiteral {
public NextWeekLiteral() {
this.ref = getRelativeDatetime(System.today().toStartOfWeek().addDays(7), START_TIME);
this.bound = getRelativeDatetime(this.ref.addDays(6).dateGmt(), END_TIME);
this.bound = getRelativeDatetime(this.ref.addDays(6).date(), END_TIME);
}
}

Expand Down Expand Up @@ -397,7 +397,7 @@ public without sharing abstract class RollupDateLiteral {
private class Last90DaysLiteral extends RollupDateLiteral {
public Last90DaysLiteral() {
this.bound = getRelativeDatetime(START_OF_TODAY.date(), END_TIME);
this.ref = getRelativeDatetime(this.bound.addDays(-91).dateGmt(), START_TIME);
this.ref = getRelativeDatetime(this.bound.addDays(-91).date(), START_TIME);
}
}

Expand Down
10 changes: 10 additions & 0 deletions rollup/core/classes/RollupFullBatchRecalculator.cls
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ public without sharing virtual class RollupFullBatchRecalculator extends RollupF

private static final Integer DEFAULT_CHUNK_SIZE = 500;

public class NonResettingBulkFullRecalc extends RollupFullBatchRecalculator {
public NonResettingBulkFullRecalc(String queryString, InvocationPoint invokePoint, List<Rollup__mdt> rollupMetas, SObjectType calcItemType) {
super(queryString, invokePoint, rollupMetas, calcItemType, new Set<String>(), null);
}

public override Boolean getShouldResetParentRecordsDuringRecalculation() {
return false;
}
}

public RollupFullBatchRecalculator(
String queryString,
InvocationPoint invokePoint,
Expand Down
Loading

0 comments on commit d89e615

Please sign in to comment.