diff --git a/rollup/main/default/classes/Rollup.cls b/rollup/main/default/classes/Rollup.cls index 95aa981d..b15a2395 100644 --- a/rollup/main/default/classes/Rollup.cls +++ b/rollup/main/default/classes/Rollup.cls @@ -299,16 +299,16 @@ global without sharing virtual class Rollup implements Database.Batchable objIds = new Set(); for (Rollup rollup : this.rollups) { + sObjectType = rollup.lookupObj; lookupFieldOnLookupObject = rollup.lookupFieldOnLookupObject.getDescribe().getName(); - objIds.addAll(this.getCalcItemsByLookupField(rollup).keySet()); - } - for (SObjectType sObjectType : this.lookupObjectToUniqueFieldNames.keySet()) { - query = getQueryString(sObjectType, new List(this.lookupObjectToUniqueFieldNames.get(sObjectType)), lookupFieldOnLookupObject, '='); + objIds.addAll(this.getCalcItemsByLookupField(rollup, this.lookupObjectToUniqueFieldNames.get(sObjectType)).keySet()); } + Set uniqueQueryFieldNames = this.lookupObjectToUniqueFieldNames.get(sObjectType); + String query = getQueryString(sObjectType, new List(uniqueQueryFieldNames), lookupFieldOnLookupObject, '='); return Database.getQueryLocator(query); } @@ -373,23 +373,18 @@ global without sharing virtual class Rollup implements Database.Batchable getExistingLookupItems(Set objIds, Rollup rollup, Set uniqueQueryFieldNames) { if (objIds.isEmpty()) { return new List(); - } else if (String.isNotBlank(rollup.metadata.GrandparentRelationshipFieldPath__c)) { - // TODO handle if traversal doesn't finish - rollup.traversal = new RollupRelationshipFieldFinder( - rollup.rollupControl, - rollup.metadata.GrandparentRelationshipFieldPath__c, - uniqueQueryFieldNames, - rollup.lookupObj, - rollup.oldCalcItems - ) - .getParents(rollup.calcItems); - return rollup.traversal.getAllParents(); + } else { + List lookupItems; + if (String.isNotBlank(rollup.metadata.GrandparentRelationshipFieldPath__c)) { + lookupItems = rollup.traversal.getAllParents(); + } else { + String queryString = getQueryString(rollup.lookupObj, new List(uniqueQueryFieldNames), String.valueOf(rollup.lookupFieldOnLookupObject), '='); + // non-obvious coupling between "objIds" and the computed "queryString", which uses dynamic variable binding + lookupItems = Database.query(queryString); + } + this.initializeRollupFieldDefaults(lookupItems, rollup); + return lookupItems; } - // non-obvious coupling between "objIds" and the computed "queryString", which uses dynamic variable binding - String queryString = getQueryString(rollup.lookupObj, new List(uniqueQueryFieldNames), String.valueOf(rollup.lookupFieldOnLookupObject), '='); - List lookupItems = Database.query(queryString); - this.initializeRollupFieldDefaults(lookupItems, rollup); - return lookupItems; } public void execute(System.QueueableContext qc) { @@ -1939,7 +1934,7 @@ global without sharing virtual class Rollup implements Database.Batchable> calcItemsByLookupField = this.getCalcItemsByLookupField(rollup); + Map> calcItemsByLookupField = this.getCalcItemsByLookupField(rollup, this.lookupObjectToUniqueFieldNames.get(rollup.lookupObj)); List lookupItems = new List(); Set lookupItemKeys = new Set(calcItemsByLookupField.keySet()); for (String lookupId : calcItemsByLookupField.keySet()) { @@ -2033,7 +2028,19 @@ global without sharing virtual class Rollup implements Database.Batchable> getCalcItemsByLookupField(Rollup rollup) { + private Map> getCalcItemsByLookupField(Rollup rollup, Set uniqueQueryFieldNames) { + if (String.isNotBlank(rollup.metadata.GrandparentRelationshipFieldPath__c)) { + // TODO handle if traversal doesn't finish AND don't requery if we don't need to + rollup.traversal = new RollupRelationshipFieldFinder( + rollup.rollupControl, + rollup.metadata.GrandparentRelationshipFieldPath__c, + uniqueQueryFieldNames, + rollup.lookupObj, + rollup.oldCalcItems + ) + .getParents(rollup.calcItems); + return rollup.traversal.getParentLookupToRecords(); + } Map> lookupFieldToCalcItems = new Map>(); for (SObject calcItem : rollup.calcItems) { String key = (String) calcItem.get(rollup.lookupFieldOnCalcItem); @@ -2107,8 +2114,6 @@ global without sharing virtual class Rollup implements Database.Batchable calcItems = calcItemsByLookupField.get(key); - // Check for reparented records - Map oldCalcItems = rollup.oldCalcItems; for (Integer index = calcItems.size() - 1; index >= 0; index--) { SObject calcItem = calcItems[index]; if (rollup.eval?.matches(calcItem) == false && rollup.metadata?.IsFullRecordSet__c == true) { @@ -2118,7 +2123,8 @@ global without sharing virtual class Rollup implements Database.Batchable()); } diff --git a/rollup/main/default/classes/RollupRelationshipFieldFinder.cls b/rollup/main/default/classes/RollupRelationshipFieldFinder.cls index 70806d72..19f913c4 100644 --- a/rollup/main/default/classes/RollupRelationshipFieldFinder.cls +++ b/rollup/main/default/classes/RollupRelationshipFieldFinder.cls @@ -12,6 +12,7 @@ public without sharing class RollupRelationshipFieldFinder { private final Map oldRecords; private final Set uniqueFinalFieldNames; + private List records; private List relationshipParts; private Boolean isFirstRun = true; @@ -47,7 +48,21 @@ public without sharing class RollupRelationshipFieldFinder { } public List getAllParents() { - return this.lookupIdToFinalRecords.values(); + // not ideal, but because multiple parents can be tied to different descendants ... + return new List(new Set(this.lookupIdToFinalRecords.values())); + } + + public Map> getParentLookupToRecords() { + Map> parentToLookupRecords = new Map>(); + for(SObject record : this.finder.records) { + SObject parentRecord = this.retrieveParent(record.Id); + if(parentToLookupRecords.containsKey(parentRecord.Id)) { + parentToLookupRecords.get(parentRecord.Id).add(record); + } else { + parentToLookupRecords.put(parentRecord.Id, new List{ record }); + } + } + return parentToLookupRecords; } public Boolean isUltimatelyReparented(SObject record, String relationshipFieldName) { @@ -196,7 +211,10 @@ public without sharing class RollupRelationshipFieldFinder { // NB - we only support one route through polymorphic fields such as Task.WhoId and Task.WhatId for this sort of thing String query = 'SELECT ' + String.join(fieldNames, ',') + ' FROM ' + nextSObjectType.getDescribe().getName() + ' WHERE Id = :lookupIds'; // recurse through till we get to the top/bottom of the chain - this.isFirstRun = false; + if(this.isFirstRun) { + this.records = records; + this.isFirstRun = false; + } return this.getParents(Database.query(query)); } }