Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #494 - allows unlimited depth for child subqueries. Salesforce will still limit you at 5, but fflib_QueryFactory won't #503

Merged
merged 4 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions sfdx-source/apex-common/main/classes/fflib_QueryFactory.cls
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@
* will be included by default. Constructing Ordering instances manually is discouraged.
*
* Subselect Queries are supported with the subselectQuery methods.
* More than one sub-query can be added to a single query, but sub-queries can only be 1 level deep.
* An exception will thrown from the subselectQuery method when there is an attempt to add a subquery to a sub-query
* or to add a subquery to a query with an invalid relationship.
* More than one sub-query can be added to a single query.
* An exception will thrown from the subselectQuery method when there is an attempt to add a subquery to a query with an invalid relationship.
*
* Current limitations:
* - Aggregate functions are not currently supported.
Expand Down Expand Up @@ -503,9 +502,6 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
* @param relationship The ChildRelationship to be added as a subquery
**/
private fflib_QueryFactory setSubselectQuery(Schema.ChildRelationship relationship, Boolean assertIsAccessible){
if (this.relationship != null){
throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery. You may not add a subselect query to a subselect query.');
}
if (this.subselectQueryMap == null){
this.subselectQueryMap = new Map<Schema.ChildRelationship, fflib_QueryFactory>();
}
Expand Down
21 changes: 0 additions & 21 deletions sfdx-source/apex-common/test/classes/fflib_QueryFactoryTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -432,27 +432,6 @@ private class fflib_QueryFactoryTest {
System.assertNotEquals(e, null);
}

@isTest
static void addChildQueries_invalidChildRelationshipTooDeep(){
fflib_QueryFactory qf = new fflib_QueryFactory(Contact.SObjectType);
qf.selectField('name');
qf.selectField('email');
qf.setCondition( 'name like \'%test%\'' );
qf.addOrdering( new fflib_QueryFactory.Ordering('Contact','name',fflib_QueryFactory.SortOrder.ASCENDING) ).addOrdering('CreatedDATE',fflib_QueryFactory.SortOrder.DESCENDING);
Schema.DescribeSObjectResult descResult = Contact.SObjectType.getDescribe();

fflib_QueryFactory childQf = qf.subselectQuery(Task.SObjectType);
childQf.selectField('Id');
childQf.selectField('Subject');
Exception e;
try {
fflib_QueryFactory subChildQf = childQf.subselectQuery(Task.SObjectType);
} catch (fflib_QueryFactory.InvalidSubqueryRelationshipException ex) {
e = ex;
}
System.assertNotEquals(e, null);
}

@isTest
static void checkFieldObjectReadSort_success(){
fflib_QueryFactory qf = new fflib_QueryFactory(Contact.SObjectType);
Expand Down
113 changes: 97 additions & 16 deletions sfdx-source/apex-common/test/classes/fflib_SObjectSelectorTest.cls
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private with sharing class fflib_SObjectSelectorTest

static testMethod void testGetSObjectName()
{
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
AccountSelector selector = new AccountSelector();
system.assertEquals(null, selector.getSObjectFieldSetList());
system.assertEquals('Account',selector.getSObjectName());
}
Expand All @@ -51,7 +51,7 @@ private with sharing class fflib_SObjectSelectorTest
idSet.add(item.Id);

Test.startTest();
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
AccountSelector selector = new AccountSelector();
List<Account> result = (List<Account>) selector.selectSObjectsById(idSet);
Test.stopTest();

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

Test.startTest();
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
AccountSelector selector = new AccountSelector();
Database.QueryLocator result = selector.queryLocatorById(idSet);
System.Iterator<SObject> iteratorResult = result.iterator();
Test.stopTest();
Expand Down Expand Up @@ -110,7 +110,7 @@ private with sharing class fflib_SObjectSelectorTest
return; // Abort the test if unable to create a user with low enough acess
System.runAs(testUser)
{
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
AccountSelector selector = new AccountSelector();
try
{
List<Account> result = (List<Account>) selector.selectSObjectsById(idSet);
Expand Down Expand Up @@ -139,7 +139,7 @@ private with sharing class fflib_SObjectSelectorTest
return; // Abort the test if unable to create a user with low enough acess
System.runAs(testUser)
{
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector(false, false, false, true);
AccountSelector selector = new AccountSelector(false, false, false, true);
try
{
List<Account> result = (List<Account>) selector.selectSObjectsById(idSet);
Expand All @@ -153,7 +153,7 @@ private with sharing class fflib_SObjectSelectorTest

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

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

static testMethod void testDefaultConfig()
{
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector();
AccountSelector selector = new AccountSelector();
System.assertEquals(false, selector.isEnforcingFLS());
System.assertEquals(true, selector.isEnforcingCRUD());
System.assertEquals(false, selector.isIncludeFieldSetFields());
Expand Down Expand Up @@ -213,7 +213,7 @@ private with sharing class fflib_SObjectSelectorTest
static void testWithoutSorting()
{
//Given
Testfflib_SObjectSelector selector = new Testfflib_SObjectSelector(false, false, false, false);
AccountSelector selector = new AccountSelector(false, false, false, false);
fflib_QueryFactory qf = selector.newQueryFactory();

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

// Add in the expected fields
Expand Down Expand Up @@ -466,14 +466,14 @@ private with sharing class fflib_SObjectSelectorTest
}


private class Testfflib_SObjectSelector extends fflib_SObjectSelector
private class AccountSelector extends fflib_SObjectSelector
{
public Testfflib_SObjectSelector()
public AccountSelector()
{
super();
}

public Testfflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS, Boolean sortSelectFields)
public AccountSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS, Boolean sortSelectFields)
{
super(includeFieldSetFields, enforceCRUD, enforceFLS, sortSelectFields);
}
Expand All @@ -498,7 +498,7 @@ private with sharing class fflib_SObjectSelectorTest
return 'AccountNumber DESC, AnnualRevenue ASC NULLS LAST';
}
}

private class Testfflib_UserSObjectSelector extends fflib_SObjectSelector
{
public Testfflib_UserSObjectSelector()
Expand All @@ -523,9 +523,9 @@ private with sharing class fflib_SObjectSelectorTest

}

private class Testfflib_SObjectSelectorDefaultSorting extends fflib_SObjectSelector
private class DefaultSortingAccountSelector extends fflib_SObjectSelector
{
public Testfflib_SObjectSelectorDefaultSorting(Boolean includeFieldSetFields)
public DefaultSortingAccountSelector(Boolean includeFieldSetFields)
{
super(includeFieldSetFields);
}
Expand Down Expand Up @@ -629,6 +629,56 @@ private with sharing class fflib_SObjectSelectorTest
}
}

private class ContractSelector extends fflib_SObjectSelector{
public ContractSelector(){
super(false,DataAccess.SYSTEM_MODE);
}

public Schema.SObjectType getSObjectType(){
return Contract.SObjectType;
}

public List<Schema.SObjectField> getSObjectFieldList(){
return new List<Schema.SObjectField> {
Contract.Id,
Contract.ContractNumber
};
}
}

private class OrderSelector extends fflib_SObjectSelector{
public OrderSelector(){
super(false,DataAccess.SYSTEM_MODE);
}

public Schema.SObjectType getSObjectType(){
return Order.SObjectType;
}

public List<Schema.SObjectField> getSObjectFieldList(){
return new List<Schema.SObjectField> {
Order.Id,
Order.OrderNumber
};
}
}

private class TaskSelector extends fflib_SObjectSelector{
public TaskSelector(){
super(false,DataAccess.SYSTEM_MODE);
}

public Schema.SObjectType getSObjectType(){
return Task.SObjectType;
}

public List<Schema.SObjectField> getSObjectFieldList(){
return new List<Schema.SObjectField> {
Task.Id,
Task.Subject
};
}
}

/**
* Create test user
Expand Down Expand Up @@ -762,6 +812,37 @@ private with sharing class fflib_SObjectSelectorTest
System.assertEquals(expectedSOQL, actualSOQL);
}

@IsTest
static void toSOQL_When_GreatGreatGrandchildRelationships_Expect_WelformedSOQL(){
AccessLevelAccountSelector aSel = new AccessLevelAccountSelector();
fflib_QueryFactory aQF = aSel.newQueryFactory();

ContractSelector cSel = new ContractSelector();
fflib_QueryFactory cQF = cSel.addQueryFactorySubselect(aQF);

AccessLevelOpportunitySelector oppSel = new AccessLevelOpportunitySelector();
fflib_QueryFactory oppQF = oppSel.addQueryFactorySubselect(cQF);

OrderSelector orderSel = new OrderSelector();
fflib_QueryFactory orderQF = orderSel.addQueryFactorySubselect(oppQF);

TaskSelector tSel = new TaskSelector();
fflib_QueryFactory tQF = tSel.addQueryFactorySubselect(orderQF);

String expected
= 'SELECT name, id, annualrevenue, accountnumber, '
+ '(SELECT id, contractnumber, '
+ '(SELECT name, id, amount, closedate, '
+ '(SELECT id, ordernumber, '
+ '(SELECT id, subject FROM Tasks ORDER BY Subject ASC NULLS FIRST ) '
+ 'FROM Orders ORDER BY OrderNumber ASC NULLS FIRST ) '
+ 'FROM Opportunities ORDER BY Name ASC NULLS FIRST ) '
+ 'FROM Contracts ORDER BY ContractNumber ASC NULLS FIRST ) '
+ 'FROM Account WITH USER_MODE ORDER BY Name ASC NULLS FIRST ';

Assert.areEqual(expected,aQF.toSOQL());
}

private class CampaignMemberSelector extends fflib_SObjectSelector {
public CampaignMemberSelector(DataAccess access) {
super(false, access);
Expand Down