diff --git a/.prettierrc b/.prettierrc
index a3223d26..5c665af8 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -16,6 +16,10 @@
{
"files": "*.{cls, apex}",
"options": { "parser": "apex" }
+ },
+ {
+ "files": "**/*.xml",
+ "options": { "parser": "xml" }
}
],
"plugins": ["prettier-plugin-apex", "@prettier/plugin-xml"],
diff --git a/README.md b/README.md
index 97e2bf6e..87370b2b 100644
--- a/README.md
+++ b/README.md
@@ -24,11 +24,11 @@ As well, don't miss [the Wiki](../../wiki), which includes even more info for co
## Deployment & Setup
-
+
-
+
@@ -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
);
```
diff --git a/extra-tests/classes/RollupDateLiteralTests.cls b/extra-tests/classes/RollupDateLiteralTests.cls
index 4ed69395..7965100b 100644
--- a/extra-tests/classes/RollupDateLiteralTests.cls
+++ b/extra-tests/classes/RollupDateLiteralTests.cls
@@ -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, '>');
@@ -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');
@@ -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');
}
@@ -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');
}
@@ -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');
}
@@ -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
@@ -592,4 +616,4 @@ private class RollupDateLiteralTests {
System.assertEquals(false, weekInYear.matches(oneWeekAfter, '='));
System.assertEquals(false, weekInYear.matches(comparisonDate, '!='));
}
-}
+}
\ No newline at end of file
diff --git a/extra-tests/classes/RollupIntegrationTests.cls b/extra-tests/classes/RollupIntegrationTests.cls
index 922227b0..45a61aec 100644
--- a/extra-tests/classes/RollupIntegrationTests.cls
+++ b/extra-tests/classes/RollupIntegrationTests.cls
@@ -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());
}
}
@@ -1968,4 +1968,74 @@ private class RollupIntegrationTests {
System.assertNotEquals(null, jobId);
}
+
+ @IsTest
+ static void usesFullBatchRecalculatorForLargerQueries() {
+ List accounts = [SELECT Id FROM Account];
+ RollupAsyncProcessor.stubParentRecords = accounts;
+ Rollup.defaultControl = new RollupControl__mdt(MaxQueryRows__c = 0);
+ Rollup.onlyUseMockMetadata = true;
+ Rollup.rollupMetadata = new List{
+ 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{
+ 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{
+ 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);
+ }
}
diff --git a/package.json b/package.json
index 4a7a79d3..ff4b72c8 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/rollup-namespaced/README.md b/rollup-namespaced/README.md
index 24a4c628..9d8a5e85 100644
--- a/rollup-namespaced/README.md
+++ b/rollup-namespaced/README.md
@@ -18,12 +18,12 @@ For more info, see the base `README`.
## Deployment & Setup
-
+
-
+
diff --git a/rollup-namespaced/sfdx-project.json b/rollup-namespaced/sfdx-project.json
index dbdb0df5..c4eb03ec 100644
--- a/rollup-namespaced/sfdx-project.json
+++ b/rollup-namespaced/sfdx-project.json
@@ -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": {
@@ -25,6 +25,7 @@
"apex-rollup-namespaced@1.1.28": "04t6g000008OfKnAAK",
"apex-rollup-namespaced@1.1.29": "04t6g000008OfMjAAK",
"apex-rollup-namespaced@1.1.30": "04t6g000008OfSJAA0",
- "apex-rollup-namespaced@1.2.0": "04t6g000008OfU0AAK"
+ "apex-rollup-namespaced@1.2.0": "04t6g000008OfU0AAK",
+ "apex-rollup-namespaced@1.2.1": "04t6g000008OfWVAA0"
}
}
diff --git a/rollup/core/classes/Rollup.cls b/rollup/core/classes/Rollup.cls
index f573e532..34a5b76b 100644
--- a/rollup/core/classes/Rollup.cls
+++ b/rollup/core/classes/Rollup.cls
@@ -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 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 localCalcItems = repo.get();
+ rollToSchedule = getRollup(
+ getRollupMetadataBySObject(localCalcItems.getSObjectType()),
+ localCalcItems.getSObjectType(),
+ localCalcItems,
+ new Map(),
+ 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(),
- 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) {
diff --git a/rollup/core/classes/RollupDateLiteral.cls b/rollup/core/classes/RollupDateLiteral.cls
index 54b43dad..8575b8ba 100644
--- a/rollup/core/classes/RollupDateLiteral.cls
+++ b/rollup/core/classes/RollupDateLiteral.cls
@@ -258,7 +258,7 @@ 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;
@@ -266,7 +266,7 @@ public without sharing abstract class RollupDateLiteral {
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;
@@ -274,7 +274,7 @@ public without sharing abstract class RollupDateLiteral {
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;
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
diff --git a/rollup/core/classes/RollupFullBatchRecalculator.cls b/rollup/core/classes/RollupFullBatchRecalculator.cls
index 5e0d8dae..d27c70cc 100644
--- a/rollup/core/classes/RollupFullBatchRecalculator.cls
+++ b/rollup/core/classes/RollupFullBatchRecalculator.cls
@@ -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 rollupMetas, SObjectType calcItemType) {
+ super(queryString, invokePoint, rollupMetas, calcItemType, new Set(), null);
+ }
+
+ public override Boolean getShouldResetParentRecordsDuringRecalculation() {
+ return false;
+ }
+ }
+
public RollupFullBatchRecalculator(
String queryString,
InvocationPoint invokePoint,
diff --git a/rollup/core/classes/RollupFullRecalcProcessor.cls b/rollup/core/classes/RollupFullRecalcProcessor.cls
index 23d6fd18..13fee11d 100644
--- a/rollup/core/classes/RollupFullRecalcProcessor.cls
+++ b/rollup/core/classes/RollupFullRecalcProcessor.cls
@@ -165,13 +165,17 @@ global abstract without sharing class RollupFullRecalcProcessor extends RollupAs
}
for (RollupAsyncProcessor innerRoll : processor.rollups) {
innerRoll.fullRecalcProcessor = this;
- innerRoll.isFullRecalc = true;
+ innerRoll.isFullRecalc = this.getShouldResetParentRecordsDuringRecalculation();
innerRoll.calcItems = calcItems;
}
this.fullRecalcProcessor = this;
return processor.rollups;
}
+ protected virtual Boolean getShouldResetParentRecordsDuringRecalculation() {
+ return true;
+ }
+
protected virtual override Map customizeToStringEntries(Map props) {
super.customizeToStringEntries(props);
Integer numberOfRollups = this.rollupMetas?.size();
@@ -207,4 +211,4 @@ global abstract without sharing class RollupFullRecalcProcessor extends RollupAs
this.runAsMode = RollupMetaPicklists.getAccessLevel(meta);
}
}
-}
+}
\ No newline at end of file
diff --git a/rollup/core/classes/RollupLogger.cls b/rollup/core/classes/RollupLogger.cls
index d83285d7..7d02cb3c 100644
--- a/rollup/core/classes/RollupLogger.cls
+++ b/rollup/core/classes/RollupLogger.cls
@@ -1,7 +1,7 @@
global without sharing virtual class RollupLogger implements ILogger {
@TestVisible
// this gets updated via the pipeline as the version number gets incremented
- private static final String CURRENT_VERSION_NUMBER = 'v1.7.0';
+ private static final String CURRENT_VERSION_NUMBER = 'v1.7.1';
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
private static final RollupPlugin PLUGIN = new RollupPlugin();
diff --git a/rollup/core/classes/RollupRepository.cls b/rollup/core/classes/RollupRepository.cls
index adac329d..e1c072df 100644
--- a/rollup/core/classes/RollupRepository.cls
+++ b/rollup/core/classes/RollupRepository.cls
@@ -62,10 +62,12 @@ public without sharing class RollupRepository implements RollupLogger.ToStringOb
}
public Integer getCount() {
+ String originalQuery = this.args.query;
if (this.args.query.contains(RollupQueryBuilder.ALL_ROWS)) {
this.args.query = this.args.query.replace(RollupQueryBuilder.ALL_ROWS, '');
}
this.args.query = this.args.query.replaceFirst('SELECT.+\n', 'SELECT Count()\n');
+ this.createQueryLog('Getting count');
Integer countAmount;
try {
@@ -77,6 +79,7 @@ public without sharing class RollupRepository implements RollupLogger.ToStringOb
countAmount = SENTINEL_COUNT_VALUE;
}
this.createQueryLog('Returned amount: ' + countAmount);
+ this.args.query = originalQuery;
return countAmount;
}
diff --git a/sfdx-project.json b/sfdx-project.json
index 1476c772..8d89ff52 100644
--- a/sfdx-project.json
+++ b/sfdx-project.json
@@ -5,8 +5,8 @@
"package": "apex-rollup",
"path": "rollup",
"scopeProfiles": true,
- "versionName": "New RollupState implementation to stop running out of memory on large full recalcs",
- "versionNumber": "1.7.0.0",
+ "versionName": "Scheduled Rollup updates, RollupDateLiteral updates",
+ "versionNumber": "1.7.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": {
@@ -106,6 +106,7 @@
"apex-rollup@1.6.35": "04t6g000008OfKiAAK",
"apex-rollup@1.6.36": "04t6g000008OfMeAAK",
"apex-rollup@1.6.37": "04t6g000008OfSEAA0",
- "apex-rollup@1.7.0": "04t6g000008OfTvAAK"
+ "apex-rollup@1.7.0": "04t6g000008OfTvAAK",
+ "apex-rollup@1.7.1": "04t6g000008OfWQAA0"
}
}