Skip to content

Commit

Permalink
Fixes issue reported with RollupCalcItemReplacer incorrectly detectin…
Browse files Browse the repository at this point in the history
…g Type__r fields as polymorphic
  • Loading branch information
jamessimone committed Dec 11, 2024
1 parent d89e615 commit c54de82
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 10 deletions.
14 changes: 14 additions & 0 deletions extra-tests/classes/RollupCalcItemReplacerTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,18 @@ private class RollupCalcItemReplacerTests {

Assert.areEqual(0, records.size());
}

@IsTest
static void doesNotReplaceForCustomTypeFields() {
List<Account> accounts = [SELECT Id FROM Account];
RollupCalcItemReplacer replacer = new RollupCalcItemReplacer(
new RollupControl__mdt(IsRollupLoggingEnabled__c = true, ReplaceCalcItemsAsyncWhenOverCount__c = 3)
);
List<Rollup__mdt> metas = new List<Rollup__mdt>{ new Rollup__mdt(CalcItemWhereClause__c = 'Parent.Type__r = \'hello\'', CalcItem__c = 'Account') };

List<SObject> records = replacer.replace(accounts, metas);

Assert.areEqual(1, records.size());
Assert.isTrue(replacer.hasProcessedMetadata(metas, accounts));
}
}
58 changes: 58 additions & 0 deletions extra-tests/classes/RollupFlowBulkProcessorTests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -639,4 +639,62 @@ private class RollupFlowBulkProcessorTests {
Individual updatedIndy = [SELECT Id, ConsumerCreditScore FROM Individual WHERE Id = :indy.Id];
System.assertEquals(secondChild.AnnualRevenue, updatedIndy.ConsumerCreditScore);
}

@IsTest
static void doesNotNoOpForSomeAllNoneRollups() {
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 = 'SOME',
CalcItem__c = 'Contact',
CalcItemWhereClause__c = 'FirstName != \'One\''
),
new Rollup__mdt(
RollupFieldOnCalcItem__c = 'Id',
LookupObject__c = 'Account',
LookupFieldOnCalcItem__c = 'AccountId',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'Description',
RollupOperation__c = 'ALL',
CalcItem__c = 'Contact',
CalcItemWhereClause__c = 'FirstName != \'One\''
),
new Rollup__mdt(
RollupFieldOnCalcItem__c = 'Id',
LookupObject__c = 'Account',
LookupFieldOnCalcItem__c = 'AccountId',
LookupFieldOnLookupObject__c = 'Id',
RollupFieldOnLookupObject__c = 'NumberOfEmployees',
RollupOperation__c = 'NONE',
CalcItem__c = 'Contact',
CalcItemWhereClause__c = 'FirstName != \'One\''
)
};
Rollup.onlyUseMockMetadata = true;
Account acc = (Account) RollupTestUtils.queryRecord(Account.SObjectType, new List<Schema.SObjectField>());
acc.Description = 'true';
acc.AnnualRevenue = 1;
acc.NumberOfEmployees = 0;
RollupAsyncProcessor.stubParentRecords = new List<SObject>{ acc };

Rollup.records = new List<Contact>{ new Contact(FirstName = 'One', AccountId = acc.Id) };

RollupFlowBulkProcessor.FlowInput input = new RollupFlowBulkProcessor.FlowInput();
input.recordsToRollup = new List<Contact>{ new Contact(FirstName = 'One', AccountId = acc.Id, Id = RollupTestUtils.createId(Contact.SObjectType)) };
input.oldRecordsToRollup = new List<SObject>{ new Contact(Id = input.recordsToRollup[0].Id, FirstName = 'One', AccountId = acc.Id) };
input.rollupContext = 'UPDATE';
input.deferProcessing = false;

Test.startTest();
RollupFlowBulkProcessor.addRollup(new List<RollupFlowBulkProcessor.FlowInput>{ input });
Test.stopTest();

