From c54de8285b7aed9932ff75cb53220f67a808e700 Mon Sep 17 00:00:00 2001 From: James Simone <16430727+jamessimone@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:55:00 -0500 Subject: [PATCH] Fixes issue reported with RollupCalcItemReplacer incorrectly detecting Type__r fields as polymorphic --- .../classes/RollupCalcItemReplacerTests.cls | 14 +++++ .../classes/RollupFlowBulkProcessorTests.cls | 58 +++++++++++++++++++ rollup/core/classes/Rollup.cls | 2 +- .../core/classes/RollupCalcItemReplacer.cls | 12 ++-- rollup/core/classes/RollupLogger.cls | 2 +- sfdx-project.json | 4 +- 6 files changed, 82 insertions(+), 10 deletions(-) diff --git a/extra-tests/classes/RollupCalcItemReplacerTests.cls b/extra-tests/classes/RollupCalcItemReplacerTests.cls index 647c7a8a..a778ebe7 100644 --- a/extra-tests/classes/RollupCalcItemReplacerTests.cls +++ b/extra-tests/classes/RollupCalcItemReplacerTests.cls @@ -195,4 +195,18 @@ private class RollupCalcItemReplacerTests { Assert.areEqual(0, records.size()); } + + @IsTest + static void doesNotReplaceForCustomTypeFields() { + List accounts = [SELECT Id FROM Account]; + RollupCalcItemReplacer replacer = new RollupCalcItemReplacer( + new RollupControl__mdt(IsRollupLoggingEnabled__c = true, ReplaceCalcItemsAsyncWhenOverCount__c = 3) + ); + List metas = new List{ new Rollup__mdt(CalcItemWhereClause__c = 'Parent.Type__r = \'hello\'', CalcItem__c = 'Account') }; + + List records = replacer.replace(accounts, metas); + + Assert.areEqual(1, records.size()); + Assert.isTrue(replacer.hasProcessedMetadata(metas, accounts)); + } } diff --git a/extra-tests/classes/RollupFlowBulkProcessorTests.cls b/extra-tests/classes/RollupFlowBulkProcessorTests.cls index f624ccb0..01f884e8 100644 --- a/extra-tests/classes/RollupFlowBulkProcessorTests.cls +++ b/extra-tests/classes/RollupFlowBulkProcessorTests.cls @@ -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{ + 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()); + acc.Description = 'true'; + acc.AnnualRevenue = 1; + acc.NumberOfEmployees = 0; + RollupAsyncProcessor.stubParentRecords = new List{ acc }; + + Rollup.records = new List{ new Contact(FirstName = 'One', AccountId = acc.Id) }; + + RollupFlowBulkProcessor.FlowInput input = new RollupFlowBulkProcessor.FlowInput(); + input.recordsToRollup = new List{ new Contact(FirstName = 'One', AccountId = acc.Id, Id = RollupTestUtils.createId(Contact.SObjectType)) }; + input.oldRecordsToRollup = new List{ new Contact(Id = input.recordsToRollup[0].Id, FirstName = 'One', AccountId = acc.Id) }; + input.rollupContext = 'UPDATE'; + input.deferProcessing = false; + + Test.startTest(); + RollupFlowBulkProcessor.addRollup(new List{ input }); + Test.stopTest(); + + Assert.areEqual('false', acc.Description); + Assert.areEqual(0, acc.AnnualRevenue); + Assert.areEqual(1, acc.NumberOfEmployees); + } } diff --git a/rollup/core/classes/Rollup.cls b/rollup/core/classes/Rollup.cls index 34a5b76b..b6bbc992 100644 --- a/rollup/core/classes/Rollup.cls +++ b/rollup/core/classes/Rollup.cls @@ -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() { diff --git a/rollup/core/classes/RollupCalcItemReplacer.cls b/rollup/core/classes/RollupCalcItemReplacer.cls index 03d5fc75..2247e2ae 100644 --- a/rollup/core/classes/RollupCalcItemReplacer.cls +++ b/rollup/core/classes/RollupCalcItemReplacer.cls @@ -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; @@ -340,8 +340,8 @@ public without sharing class RollupCalcItemReplacer { Map 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 updatedParentFields = calcItemWIthUpdatedParentField?.getPopulatedFieldsAsMap() ?? new Map(); + SObject calcItemWithUpdatedParentField = idToCalcItemsWithParentFields.get(calcItem.Id); + Map updatedParentFields = calcItemWithUpdatedParentField?.getPopulatedFieldsAsMap() ?? new Map(); for (String fieldName : updatedParentFields.keySet()) { Schema.DescribeFieldResult fieldToken = fieldNameToFieldToken.get(fieldName)?.getDescribe(); Boolean isSkippableField = fieldToken?.getReferenceTo().isEmpty() != false || fieldToken?.getName() == 'Id'; @@ -353,7 +353,7 @@ 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)); } @@ -361,7 +361,7 @@ public without sharing class RollupCalcItemReplacer { // 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 { @@ -369,7 +369,7 @@ public without sharing class RollupCalcItemReplacer { // 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)); diff --git a/rollup/core/classes/RollupLogger.cls b/rollup/core/classes/RollupLogger.cls index 7d02cb3c..315224d9 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.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(); diff --git a/sfdx-project.json b/sfdx-project.json index 8d89ff52..8b3e3f71 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -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": {