diff --git a/README.md b/README.md
index 9c2a6a5c..9c4cabc7 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
-
+
-
+
diff --git a/extra-tests/classes/RollupCalcItemReplacerTests.cls b/extra-tests/classes/RollupCalcItemReplacerTests.cls
index a778ebe7..526838d5 100644
--- a/extra-tests/classes/RollupCalcItemReplacerTests.cls
+++ b/extra-tests/classes/RollupCalcItemReplacerTests.cls
@@ -196,6 +196,25 @@ private class RollupCalcItemReplacerTests {
Assert.areEqual(0, records.size());
}
+ @IsTest
+ static void doesNotUseAndForMultipleParentWhereClauses() {
+ Account acc = [SELECT Id FROM Account];
+ RollupCalcItemReplacer replacer = new RollupCalcItemReplacer(
+ new RollupControl__mdt(IsRollupLoggingEnabled__c = true, ReplaceCalcItemsAsyncWhenOverCount__c = 3)
+ );
+
+ List records = replacer.replace(
+ new List{ acc, acc },
+ new List{
+ new Rollup__mdt(CalcItemWhereClause__c = 'Id != \'' + acc.Id + '\' AND Owner.Name = null', CalcItem__c = 'Account'),
+ new Rollup__mdt(CalcItemWhereClause__c = 'Owner.Name != null AND Type != null', CalcItem__c = 'Account')
+ }
+ );
+
+ Assert.areEqual(1, records.size());
+ Assert.areEqual(acc.Id, records[0].Id);
+ }
+
@IsTest
static void doesNotReplaceForCustomTypeFields() {
List accounts = [SELECT Id FROM Account];
diff --git a/extra-tests/classes/RollupIntegrationTests.cls b/extra-tests/classes/RollupIntegrationTests.cls
index 45a61aec..fe65155d 100644
--- a/extra-tests/classes/RollupIntegrationTests.cls
+++ b/extra-tests/classes/RollupIntegrationTests.cls
@@ -1,5 +1,5 @@
@IsTest
-private class RollupIntegrationTests {
+private class RollupIntegrationTests implements Database.Batchable {
@TestSetup
static void setup() {
Rollup.onlyUseMockMetadata = true;
@@ -2038,4 +2038,47 @@ private class RollupIntegrationTests {
acc = [SELECT AnnualRevenue FROM Account WHERE Id = :acc.Id LIMIT 1];
Assert.areEqual(2, acc.AnnualRevenue);
}
+
+ @IsTest
+ static void startingFromBatchDoesNotBlowUpCursorBasedFullRecalc() {
+ Test.startTest();
+ Database.executeBatch(new RollupIntegrationTests());
+ Test.stopTest();
+
+ Assert.areEqual(1, [SELECT AnnualRevenue FROM Account].AnnualRevenue);
+ }
+
+ public List start(Database.BatchableContext bc) {
+ return [SELECT Id FROM Organization LIMIT 1];
+ }
+
+ public void execute(Database.BatchableContext bc, List records) {
+ Rollup.defaultControl = new RollupControl__mdt(
+ BatchChunkSize__c = 1,
+ MaxLookupRowsBeforeBatching__c = 0,
+ IsRollupLoggingEnabled__c = true,
+ MaxRollupRetries__c = 1
+ );
+ Rollup.onlyUseMockMetadata = true;
+ Rollup.rollupMetadata = new List{
+ new Rollup__mdt(
+ RollupFieldOnCalcItem__c = 'Id',
+ LookupObject__c = 'Account',
+ LookupFieldOnCalcItem__c = 'ParentId',
+ LookupFieldOnLookupObject__c = 'Id',
+ RollupFieldOnLookupObject__c = 'AnnualRevenue',
+ RollupOperation__c = 'REFRESH_COUNT',
+ CalcItem__c = 'ContactPointAddress'
+ )
+ };
+ ContactPointAddress con = new ContactPointAddress(ParentId = [SELECT Id FROM Account LIMIT 1].Id, Name = 'Child');
+ insert con;
+ Rollup.apexContext = TriggerOperation.AFTER_INSERT;
+ rollup.records = new List{ con };
+ rollup.shouldRun = true;
+ Rollup.runFromTrigger();
+ }
+
+ public void finish(Database.BatchableContext bc) {
+ }
}
diff --git a/package.json b/package.json
index 8f0afd22..5e57a39d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "apex-rollup",
- "version": "1.7.3",
+ "version": "1.7.4",
"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 700275e8..1fd565a3 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 77b4037c..cb446d7c 100644
--- a/rollup-namespaced/sfdx-project.json
+++ b/rollup-namespaced/sfdx-project.json
@@ -4,7 +4,7 @@
"default": true,
"package": "apex-rollup-namespaced",
"path": "rollup-namespaced/source/rollup",
- "versionName": "Fixes namespace shadowing issue in RollupFieldInitializer",
+ "versionName": "Fixes RollupCalcItemReplacer usage with mutually exclusive where clauses, and enables Cursor usage from batch apex jobs",
"versionNumber": "1.2.3.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",
@@ -27,6 +27,7 @@
"apex-rollup-namespaced@1.1.30": "04t6g000008OfSJAA0",
"apex-rollup-namespaced@1.2.0": "04t6g000008OfU0AAK",
"apex-rollup-namespaced@1.2.1": "04t6g000008OfWVAA0",
- "apex-rollup-namespaced@1.2.2": "04t6g000008OfXYAA0"
+ "apex-rollup-namespaced@1.2.2": "04t6g000008OfXYAA0",
+ "apex-rollup-namespaced@1.2.3": "04t6g000008Off4AAC"
}
}
diff --git a/rollup/core/classes/RollupAsyncProcessor.cls b/rollup/core/classes/RollupAsyncProcessor.cls
index 8f92c21b..8647461a 100644
--- a/rollup/core/classes/RollupAsyncProcessor.cls
+++ b/rollup/core/classes/RollupAsyncProcessor.cls
@@ -266,9 +266,9 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
if (this.isNoOp) {
logMessage = 'no-op, exiting early to avoid burning async job';
} else if (this.rollupControl.ShouldAbortRun__c) {
- logMessage = String.valueOf(RollupControl__mdt.SObjectType) + '.' + String.valueOf(RollupControl__mdt.ShouldAbortRun__c) + ' set to true, exiting early';
+ logMessage = RollupControl__mdt.SObjectType.toString() + '.' + RollupControl__mdt.ShouldAbortRun__c + ' set to true, exiting early';
} else if (RollupSettings__c.getInstance().IsEnabled__c == false && shouldRunWithoutCustomSetting == false) {
- logMessage = String.valueOf(RollupSettings__c.SObjectType) + '.' + String.valueOf(RollupSettings__c.IsEnabled__c) + ' is false, exiting early';
+ logMessage = RollupSettings__c.SObjectType.toString() + '.' + RollupSettings__c.IsEnabled__c + ' is false, exiting early';
} else {
if (syncRollups.isEmpty() == false) {
rollupProcessId = this.getNoProcessId();
@@ -506,10 +506,18 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme
}
protected String startBatchProcessor() {
+ String processId;
if (this.isFromBatchExecute) {
- return new QueueableProcessor(this).startAsyncWork();
+ processId = new QueueableProcessor(this).startAsyncWork();
+ } else {
+ try {
+ processId = Database.executeBatch(this, this.rollupControl.BatchChunkSize__c.intValue());
+ } catch (Exception ex) {
+ this.logger.log('Could not start batch, trying again as Queueable', ex, System.LoggingLevel.WARN);
+ processId = new QueueableProcessor(this).startAsyncWork();
+ }
}
- return Database.executeBatch(this, this.rollupControl.BatchChunkSize__c.intValue());
+ return processId;
}
protected List getExistingLookupItems(Set lookupKeys, RollupAsyncProcessor roll, Set uniqueQueryFieldNames) {
diff --git a/rollup/core/classes/RollupCalcItemReplacer.cls b/rollup/core/classes/RollupCalcItemReplacer.cls
index 2247e2ae..202aa39a 100644
--- a/rollup/core/classes/RollupCalcItemReplacer.cls
+++ b/rollup/core/classes/RollupCalcItemReplacer.cls
@@ -239,7 +239,7 @@ public without sharing class RollupCalcItemReplacer {
new List(additionalQueryFields),
'Id',
'=',
- String.join(optionalWhereClauses, ' AND ')
+ String.join(optionalWhereClauses, ' OR ')
);
calcItems = this.repo.setQuery(queryString).setArg(calcItems).get();
diff --git a/rollup/core/classes/RollupFullBatchRecalculator.cls b/rollup/core/classes/RollupFullBatchRecalculator.cls
index d27c70cc..318fb6c4 100644
--- a/rollup/core/classes/RollupFullBatchRecalculator.cls
+++ b/rollup/core/classes/RollupFullBatchRecalculator.cls
@@ -1,7 +1,6 @@
public without sharing virtual class RollupFullBatchRecalculator extends RollupFullRecalcProcessor {
private final RollupState state = new RollupState();
- private Database.Cursor cursor;
- private Integer currentPosition = 0;
+ private CalcItemRetriever retriever;
private static final Integer DEFAULT_CHUNK_SIZE = 500;
@@ -56,21 +55,68 @@ public without sharing virtual class RollupFullBatchRecalculator extends RollupF
this.finalizer.addCaboose(this.cabooses.remove(0));
}
}
- this.cursor = this.cursor ?? this.preStart().getCursor();
-
- Integer countOfRecordsToReturn = this.rollupControl.BatchChunkSize__c.intValue();
- if (countOfRecordsToReturn + this.currentPosition > this.cursor.getNumRecords()) {
- countOfRecordsToReturn = this.cursor.getNumRecords() - this.currentPosition;
+ if (this.retriever == null) {
+ try {
+ this.retriever = new CursorBasedRetriever(this.preStart(), this.rollupControl);
+ } catch (Exception ex) {
+ this.logger.log('cursor use disallowed', ex, System.LoggingLevel.WARN);
+ return System.enqueueJob(new FullBatchQueueableFailsafe(this));
+ }
}
- this.calcItems = this.cursor.fetch(this.currentPosition, countOfRecordsToReturn);
- this.currentPosition += countOfRecordsToReturn;
+
+ this.calcItems = this.retriever.fetch();
return super.startAsyncWork();
}
+ private String runBatch() {
+ return this.startBatchProcessor();
+ }
+
protected override RollupFinalizer getFinalizer() {
return new FullRecalcFinalizer(this);
}
+ private interface CalcItemRetriever {
+ List fetch();
+ Boolean shouldContinue();
+ }
+
+ private class CursorBasedRetriever implements CalcItemRetriever {
+ private final Database.Cursor cursor;
+ private Integer countOfRecordsToReturn;
+ private Integer currentPosition = 0;
+
+ public CursorBasedRetriever(RollupRepository repo, RollupControl__mdt control) {
+ this.cursor = repo.getCursor();
+ this.countOfRecordsToReturn = control.BatchChunkSize__c.intValue();
+ }
+
+ public List fetch() {
+ if (this.countOfRecordsToReturn + this.currentPosition > this.cursor.getNumRecords()) {
+ this.countOfRecordsToReturn = this.cursor.getNumRecords() - this.currentPosition;
+ }
+ List fetchedRecords = this.cursor.fetch(this.currentPosition, this.countOfRecordsToReturn);
+ this.currentPosition += this.countOfRecordsToReturn;
+ return fetchedRecords;
+ }
+
+ public Boolean shouldContinue() {
+ return this.currentPosition < this.cursor.getNumRecords();
+ }
+ }
+
+ private class FullBatchQueueableFailsafe implements System.Queueable {
+ private final RollupFullBatchRecalculator roll;
+
+ public FullBatchQueueableFailsafe(RollupFullBatchRecalculator roll) {
+ this.roll = roll;
+ }
+
+ public void execute(QueueableContext qc) {
+ this.roll.runBatch();
+ }
+ }
+
private class FullRecalcFinalizer extends RollupFinalizer {
private final RollupFullBatchRecalculator conductor;
public FullRecalcFinalizer(RollupFullBatchRecalculator conductor) {
@@ -78,7 +124,7 @@ public without sharing virtual class RollupFullBatchRecalculator extends RollupF
}
public override void handleSuccess() {
- if (this.conductor.currentPosition < this.conductor.cursor.getNumRecords()) {
+ if (this.conductor.retriever?.shouldContinue() ?? false) {
this.conductor.startAsyncWork();
} else {
this.conductor.finish();
diff --git a/rollup/core/classes/RollupLogger.cls b/rollup/core/classes/RollupLogger.cls
index 1012b0d2..c59dd171 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.3';
+ private static final String CURRENT_VERSION_NUMBER = 'v1.7.4';
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
private static final RollupPlugin PLUGIN = new RollupPlugin();
diff --git a/sfdx-project.json b/sfdx-project.json
index 630409ad..5b3f5fbc 100644
--- a/sfdx-project.json
+++ b/sfdx-project.json
@@ -5,8 +5,8 @@
"package": "apex-rollup",
"path": "rollup",
"scopeProfiles": true,
- "versionName": "Fixes namespace shadowing issue in RollupFieldInitializer",
- "versionNumber": "1.7.3.0",
+ "versionName": "Fixes RollupCalcItemReplacer usage with mutually exclusive where clauses, and enables Cursor usage from batch apex jobs",
+ "versionNumber": "1.7.4.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": {
@@ -109,6 +109,7 @@
"apex-rollup@1.7.0": "04t6g000008OfTvAAK",
"apex-rollup@1.7.1": "04t6g000008OfWQAA0",
"apex-rollup@1.7.2": "04t6g000008OfXTAA0",
- "apex-rollup@1.7.3": "04t6g000008OfdwAAC"
+ "apex-rollup@1.7.3": "04t6g000008OfdwAAC",
+ "apex-rollup@1.7.4": "04t6g000008OfezAAC"
}
}