Skip to content

Commit

Permalink
First passing integration test for #46. still some cleanup/tweaks to …
Browse files Browse the repository at this point in the history
…do related to re-queueing and avoiding duplicate queries
  • Loading branch information
jamessimone committed Mar 16, 2021
1 parent 357cc33 commit 5e23ffe
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 29 deletions.
64 changes: 37 additions & 27 deletions rollup/main/default/classes/Rollup.cls
Original file line number Diff line number Diff line change
Expand Up @@ -299,16 +299,16 @@ global without sharing virtual class Rollup implements Database.Batchable<SObjec
* "System.AsyncException: Queueable cannot be implemented with other system interfaces" exception
*/
this.getFieldNamesForRollups(this.rollups);
String query;
String lookupFieldOnLookupObject;
SObjectType sObjectType;
Set<String> objIds = new Set<String>();
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<String>(this.lookupObjectToUniqueFieldNames.get(sObjectType)), lookupFieldOnLookupObject, '=');
objIds.addAll(this.getCalcItemsByLookupField(rollup, this.lookupObjectToUniqueFieldNames.get(sObjectType)).keySet());
}
Set<String> uniqueQueryFieldNames = this.lookupObjectToUniqueFieldNames.get(sObjectType);
String query = getQueryString(sObjectType, new List<String>(uniqueQueryFieldNames), lookupFieldOnLookupObject, '=');
return Database.getQueryLocator(query);
}

Expand Down Expand Up @@ -373,23 +373,18 @@ global without sharing virtual class Rollup implements Database.Batchable<SObjec
protected override List<SObject> getExistingLookupItems(Set<String> objIds, Rollup rollup, Set<String> uniqueQueryFieldNames) {
if (objIds.isEmpty()) {
return new List<SObject>();
} 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<SObject> lookupItems;
if (String.isNotBlank(rollup.metadata.GrandparentRelationshipFieldPath__c)) {
lookupItems = rollup.traversal.getAllParents();
} else {
String queryString = getQueryString(rollup.lookupObj, new List<String>(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<String>(uniqueQueryFieldNames), String.valueOf(rollup.lookupFieldOnLookupObject), '=');
List<SObject> lookupItems = Database.query(queryString);
this.initializeRollupFieldDefaults(lookupItems, rollup);
return lookupItems;
}

public void execute(System.QueueableContext qc) {
Expand Down Expand Up @@ -1939,7 +1934,7 @@ global without sharing virtual class Rollup implements Database.Batchable<SObjec
this.deferredRollups.add(rollup);
continue;
}
Map<String, List<SObject>> calcItemsByLookupField = this.getCalcItemsByLookupField(rollup);
Map<String, List<SObject>> calcItemsByLookupField = this.getCalcItemsByLookupField(rollup, this.lookupObjectToUniqueFieldNames.get(rollup.lookupObj));
List<SObject> lookupItems = new List<SObject>();
Set<String> lookupItemKeys = new Set<String>(calcItemsByLookupField.keySet());
for (String lookupId : calcItemsByLookupField.keySet()) {
Expand Down Expand Up @@ -2033,7 +2028,19 @@ global without sharing virtual class Rollup implements Database.Batchable<SObjec
}
}

private Map<String, List<SObject>> getCalcItemsByLookupField(Rollup rollup) {
private Map<String, List<SObject>> getCalcItemsByLookupField(Rollup rollup, Set<String> 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<String, List<SObject>> lookupFieldToCalcItems = new Map<String, List<SObject>>();
for (SObject calcItem : rollup.calcItems) {
String key = (String) calcItem.get(rollup.lookupFieldOnCalcItem);
Expand Down Expand Up @@ -2107,8 +2114,6 @@ global without sharing virtual class Rollup implements Database.Batchable<SObjec
if (calcItemsByLookupField.containsKey(key)) {
List<SObject> calcItems = calcItemsByLookupField.get(key);

// Check for reparented records
Map<Id, SObject> 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) {
Expand All @@ -2118,7 +2123,8 @@ global without sharing virtual class Rollup implements Database.Batchable<SObjec
calcItems.remove(index);
continue;
}
SObject oldCalcItem = oldCalcItems.get(calcItem.Id);
// Check for reparented records
SObject oldCalcItem = rollup.oldCalcItems.get(calcItem.Id);

if (oldCalcItem == null) {
continue;
Expand All @@ -2127,7 +2133,11 @@ global without sharing virtual class Rollup implements Database.Batchable<SObjec
String priorLookup = (String) oldCalcItem.get(rollup.lookupFieldOnCalcItem);
Object newLookup = calcItem.get(rollup.lookupFieldOnCalcItem);

if (newLookup != priorLookup) {
if (
newLookup != priorLookup &&
rollup.traversal == null ||
rollup.traversal != null && rollup.traversal.isUltimatelyReparented(calcItem, rollup.lookupFieldOnCalcItem.getDescribe().getName())
) {
if (!oldLookupItems.containsKey(priorLookup)) {
oldLookupItems.put(priorLookup, new List<SObject>());
}
Expand Down
22 changes: 20 additions & 2 deletions rollup/main/default/classes/RollupRelationshipFieldFinder.cls
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public without sharing class RollupRelationshipFieldFinder {
private final Map<Id, SObject> oldRecords;
private final Set<String> uniqueFinalFieldNames;

private List<SObject> records;
private List<String> relationshipParts;
private Boolean isFirstRun = true;

Expand Down Expand Up @@ -47,7 +48,21 @@ public without sharing class RollupRelationshipFieldFinder {
}

public List<SObject> getAllParents() {
return this.lookupIdToFinalRecords.values();
// not ideal, but because multiple parents can be tied to different descendants ...
return new List<SObject>(new Set<SObject>(this.lookupIdToFinalRecords.values()));
}

public Map<String, List<SObject>> getParentLookupToRecords() {
Map<String, List<SObject>> parentToLookupRecords = new Map<String, List<SObject>>();
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<SObject>{ record });
}
}
return parentToLookupRecords;
}

public Boolean isUltimatelyReparented(SObject record, String relationshipFieldName) {
Expand Down Expand Up @@ -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));
}
}

0 comments on commit 5e23ffe

Please sign in to comment.