Skip to content

Commit b7ac8d7

Browse files
authored
Support for TYPEOF fields for polymorphic fields
1 parent baa5b68 commit b7ac8d7

File tree

2 files changed

+109
-12
lines changed

2 files changed

+109
-12
lines changed

src/classes/SobjectQueryBuilder.cls

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@
55
public class SobjectQueryBuilder extends Soql {
66

77
private String displayFieldApiName;
8+
private List<String> polymorphicFieldStatements;
89
private List<String> childRelationshipQueries;
910
private Boolean forReference, forUpdate, forView, includeLabels, includeFormattedValues;
1011

1112
public SobjectQueryBuilder(Schema.SobjectType sobjectType) {
1213
super(sobjectType, true);
1314

14-
this.displayFieldApiName = this.getDisplayFieldApiName(this.sobjectType);
15-
this.childRelationshipQueries = new List<String>();
16-
this.forReference = false;
17-
this.forUpdate = false;
18-
this.forView = false;
19-
this.includeLabels = false;
20-
this.includeFormattedValues = false;
15+
this.displayFieldApiName = this.getDisplayFieldApiName(this.sobjectType);
16+
this.polymorphicFieldStatements = new List<String>();
17+
this.childRelationshipQueries = new List<String>();
18+
this.forReference = false;
19+
this.forUpdate = false;
20+
this.forView = false;
21+
this.includeLabels = false;
22+
this.includeFormattedValues = false;
2123

2224
this.addDefaultFields();
2325
}
@@ -79,6 +81,53 @@ public class SobjectQueryBuilder extends Soql {
7981
return this.addFields(queryFields, fieldCategory);
8082
}
8183

84+
public SobjectQueryBuilder addPolymorphicFields(Schema.SobjectField polymorphicRelationshipField) {
85+
return addPolymorphicFields(polymorphicRelationshipField, new Map<Schema.SobjectType, List<Schema.SobjectField>>());
86+
}
87+
88+
public SobjectQueryBuilder addPolymorphicFields(Schema.SobjectField polymorphicRelationshipField, Map<Schema.SobjectType, List<Schema.SobjectField>> fieldsBySobjectType) {
89+
Map<Schema.SobjectType, List<Soql.QueryField>> queryFieldsBySobjectType = new Map<Schema.SobjectType, List<Soql.QueryField>>();
90+
for(Schema.SobjectType sobjectType : fieldsBySobjectType.keySet()) {
91+
List<Soql.QueryField> queryFields = new List<Soql.QueryField>();
92+
for(Schema.SobjectField field : fieldsBySobjectType.get(sobjectType)) {
93+
queryFields.add(new Soql.QueryField(field));
94+
}
95+
queryFieldsBySobjectType.put(sobjectType, queryFields);
96+
}
97+
return this.addPolymorphicFields(polymorphicRelationshipField, queryFieldsBySobjectType);
98+
}
99+
100+
public SobjectQueryBuilder addPolymorphicFields(Schema.SobjectField polymorphicRelationshipField, Map<Schema.SobjectType, List<Soql.QueryField>> queryFieldsBySobjectType) {
101+
String polymorphicFieldStatement = queryFieldsBySobjectType.isEmpty() ? '' : 'TYPEOF ' + polymorphicRelationshipField.getDescribe().getRelationshipName();
102+
for(Schema.SobjectType sobjectType : queryFieldsBySobjectType.keySet()) {
103+
List<String> fieldNames = new List<String>();
104+
for(Soql.QueryField queryField : queryFieldsBySobjectType.get(sobjectType)) {
105+
fieldNames.addAll(this.getFieldsToQuery(queryField, Soql.FieldCategory.ACCESSIBLE));
106+
}
107+
fieldNames.sort();
108+
polymorphicFieldStatement += ' WHEN ' + sobjectType + ' THEN ' + String.join(fieldNames, ', ');
109+
}
110+
111+
// The Name object contains the list of all possible polymorphic fields in the org
112+
List<String> supportedPolymorphicFieldNames = new List<String>();
113+
for(Schema.SobjectField field : Schema.Name.SobjectType.getDescribe().fields.getMap().values()) {
114+
supportedPolymorphicFieldNames.addAll(this.getFieldsToQuery(new QueryField(field), Soql.FieldCategory.ACCESSIBLE));
115+
}
116+
supportedPolymorphicFieldNames.sort();
117+
if(!queryFieldsBySobjectType.isEmpty()) polymorphicFieldStatement += ' ELSE ';
118+
else if(queryFieldsBySobjectType.isEmpty()) {
119+
String supportedPolymorphicFieldPrefix = queryFieldsBySobjectType.isEmpty() ? 'Who.' : '';
120+
for(Integer i = 0; i < supportedPolymorphicFieldNames.size(); i++) {
121+
supportedPolymorphicFieldNames[i] = supportedPolymorphicFieldPrefix + supportedPolymorphicFieldNames[i];
122+
}
123+
}
124+
polymorphicFieldStatement += String.join(supportedPolymorphicFieldNames, ', ');
125+
if(!queryFieldsBySobjectType.isEmpty()) polymorphicFieldStatement += ' END';
126+
127+
this.polymorphicFieldStatements.add(polymorphicFieldStatement);
128+
return this.setHasChanged();
129+
}
130+
82131
public SobjectQueryBuilder includeLabels() {
83132
this.includeLabels = true;
84133
return this.setHasChanged();
@@ -209,10 +258,15 @@ public class SobjectQueryBuilder extends Soql {
209258
if(this.query != null && !this.hasChanged) return this.query;
210259

211260
String queryFieldString = this.getQueryFieldString();
261+
262+
String polymorphicFieldsString = String.join(this.polymorphicFieldStatements, ', ');
263+
String polymorphicFieldsDelimiter = !String.isEmpty(queryFieldString) && !String.isEmpty(polymorphicFieldsString) ? ', ' : '';
264+
212265
String childRelationshipsQueryFieldString = this.getChildRelationshipsQueryFieldString();
213-
String childRelationshipDelimiter = !String.isEmpty(queryFieldString) && !String.isEmpty(childRelationshipsQueryFieldString) ? ', ' : '';
266+
String childRelationshipDelimiter = !String.isEmpty(queryFieldString) && !String.isEmpty(childRelationshipsQueryFieldString) ? ', ' : '';
214267

215268
this.query = 'SELECT ' + queryFieldString
269+
+ polymorphicFieldsDelimiter + polymorphicFieldsString
216270
+ childRelationshipDelimiter + childRelationshipsQueryFieldString
217271
+ ' FROM ' + this.sobjectType
218272
+ super.doGetUsingScopeString()
@@ -330,7 +384,7 @@ public class SobjectQueryBuilder extends Soql {
330384
return null;
331385
}
332386

333-
private String getParentObjectNameField(Schema.DescribeFieldResult fieldDescribe) {
387+
private String getParentSobjectNameField(Schema.DescribeFieldResult fieldDescribe) {
334388
String relationshipName = fieldDescribe.getRelationshipName();
335389
Schema.SobjectType parentSobjectType = fieldDescribe.getReferenceTo()[0];
336390
String nameField = this.getDisplayFieldApiName(parentSobjectType);
@@ -341,7 +395,6 @@ public class SobjectQueryBuilder extends Soql {
341395
}
342396

343397
private List<String> getFieldsToQuery(Soql.QueryField queryField, Soql.FieldCategory fieldCat) {
344-
//List<String> fieldsToReturn = super.doGetFieldsToQuery(queryField, fieldCat);
345398
List<String> fieldsToReturn = new List<String>();
346399

347400
if(fieldCat == null) return fieldsToReturn;
@@ -367,15 +420,15 @@ public class SobjectQueryBuilder extends Soql {
367420
}
368421

369422
// If the field is a lookup, then we need to get the name field from the parent object
370-
if(queryField.getDescribe().getType().name() == 'Reference') {
423+
if(queryField.getDescribe().getType().name() == 'REFERENCE') {
371424
if(queryField.getDescribe().isNamePointing()) {
372425
String fieldPath = queryField.getFieldPath();
373426
Integer indx = fieldPath.lastIndexOf(queryField.getDescribe().getName());
374427
String parentTypeFieldPath = fieldPath.substring(0, indx) + queryField.getDescribe().getRelationshipName() + '.Type';
375428
fieldsToReturn.add(parentTypeFieldPath);
376429
}
377430

378-
String parentNameField = this.getParentObjectNameField(queryField.getDescribe());
431+
String parentNameField = this.getParentSobjectNameField(queryField.getDescribe());
379432
if(parentNameField != null) {
380433
fieldsToReturn.add(parentNameField);
381434
// Record type names can be translated, so include the translation

src/classes/SobjectQueryBuilder_Tests.cls

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,50 @@ private class SobjectQueryBuilder_Tests {
8585
List<Account> accounts = accountQuery.getResults();
8686
}
8787

88+
@isTest
89+
static void it_should_return_results_and_include_separate_fields_for_for_polymorphic_field() {
90+
List<String> supportedPolymorphicFieldNames = new List<String>();
91+
for(Schema.SobjectField field : Schema.Name.SobjectType.getDescribe().fields.getMap().values()) {
92+
Schema.DescribeFieldResult fieldDescribe = field.getDescribe();
93+
94+
if(!fieldDescribe.isAccessible()) continue;
95+
supportedPolymorphicFieldNames.add(String.valueOf(field));
96+
97+
Set<String> referenceFieldNames = new Set<String>{'ProfileId', 'RecordTypeId', 'UserRoleId'};
98+
if(referenceFieldNames.contains(fieldDescribe.getName())) supportedPolymorphicFieldNames.add(fieldDescribe.getRelationshipName() + '.Name');
99+
}
100+
supportedPolymorphicFieldNames.sort();
101+
String expectedQuery = 'SELECT Id, Subject, TYPEOF Who WHEN Contact THEN Account.Name, AccountId, Id, Name'
102+
+' WHEN Lead THEN Company, Id, LeadSource, Name'
103+
+ ' ELSE ' + String.join(supportedPolymorphicFieldNames, ', ') + ' END'
104+
+ ' FROM Task';
105+
106+
Test.startTest();
107+
108+
Map<Schema.SobjectType, List<Schema.SobjectField>> fieldsBySobjectType = new Map<Schema.SobjectType, List<Schema.SobjectField>>();
109+
110+
// Contact fields
111+
List<Schema.SobjectField> contactFields = new List<Schema.SobjectField>{
112+
Schema.Contact.Id, Schema.Contact.Name, Schema.Contact.AccountId
113+
};
114+
fieldsBySobjectType.put(Schema.Contact.SobjectType, contactFields);
115+
116+
// Lead fields
117+
List<Schema.SobjectField> leadFields = new List<Schema.SobjectField>{
118+
Schema.Lead.Id, Schema.Lead.Name, Schema.Lead.Company, Schema.Lead.LeadSource
119+
};
120+
fieldsBySobjectType.put(Schema.Lead.SobjectType, leadFields );
121+
122+
// Query the task object
123+
SobjectQueryBuilder taskQuery = new SobjectQueryBuilder(Schema.Task.SobjectType)
124+
.addPolymorphicFields(Schema.Task.WhoId, fieldsBySobjectType);
125+
126+
Test.stopTest();
127+
128+
System.assertEquals(expectedQuery, taskQuery.getQuery());
129+
System.debug(taskQuery.getResults());
130+
}
131+
88132
@isTest
89133
static void it_should_return_results_and_include_sobject_type_for_polymorphic_field() {
90134
String expectedQueryString = 'SELECT Id, Name, Owner.Name, Owner.Type, OwnerId FROM Lead';

0 commit comments

Comments
 (0)