Skip to content
This repository was archived by the owner on Jun 3, 2024. It is now read-only.

Commit 2948ab2

Browse files
authored
Release 2.0
QueryBuilder.cls split into 3 new query builder classes - SObjectQueryBuilder (standard SOQL queries, AggregateResultQueryBuilder (aggregate SOQL queries) and SearchQueryBuilder (SOSL queries) * QueryBuilder.cls is now an abstract class, used by the 3 types of query builders for shared logic * Added QueryField.cls - parses an SObjectField or list of SObjectFields into the SOQL/SOSL string version. This can be used as a field in your 'SELECT' statement, as a field for QueryFilter, and in the 'ORDER BY' statement * Added QueryDate.cls - this represents date functions for date & datetime fields, like 'CALENDAR_MONTH(CreatedDate)'. QueryDates can be used in QueryFilter, as an aggregate result field, and in the 'ORDER BY' statement - DML methods now return a list of corresponding database result types - Began adding ApexDoc to classes
1 parent 44814ec commit 2948ab2

File tree

80 files changed

+2283
-744
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+2283
-744
lines changed

.atom-build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
cwd: '{PROJECT_PATH}'
2+
cmd: java -jar .\tools\apexdoc\apexdoc.jar -s .\src\classes -t . -p public;protected -g https://github.com/jongpie/NebulaFramework/blob/master/src/classes/ -a header.html
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*************************************************************************************************
2+
* This file is part of the Nebula Framework project, released under the MIT License. *
3+
* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. *
4+
*************************************************************************************************/
5+
6+
/**
7+
*
8+
* @group Query Builder
9+
*
10+
* @description A builder class that generates dynamic queries & returns a list of AggregateResult
11+
*
12+
*/
13+
public class AggregateResultQueryBuilder extends QueryBuilder implements IAggregateResultQueryBuilder {
14+
15+
private Schema.SObjectType sobjectType;
16+
private List<String> groupByList;
17+
private List<String> aggregateFunctionList;
18+
19+
public AggregateResultQueryBuilder(Schema.SObjectType sobjectType) {
20+
this.sobjectType = sobjectType;
21+
22+
this.aggregateFunctionList = new List<String>();
23+
this.groupByList = new List<String>();
24+
}
25+
26+
public IAggregateResultQueryBuilder groupBy(IQueryField groupByQueryField) {
27+
return this.groupBy(new List<IQueryField>{groupByQueryField});
28+
}
29+
30+
public IAggregateResultQueryBuilder groupBy(List<IQueryField> groupByQueryFields) {
31+
for(IQueryField groupByQueryField : groupByQueryFields) this.groupByList.add(groupByQueryField.getValue());
32+
return this;
33+
}
34+
35+
public IAggregateResultQueryBuilder groupBy(Schema.FieldSet fieldSet) {
36+
for(Schema.FieldSetMember field : fieldSet.getFields()) this.groupByList.add(field.getFieldPath());
37+
return this;
38+
}
39+
40+
public IAggregateResultQueryBuilder groupBy(QueryDate groupByQueryDate) {
41+
this.groupByList.add(groupByQueryDate.getValue());
42+
return this;
43+
}
44+
45+
/**
46+
* @description Adds the average value of the numeric field to the dynamically generated aggregate query
47+
* @param numericQueryField The field to use for calculating the average
48+
* @return The instance of IAggregateResultQueryBuilder, to allow chaining methods
49+
*/
50+
public IAggregateResultQueryBuilder avg(IQueryField numericQueryField) {
51+
return this.avg(numericQueryField, null);
52+
}
53+
54+
public IAggregateResultQueryBuilder avg(IQueryField numericQueryField, String fieldAlias) {
55+
return buildAggregateFunction('AVG', numericQueryField, fieldAlias);
56+
}
57+
58+
public IAggregateResultQueryBuilder count(IQueryField queryField) {
59+
return this.count(queryField, null);
60+
}
61+
62+
public IAggregateResultQueryBuilder count(IQueryField queryField, String fieldAlias) {
63+
return buildAggregateFunction('COUNT', queryField, fieldAlias);
64+
}
65+
66+
public IAggregateResultQueryBuilder countDistinct(IQueryField queryField) {
67+
return this.countDistinct(queryField, null);
68+
}
69+
70+
public IAggregateResultQueryBuilder countDistinct(IQueryField queryField, String fieldAlias) {
71+
return buildAggregateFunction('COUNT_DISTINCT', queryField, fieldAlias);
72+
}
73+
74+
/**
75+
* @description Adds the maximum value of the field to the dynamically generated aggregate query
76+
* @param queryField The field to use for calculating the maximum
77+
* @return The instance of IAggregateResultQueryBuilder, to allow chaining methods
78+
*/
79+
public IAggregateResultQueryBuilder max(IQueryField queryField) {
80+
return this.max(queryField, null);
81+
}
82+
83+
public IAggregateResultQueryBuilder max(IQueryField queryField, String fieldAlias) {
84+
return buildAggregateFunction('MAX', queryField, fieldAlias);
85+
}
86+
87+
/**
88+
* @description Adds the minimum value of the field to the dynamically generated aggregate query
89+
* @param queryField The field to use for calculating the minimum
90+
* @return The instance of IAggregateResultQueryBuilder, to allow chaining methods
91+
*/
92+
public IAggregateResultQueryBuilder min(IQueryField queryField) {
93+
return this.min(queryField, null);
94+
}
95+
96+
public IAggregateResultQueryBuilder min(IQueryField queryField, String fieldAlias) {
97+
return buildAggregateFunction('MIN', queryField, fieldAlias);
98+
}
99+
100+
/**
101+
* @description Sums the values of the supplied numeric field to the dynamically generated aggregate query
102+
* @param numericQueryField The field to use for calculating the minimum
103+
* @return The instance of IAggregateResultQueryBuilder, to allow chaining methods
104+
*/
105+
public IAggregateResultQueryBuilder sum(IQueryField numericQueryField) {
106+
return this.sum(numericQueryField, null);
107+
}
108+
109+
public IAggregateResultQueryBuilder sum(IQueryField numericQueryField, String fieldAlias) {
110+
return buildAggregateFunction('SUM', numericQueryField, fieldAlias);
111+
}
112+
113+
public IAggregateResultQueryBuilder filterBy(IQueryFilter queryFilter) {
114+
super.doFilterBy(queryFilter);
115+
return this;
116+
}
117+
118+
public IAggregateResultQueryBuilder filterBy(List<IQueryFilter> queryFilters) {
119+
super.doFilterBy(queryFilters);
120+
return this;
121+
}
122+
123+
public IAggregateResultQueryBuilder orderBy(IQueryField orderByQueryField) {
124+
super.doOrderBy(orderByQueryField);
125+
return this;
126+
}
127+
128+
public IAggregateResultQueryBuilder orderBy(IQueryField orderByQueryField, QuerySortOrder sortOrder) {
129+
super.doOrderBy(orderByQueryField, sortOrder);
130+
return this;
131+
}
132+
133+
public IAggregateResultQueryBuilder orderBy(IQueryField orderByQueryField, QuerySortOrder sortOrder, QueryNullSortOrder nullsSortOrder) {
134+
super.doOrderBy(orderByQueryField, sortOrder, nullsSortOrder);
135+
return this;
136+
}
137+
138+
public IAggregateResultQueryBuilder limitCount(Integer limitCount) {
139+
super.doLimitCount(limitCount);
140+
return this;
141+
}
142+
143+
public String getQuery() {
144+
String queryString = 'SELECT ' + this.getGroupByFieldString(false) + this.getAggregateFunctionString()
145+
+ '\nFROM ' + this.sobjectType.getDescribe().getName()
146+
+ super.doGetWhereClauseString()
147+
+ this.getGroupByFieldString(true)
148+
+ super.doGetOrderByString()
149+
+ super.doGetLimitCountString();
150+
151+
return queryString;
152+
}
153+
154+
public AggregateResult getFirstQueryResult() {
155+
return this.getQueryResults()[0];
156+
}
157+
158+
public List<AggregateResult> getQueryResults() {
159+
return super.doGetQueryResults(this.getQuery());
160+
}
161+
162+
private String getGroupByFieldString(Boolean appendGroupByString) {
163+
String prefix = appendGroupByString && !this.groupByList.isEmpty() ? '\nGROUP BY ' : '';
164+
return prefix + String.join(this.groupByList, ', ');
165+
}
166+
167+
private String getAggregateFunctionString() {
168+
if(this.groupByList.isEmpty() && this.aggregateFunctionList.isEmpty()) return 'COUNT(Id) COUNT__Id';
169+
170+
this.aggregateFunctionList.sort();
171+
// The extra delimiter adds a comma when needed for grouping by fields & aggregate functions
172+
// Example: 'Type, COUNT_DISTINCT(OwnerId)'
173+
String extraDelimiter = getGroupByFieldString(false) == null ? '' : ',\n';
174+
return extraDelimiter + String.join(this.aggregateFunctionList, ', ');
175+
}
176+
177+
private IAggregateResultQueryBuilder buildAggregateFunction(String functionName, IQueryField queryField) {
178+
return this.buildAggregateFunction(functionName, queryField, null);
179+
}
180+
181+
private IAggregateResultQueryBuilder buildAggregateFunction(String functionName, IQueryField queryField, String fieldAlias) {
182+
if(fieldAlias == null) fieldAlias = functionName + '__' + queryField.getValue();
183+
// Alias: MIN(Schema.Lead.MyField__c) is auto-aliased to MIN_MyField__c
184+
this.aggregateFunctionList.add(functionName + '(' + queryField.getValue() + ') ' + fieldAlias);
185+
return this;
186+
}
187+
188+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*************************************************************************************************
2+
* This file is part of the Nebula Framework project, released under the MIT License. *
3+
* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. *
4+
*************************************************************************************************/
5+
@isTest
6+
private class AggregateResultQueryBuilder_Tests {
7+
8+
@isTest
9+
static void it_should_build_a_ridiculous_query_string() {
10+
String expectedString = 'SELECT Type,\nAVG(Amount) AVG__Amount, COUNT(AccountId) COUNT__AccountId, '
11+
+ 'COUNT_DISTINCT(OwnerId) COUNT_DISTINCT__OwnerId, MAX(CreatedDate) MAX__CreatedDate, MIN(CreatedDate) MIN__CreatedDate'
12+
+ '\nFROM Opportunity'
13+
+ '\nGROUP BY Type';
14+
15+
IAggregateResultQueryBuilder aggregateResultQueryBuilder = new AggregateResultQueryBuilder(Schema.Opportunity.SObjectType)
16+
.max(new QueryField(Schema.Opportunity.CreatedDate))
17+
.avg(new QueryField(Schema.Opportunity.Amount))
18+
.countDistinct(new QueryField(Schema.Opportunity.OwnerId))
19+
.min(new QueryField(Schema.Opportunity.CreatedDate))
20+
.groupBy(new QueryField(Schema.Opportunity.Type))
21+
.count(new QueryField(Schema.Opportunity.AccountId));
22+
String returnedQueryString = aggregateResultQueryBuilder.getQuery();
23+
24+
System.assertEquals(expectedString, returnedQueryString);
25+
26+
// Verify that the query can be executed
27+
Database.query(returnedQueryString);
28+
}
29+
30+
31+
}

src/classes/CollectionUtils.cls

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,71 @@
22
* This file is part of the Nebula Framework project, released under the MIT License. *
33
* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. *
44
*************************************************************************************************/
5+
6+
/**
7+
*
8+
* @group Utils
9+
*
10+
* @description A utility class to help with dealing with collections (lists, sets & maps)
11+
*
12+
*/
513
public without sharing class CollectionUtils {
614

15+
/**
16+
* @description Returns the last item in a list
17+
* @param listOfItems the list to check
18+
* @return The last Object in the provided list
19+
* @example
20+
* List<String> myList = new List<String>{'A', B', 'C'};
21+
* String lastItem = CollectionUtils.getLastItem(myList);
22+
* System.assertEquals('C', lastItem);
23+
*/
24+
public static Object getLastItem(List<Object> listOfItems) {
25+
Integer indexOfItem = listOfItems.size() - 1;
26+
return listOfItems[indexOfItem];
27+
}
28+
29+
/**
30+
* @description Removes the last item in the provided list & returns the item
31+
* @param listOfItems the list to check
32+
* @return The last Object in the provided list
33+
* @example
34+
* List<String> myList = new List<String>{'A', B', 'C'};
35+
* System.assertEquals(3, myList.size());
36+
* String lastItem = CollectionUtils.getLastItem(myList);
37+
* System.assertEquals('C', lastItem);
38+
* System.assertEquals(2, myList.size());
39+
*/
40+
public static Object pop(List<Object> listToSplice) {
41+
return splice(listToSplice, listToSplice.size() - 1);
42+
}
43+
44+
/**
45+
* @description Removes the item in the specified index from the provided list & returns the item
46+
* @param listOfItems The list to check
47+
* @param indexOfItem The index of the item to remove
48+
* @return The Object at the specified index
49+
* @example
50+
* List<String> myList = new List<String>{'A', B', 'C'};
51+
* System.assertEquals(3, myList.size());
52+
* String itemToRemove = CollectionUtils.splice(myList, 1);
53+
* System.assertEquals('B', itemToRemove);
54+
* System.assertEquals(2, myList.size());
55+
*/
56+
public static Object splice(List<Object> listToSplice, Integer indexOfItem) {
57+
Object itemToRemove = listToSplice[indexOfItem];
58+
listToSplice.remove(indexOfItem);
59+
return itemToRemove;
60+
}
61+
62+
/**
63+
* @description Determines if the provided input is a type of collection (list, set or map)
64+
* @param input The Object to check
65+
* @return true if the item is a type of collection, otherwise returns false
66+
* @example
67+
* List<String> myList = new List<String>{'A', 'B', 'C'};
68+
* System.assert(CollectionUtils.isCollection(myList));
69+
*/
770
public static Boolean isCollection(Object input) {
871
return isList(input) || isSet(input) || isMap(input);
972
}

src/classes/CollectionUtils_Tests.cls

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,37 @@
55
@isTest
66
private class CollectionUtils_Tests {
77

8+
@isTest
9+
static void it_should_get_the_last_item_in_a_list() {
10+
List<String> collectionToCheck = new List<String>{'A', 'B', 'C'};
11+
Integer originalCollectionSize = collectionToCheck.size();
12+
13+
System.assertEquals('C', CollectionUtils.getLastItem(collectionToCheck));
14+
System.assertEquals(originalCollectionSize, collectionToCheck.size());
15+
}
16+
17+
@isTest
18+
static void it_should_pop_the_last_item_in_a_list() {
19+
List<String> collectionToCheck = new List<String>{'A', 'B', 'C'};
20+
Integer originalCollectionSize = collectionToCheck.size();
21+
22+
System.assertEquals('C', CollectionUtils.pop(collectionToCheck));
23+
System.assertEquals(originalCollectionSize - 1, collectionToCheck.size());
24+
// Verify that the last item has been removed
25+
System.assertEquals(false, new Set<String>(collectionToCheck).contains('C'));
26+
}
27+
28+
@isTest
29+
static void it_should_splice_the_specified_item_in_a_list() {
30+
List<String> collectionToCheck = new List<String>{'A', 'B', 'C'};
31+
Integer originalCollectionSize = collectionToCheck.size();
32+
33+
System.assertEquals('B', CollectionUtils.splice(collectionToCheck, 1));
34+
System.assertEquals(originalCollectionSize - 1, collectionToCheck.size());
35+
// Verify that the specified item has been removed
36+
System.assertEquals(false, new Set<String>(collectionToCheck).contains('B'));
37+
}
38+
839
// Tests for lists
940
@isTest
1041
static void it_should_say_that_a_list_of_strings_is_a_list_and_a_collection() {

0 commit comments

Comments
 (0)