Skip to content

Commit e609d68

Browse files
Merge pull request #503 from apex-enterprise-patterns/494-allow-great-grandchildren
Fixes #494 - allows unlimited depth for child subqueries. Salesforce will still limit you at 5, but fflib_QueryFactory won't
2 parents c799b0b + eac5d29 commit e609d68

File tree

3 files changed

+99
-43
lines changed

3 files changed

+99
-43
lines changed

sfdx-source/apex-common/main/classes/fflib_QueryFactory.cls

+2-6
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,8 @@
4040
* will be included by default. Constructing Ordering instances manually is discouraged.
4141
*
4242
* Subselect Queries are supported with the subselectQuery methods.
43-
* More than one sub-query can be added to a single query, but sub-queries can only be 1 level deep.
44-
* An exception will thrown from the subselectQuery method when there is an attempt to add a subquery to a sub-query
45-
* or to add a subquery to a query with an invalid relationship.
43+
* More than one sub-query can be added to a single query.
44+
* An exception will thrown from the subselectQuery method when there is an attempt to add a subquery to a query with an invalid relationship.
4645
*
4746
* Current limitations:
4847
* - Aggregate functions are not currently supported.
@@ -503,9 +502,6 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
503502
* @param relationship The ChildRelationship to be added as a subquery
504503
**/
505504
private fflib_QueryFactory setSubselectQuery(Schema.ChildRelationship relationship, Boolean assertIsAccessible){
506-
if (this.relationship != null){
507-
throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery. You may not add a subselect query to a subselect query.');
508-
}
509505
if (this.subselectQueryMap == null){
510506
this.subselectQueryMap = new Map<Schema.ChildRelationship, fflib_QueryFactory>();
511507
}

sfdx-source/apex-common/test/classes/fflib_QueryFactoryTest.cls

-21
Original file line numberDiff line numberDiff line change
@@ -432,27 +432,6 @@ private class fflib_QueryFactoryTest {
432432
System.assertNotEquals(e, null);
433433
}
434434

435-
@isTest
436-
static void addChildQueries_invalidChildRelationshipTooDeep(){
437-
fflib_QueryFactory qf = new fflib_QueryFactory(Contact.SObjectType);
438-
qf.selectField('name');
439-
qf.selectField('email');
440-
qf.setCondition( 'name like \'%test%\'' );
441-
qf.addOrdering( new fflib_QueryFactory.Ordering('Contact','name',fflib_QueryFactory.SortOrder.ASCENDING) ).addOrdering('CreatedDATE',fflib_QueryFactory.SortOrder.DESCENDING);
442-
Schema.DescribeSObjectResult descResult = Contact.SObjectType.getDescribe();
443-
444-
fflib_QueryFactory childQf = qf.subselectQuery(Task.SObjectType);
445-
childQf.selectField('Id');
446-
childQf.selectField('Subject');
447-
Exception e;
448-
try {
449-
fflib_QueryFactory subChildQf = childQf.subselectQuery(Task.SObjectType);
450-
} catch (fflib_QueryFactory.InvalidSubqueryRelationshipException ex) {
451-
e = ex;
452-
}
453-
System.assertNotEquals(e, null);
454-
}
455-
456435
@isTest
457436
static void checkFieldObjectReadSort_success(){
458437
fflib_QueryFactory qf = new fflib_QueryFactory(Contact.SObjectType);

sfdx-source/apex-common/test/classes/fflib_SObjectSelectorTest.cls

+97-16
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ private with sharing class fflib_SObjectSelectorTest
3434

3535
static testMethod void testGetSObjectName()
3636
{
37-
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
37+
AccountSelector selector = new AccountSelector();
3838
system.assertEquals(null, selector.getSObjectFieldSetList());
3939
system.assertEquals('Account',selector.getSObjectName());
4040
}
@@ -51,7 +51,7 @@ private with sharing class fflib_SObjectSelectorTest
5151
idSet.add(item.Id);
5252

5353
Test.startTest();
54-
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
54+
AccountSelector selector = new AccountSelector();
5555
List<Account> result = (List<Account>) selector.selectSObjectsById(idSet);
5656
Test.stopTest();
5757

@@ -76,7 +76,7 @@ private with sharing class fflib_SObjectSelectorTest
7676
idSet.add(item.Id);
7777

7878
Test.startTest();
79-
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
79+
AccountSelector selector = new AccountSelector();
8080
Database.QueryLocator result = selector.queryLocatorById(idSet);
8181
System.Iterator<SObject> iteratorResult = result.iterator();
8282
Test.stopTest();
@@ -110,7 +110,7 @@ private with sharing class fflib_SObjectSelectorTest
110110
return; // Abort the test if unable to create a user with low enough acess
111111
System.runAs(testUser)
112112
{
113-
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
113+
AccountSelector selector = new AccountSelector();
114114
try
115115
{
116116
List<Account> result = (List<Account>) selector.selectSObjectsById(idSet);
@@ -139,7 +139,7 @@ private with sharing class fflib_SObjectSelectorTest
139139
return; // Abort the test if unable to create a user with low enough acess
140140
System.runAs(testUser)
141141
{
142-
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector(false, false, false, true);
142+
AccountSelector selector = new AccountSelector(false, false, false, true);
143143
try
144144
{
145145
List<Account> result = (List<Account>) selector.selectSObjectsById(idSet);
@@ -153,7 +153,7 @@ private with sharing class fflib_SObjectSelectorTest
153153

154154
static testMethod void testSOQL()
155155
{
156-
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
156+
AccountSelector selector = new AccountSelector();
157157
String soql = selector.newQueryFactory().toSOQL();
158158
Pattern p = Pattern.compile('SELECT (.*) FROM Account ORDER BY AccountNumber DESC NULLS FIRST , AnnualRevenue ASC NULLS LAST ');
159159
Matcher m = p.matcher(soql);
@@ -165,7 +165,7 @@ private with sharing class fflib_SObjectSelectorTest
165165

166166
static testMethod void testSOQL_defaultSorting()
167167
{
168-
Testfflib_SObjectSelectorDefaultSorting selector = new Testfflib_SObjectSelectorDefaultSorting(false);
168+
DefaultSortingAccountSelector selector = new DefaultSortingAccountSelector(false);
169169
String soql = selector.newQueryFactory().toSOQL();
170170
Pattern p = Pattern.compile(String.format('SELECT (.*) FROM Account ORDER BY {0} ASC NULLS FIRST ',
171171
new List<String>{selector.getOrderBy()}));
@@ -178,7 +178,7 @@ private with sharing class fflib_SObjectSelectorTest
178178

179179
static testMethod void testDefaultConfig()
180180
{
181-
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
181+
AccountSelector selector = new AccountSelector();
182182
System.assertEquals(false, selector.isEnforcingFLS());
183183
System.assertEquals(true, selector.isEnforcingCRUD());
184184
System.assertEquals(false, selector.isIncludeFieldSetFields());
@@ -213,7 +213,7 @@ private with sharing class fflib_SObjectSelectorTest
213213
static void testWithoutSorting()
214214
{
215215
//Given
216-
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector(false, false, false, false);
216+
AccountSelector selector = new AccountSelector(false, false, false, false);
217217
fflib_QueryFactory qf = selector.newQueryFactory();
218218

219219
Set<String> expectedSelectFields = new Set<String>{ 'Name', 'Id', 'AccountNumber', 'AnnualRevenue' };
@@ -239,7 +239,7 @@ private with sharing class fflib_SObjectSelectorTest
239239
static void testWithOrderingNullsLast()
240240
{
241241
// Build the selector to test with
242-
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector(false, false, false, false);
242+
AccountSelector selector = new AccountSelector(false, false, false, false);
243243
fflib_QueryFactory qf = selector.newQueryFactory();
244244

245245
// Add in the expected fields
@@ -466,14 +466,14 @@ private with sharing class fflib_SObjectSelectorTest
466466
}
467467

468468

469-
private class Testfflib_SObjectSelector extends fflib_SObjectSelector
469+
private class AccountSelector extends fflib_SObjectSelector
470470
{
471-
public Testfflib_SObjectSelector()
471+
public AccountSelector()
472472
{
473473
super();
474474
}
475475

476-
public Testfflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS, Boolean sortSelectFields)
476+
public AccountSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS, Boolean sortSelectFields)
477477
{
478478
super(includeFieldSetFields, enforceCRUD, enforceFLS, sortSelectFields);
479479
}
@@ -498,7 +498,7 @@ private with sharing class fflib_SObjectSelectorTest
498498
return 'AccountNumber DESC, AnnualRevenue ASC NULLS LAST';
499499
}
500500
}
501-
501+
502502
private class Testfflib_UserSObjectSelector extends fflib_SObjectSelector
503503
{
504504
public Testfflib_UserSObjectSelector()
@@ -523,9 +523,9 @@ private with sharing class fflib_SObjectSelectorTest
523523

524524
}
525525

526-
private class Testfflib_SObjectSelectorDefaultSorting extends fflib_SObjectSelector
526+
private class DefaultSortingAccountSelector extends fflib_SObjectSelector
527527
{
528-
public Testfflib_SObjectSelectorDefaultSorting(Boolean includeFieldSetFields)
528+
public DefaultSortingAccountSelector(Boolean includeFieldSetFields)
529529
{
530530
super(includeFieldSetFields);
531531
}
@@ -629,6 +629,56 @@ private with sharing class fflib_SObjectSelectorTest
629629
}
630630
}
631631

632+
private class ContractSelector extends fflib_SObjectSelector{
633+
public ContractSelector(){
634+
super(false,DataAccess.SYSTEM_MODE);
635+
}
636+
637+
public Schema.SObjectType getSObjectType(){
638+
return Contract.SObjectType;
639+
}
640+
641+
public List<Schema.SObjectField> getSObjectFieldList(){
642+
return new List<Schema.SObjectField> {
643+
Contract.Id,
644+
Contract.ContractNumber
645+
};
646+
}
647+
}
648+
649+
private class OrderSelector extends fflib_SObjectSelector{
650+
public OrderSelector(){
651+
super(false,DataAccess.SYSTEM_MODE);
652+
}
653+
654+
public Schema.SObjectType getSObjectType(){
655+
return Order.SObjectType;
656+
}
657+
658+
public List<Schema.SObjectField> getSObjectFieldList(){
659+
return new List<Schema.SObjectField> {
660+
Order.Id,
661+
Order.OrderNumber
662+
};
663+
}
664+
}
665+
666+
private class TaskSelector extends fflib_SObjectSelector{
667+
public TaskSelector(){
668+
super(false,DataAccess.SYSTEM_MODE);
669+
}
670+
671+
public Schema.SObjectType getSObjectType(){
672+
return Task.SObjectType;
673+
}
674+
675+
public List<Schema.SObjectField> getSObjectFieldList(){
676+
return new List<Schema.SObjectField> {
677+
Task.Id,
678+
Task.Subject
679+
};
680+
}
681+
}
632682

633683
/**
634684
* Create test user
@@ -762,6 +812,37 @@ private with sharing class fflib_SObjectSelectorTest
762812
System.assertEquals(expectedSOQL, actualSOQL);
763813
}
764814

815+
@IsTest
816+
static void toSOQL_When_GreatGreatGrandchildRelationships_Expect_WelformedSOQL(){
817+
AccessLevelAccountSelector aSel = new AccessLevelAccountSelector();
818+
fflib_QueryFactory aQF = aSel.newQueryFactory();
819+
820+
ContractSelector cSel = new ContractSelector();
821+
fflib_QueryFactory cQF = cSel.addQueryFactorySubselect(aQF);
822+
823+
AccessLevelOpportunitySelector oppSel = new AccessLevelOpportunitySelector();
824+
fflib_QueryFactory oppQF = oppSel.addQueryFactorySubselect(cQF);
825+
826+
OrderSelector orderSel = new OrderSelector();
827+
fflib_QueryFactory orderQF = orderSel.addQueryFactorySubselect(oppQF);
828+
829+
TaskSelector tSel = new TaskSelector();
830+
fflib_QueryFactory tQF = tSel.addQueryFactorySubselect(orderQF);
831+
832+
String expected
833+
= 'SELECT name, id, annualrevenue, accountnumber, '
834+
+ '(SELECT id, contractnumber, '
835+
+ '(SELECT name, id, amount, closedate, '
836+
+ '(SELECT id, ordernumber, '
837+
+ '(SELECT id, subject FROM Tasks ORDER BY Subject ASC NULLS FIRST ) '
838+
+ 'FROM Orders ORDER BY OrderNumber ASC NULLS FIRST ) '
839+
+ 'FROM Opportunities ORDER BY Name ASC NULLS FIRST ) '
840+
+ 'FROM Contracts ORDER BY ContractNumber ASC NULLS FIRST ) '
841+
+ 'FROM Account WITH USER_MODE ORDER BY Name ASC NULLS FIRST ';
842+
843+
Assert.areEqual(expected,aQF.toSOQL());
844+
}
845+
765846
private class CampaignMemberSelector extends fflib_SObjectSelector {
766847
public CampaignMemberSelector(DataAccess access) {
767848
super(false, access);

0 commit comments

Comments
 (0)