Assert.areEqual('false', acc.Description);
Assert.areEqual(0, acc.AnnualRevenue);
Assert.areEqual(1, acc.NumberOfEmployees);
}
}
2 changes: 1 addition & 1 deletion rollup/core/classes/Rollup.cls
Original file line number Diff line number Diff line change
Expand Up @@ -2850,7 +2850,7 @@ global without sharing virtual class Rollup implements RollupLogger.ToStringObje
/**
* - in order to accomodate CDC, we set the apexContext manually there
* since technically all CDC is done from an AFTER_INSERT context
* - an undelete behaviors _strictly_ the same as an insert
* - an undelete behaves _strictly_ the same as an insert
* because the underlying SObject can't be modified till afterwards
*/
private static Boolean shouldRunFromTrigger() {
Expand Down
12 changes: 6 additions & 6 deletions rollup/core/classes/RollupCalcItemReplacer.cls
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ public without sharing class RollupCalcItemReplacer {

private void processWhereClauseForDownstreamEvals(Schema.SObjectType sObjectType, Rollup__mdt metadata, RollupEvaluator.WhereFieldEvaluator whereEval) {
for (String whereClause : whereEval.getWhereClauses()) {
Boolean hasTypeField = whereClause.contains(TYPE_FIELD);
Boolean hasTypeField = whereClause.split(TYPE_FIELD + '(?!__r)').size() > 1;
Boolean hasOwnerField = whereClause.contains(OWNER);
if (hasTypeField == false && hasOwnerField == false) {
continue;
Expand Down Expand Up @@ -340,8 +340,8 @@ public without sharing class RollupCalcItemReplacer {
Map<String, SObjectField> fieldNameToFieldToken = calcItems[0].getSObjectType().getDescribe().fields.getMap();
for (Integer index = 0; index < calcItems.size(); index++) {
SObject calcItem = calcItems[index];
SObject calcItemWIthUpdatedParentField = idToCalcItemsWithParentFields.get(calcItem.Id);
Map<String, Object> updatedParentFields = calcItemWIthUpdatedParentField?.getPopulatedFieldsAsMap() ?? new Map<String, Object>();
SObject calcItemWithUpdatedParentField = idToCalcItemsWithParentFields.get(calcItem.Id);
Map<String, Object> updatedParentFields = calcItemWithUpdatedParentField?.getPopulatedFieldsAsMap() ?? new Map<String, Object>();
for (String fieldName : updatedParentFields.keySet()) {
Schema.DescribeFieldResult fieldToken = fieldNameToFieldToken.get(fieldName)?.getDescribe();
Boolean isSkippableField = fieldToken?.getReferenceTo().isEmpty() != false || fieldToken?.getName() == 'Id';
Expand All @@ -353,23 +353,23 @@ public without sharing class RollupCalcItemReplacer {
try {
SObject parent = calcItem.getSObject(fieldToken.getRelationshipName());
if (parent == null) {
calcItem.putSObject(fieldToken.getRelationshipName(), calcItemWIthUpdatedParentField.getSObject(fieldToken.getRelationshipName()));
calcItem.putSObject(fieldToken.getRelationshipName(), calcItemWithUpdatedParentField.getSObject(fieldToken.getRelationshipName()));
} else {
parent.put(fieldName, updatedParentFields.get(fieldName));
}
} catch (SObjectException ex) {
// avoids "System.SObjectException: Relationship { relationship name } is not editable"
if (updatedParentFields.containsKey(fieldToken.getRelationshipName())) {
String relationshipName = fieldToken.getRelationshipName();
calcItems.set(index, serializeReplace(calcItem, relationshipName, calcItemWIthUpdatedParentField.getSObject(relationshipName)));
calcItems.set(index, serializeReplace(calcItem, relationshipName, calcItemWithUpdatedParentField.getSObject(relationshipName)));
}
}
} else {
// polymorphic parent fields that are returned from SOQL can get retrieved via .getSObject,
// but can't be appended via .putSObject without reinitializing the parent object to its actual type
// this is because they are returned with type "Name", and avoids the dreaded:
// "System.SObjectException: Illegal assignment from Name to { the calcItem type }"
SObject parentFieldObject = calcItemWIthUpdatedParentField.getSObject(fieldToken.getRelationshipName());
SObject parentFieldObject = calcItemWithUpdatedParentField.getSObject(fieldToken.getRelationshipName());
SObject replacementObject = parentFieldObject.Id.getSObjectType().newSObject();
for (String populatedFieldName : parentFieldObject.getPopulatedFieldsAsMap().keySet()) {
replacementObject.put(populatedFieldName, parentFieldObject.get(populatedFieldName));
Expand Down
2 changes: 1 addition & 1 deletion rollup/core/classes/RollupLogger.cls
Original file line number Diff line number Diff line change
@@ -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.1';
private static final String CURRENT_VERSION_NUMBER = 'v1.7.2-beta';
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
private static final RollupPlugin PLUGIN = new RollupPlugin();

Expand Down
4 changes: 2 additions & 2 deletions sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"package": "apex-rollup",
"path": "rollup",
"scopeProfiles": true,
"versionName": "Scheduled Rollup updates, RollupDateLiteral updates",
"versionNumber": "1.7.1.0",
"versionName": "Fixes issue in RollupCalcItemReplacer where custom Type fields were incorrectly flagged as polymorphic",
"versionNumber": "1.7.2.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 Down

0 comments on commit c54de82

Please sign in to comment.