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

Commit

Permalink
Release 2.0
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jongpie authored Aug 29, 2017
1 parent 44814ec commit 2948ab2
Show file tree
Hide file tree
Showing 80 changed files with 2,283 additions and 744 deletions.
2 changes: 2 additions & 0 deletions .atom-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cwd: '{PROJECT_PATH}'
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
188 changes: 188 additions & 0 deletions src/classes/AggregateResultQueryBuilder.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*************************************************************************************************
* This file is part of the Nebula Framework project, released under the MIT License. *
* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. *
*************************************************************************************************/

/**
*
* @group Query Builder
*
* @description A builder class that generates dynamic queries & returns a list of AggregateResult
*
*/
public class AggregateResultQueryBuilder extends QueryBuilder implements IAggregateResultQueryBuilder {

private Schema.SObjectType sobjectType;
private List<String> groupByList;
private List<String> aggregateFunctionList;

public AggregateResultQueryBuilder(Schema.SObjectType sobjectType) {
this.sobjectType = sobjectType;

this.aggregateFunctionList = new List<String>();
this.groupByList = new List<String>();
}

public IAggregateResultQueryBuilder groupBy(IQueryField groupByQueryField) {
return this.groupBy(new List<IQueryField>{groupByQueryField});
}

public IAggregateResultQueryBuilder groupBy(List<IQueryField> groupByQueryFields) {
for(IQueryField groupByQueryField : groupByQueryFields) this.groupByList.add(groupByQueryField.getValue());
return this;
}

public IAggregateResultQueryBuilder groupBy(Schema.FieldSet fieldSet) {
for(Schema.FieldSetMember field : fieldSet.getFields()) this.groupByList.add(field.getFieldPath());
return this;
}

public IAggregateResultQueryBuilder groupBy(QueryDate groupByQueryDate) {
this.groupByList.add(groupByQueryDate.getValue());
return this;
}

/**
* @description Adds the average value of the numeric field to the dynamically generated aggregate query
* @param numericQueryField The field to use for calculating the average
* @return The instance of IAggregateResultQueryBuilder, to allow chaining methods
*/
public IAggregateResultQueryBuilder avg(IQueryField numericQueryField) {
return this.avg(numericQueryField, null);
}

public IAggregateResultQueryBuilder avg(IQueryField numericQueryField, String fieldAlias) {
return buildAggregateFunction('AVG', numericQueryField, fieldAlias);
}

public IAggregateResultQueryBuilder count(IQueryField queryField) {
return this.count(queryField, null);
}

public IAggregateResultQueryBuilder count(IQueryField queryField, String fieldAlias) {
return buildAggregateFunction('COUNT', queryField, fieldAlias);
}

public IAggregateResultQueryBuilder countDistinct(IQueryField queryField) {
return this.countDistinct(queryField, null);
}

public IAggregateResultQueryBuilder countDistinct(IQueryField queryField, String fieldAlias) {
return buildAggregateFunction('COUNT_DISTINCT', queryField, fieldAlias);
}

/**
* @description Adds the maximum value of the field to the dynamically generated aggregate query
* @param queryField The field to use for calculating the maximum
* @return The instance of IAggregateResultQueryBuilder, to allow chaining methods
*/
public IAggregateResultQueryBuilder max(IQueryField queryField) {
return this.max(queryField, null);
}

public IAggregateResultQueryBuilder max(IQueryField queryField, String fieldAlias) {
return buildAggregateFunction('MAX', queryField, fieldAlias);
}

/**
* @description Adds the minimum value of the field to the dynamically generated aggregate query
* @param queryField The field to use for calculating the minimum
* @return The instance of IAggregateResultQueryBuilder, to allow chaining methods
*/
public IAggregateResultQueryBuilder min(IQueryField queryField) {
return this.min(queryField, null);
}

public IAggregateResultQueryBuilder min(IQueryField queryField, String fieldAlias) {
return buildAggregateFunction('MIN', queryField, fieldAlias);
}

/**
* @description Sums the values of the supplied numeric field to the dynamically generated aggregate query
* @param numericQueryField The field to use for calculating the minimum
* @return The instance of IAggregateResultQueryBuilder, to allow chaining methods
*/
public IAggregateResultQueryBuilder sum(IQueryField numericQueryField) {
return this.sum(numericQueryField, null);
}

public IAggregateResultQueryBuilder sum(IQueryField numericQueryField, String fieldAlias) {
return buildAggregateFunction('SUM', numericQueryField, fieldAlias);
}

public IAggregateResultQueryBuilder filterBy(IQueryFilter queryFilter) {
super.doFilterBy(queryFilter);
return this;
}

public IAggregateResultQueryBuilder filterBy(List<IQueryFilter> queryFilters) {
super.doFilterBy(queryFilters);
return this;
}

public IAggregateResultQueryBuilder orderBy(IQueryField orderByQueryField) {
super.doOrderBy(orderByQueryField);
return this;
}

public IAggregateResultQueryBuilder orderBy(IQueryField orderByQueryField, QuerySortOrder sortOrder) {
super.doOrderBy(orderByQueryField, sortOrder);
return this;
}

public IAggregateResultQueryBuilder orderBy(IQueryField orderByQueryField, QuerySortOrder sortOrder, QueryNullSortOrder nullsSortOrder) {
super.doOrderBy(orderByQueryField, sortOrder, nullsSortOrder);
return this;
}

public IAggregateResultQueryBuilder limitCount(Integer limitCount) {
super.doLimitCount(limitCount);
return this;
}

public String getQuery() {
String queryString = 'SELECT ' + this.getGroupByFieldString(false) + this.getAggregateFunctionString()
+ '\nFROM ' + this.sobjectType.getDescribe().getName()
+ super.doGetWhereClauseString()
+ this.getGroupByFieldString(true)
+ super.doGetOrderByString()
+ super.doGetLimitCountString();

return queryString;
}

public AggregateResult getFirstQueryResult() {
return this.getQueryResults()[0];
}

public List<AggregateResult> getQueryResults() {
return super.doGetQueryResults(this.getQuery());
}

private String getGroupByFieldString(Boolean appendGroupByString) {
String prefix = appendGroupByString && !this.groupByList.isEmpty() ? '\nGROUP BY ' : '';
return prefix + String.join(this.groupByList, ', ');
}

private String getAggregateFunctionString() {
if(this.groupByList.isEmpty() && this.aggregateFunctionList.isEmpty()) return 'COUNT(Id) COUNT__Id';

this.aggregateFunctionList.sort();
// The extra delimiter adds a comma when needed for grouping by fields & aggregate functions
// Example: 'Type, COUNT_DISTINCT(OwnerId)'
String extraDelimiter = getGroupByFieldString(false) == null ? '' : ',\n';
return extraDelimiter + String.join(this.aggregateFunctionList, ', ');
}

private IAggregateResultQueryBuilder buildAggregateFunction(String functionName, IQueryField queryField) {
return this.buildAggregateFunction(functionName, queryField, null);
}

private IAggregateResultQueryBuilder buildAggregateFunction(String functionName, IQueryField queryField, String fieldAlias) {
if(fieldAlias == null) fieldAlias = functionName + '__' + queryField.getValue();
// Alias: MIN(Schema.Lead.MyField__c) is auto-aliased to MIN_MyField__c
this.aggregateFunctionList.add(functionName + '(' + queryField.getValue() + ') ' + fieldAlias);
return this;
}

}
31 changes: 31 additions & 0 deletions src/classes/AggregateResultQueryBuilder_Tests.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*************************************************************************************************
* This file is part of the Nebula Framework project, released under the MIT License. *
* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. *
*************************************************************************************************/
@isTest
private class AggregateResultQueryBuilder_Tests {

@isTest
static void it_should_build_a_ridiculous_query_string() {
String expectedString = 'SELECT Type,\nAVG(Amount) AVG__Amount, COUNT(AccountId) COUNT__AccountId, '
+ 'COUNT_DISTINCT(OwnerId) COUNT_DISTINCT__OwnerId, MAX(CreatedDate) MAX__CreatedDate, MIN(CreatedDate) MIN__CreatedDate'
+ '\nFROM Opportunity'
+ '\nGROUP BY Type';

IAggregateResultQueryBuilder aggregateResultQueryBuilder = new AggregateResultQueryBuilder(Schema.Opportunity.SObjectType)
.max(new QueryField(Schema.Opportunity.CreatedDate))
.avg(new QueryField(Schema.Opportunity.Amount))
.countDistinct(new QueryField(Schema.Opportunity.OwnerId))
.min(new QueryField(Schema.Opportunity.CreatedDate))
.groupBy(new QueryField(Schema.Opportunity.Type))
.count(new QueryField(Schema.Opportunity.AccountId));
String returnedQueryString = aggregateResultQueryBuilder.getQuery();

System.assertEquals(expectedString, returnedQueryString);

// Verify that the query can be executed
Database.query(returnedQueryString);
}


}
63 changes: 63 additions & 0 deletions src/classes/CollectionUtils.cls
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,71 @@
* This file is part of the Nebula Framework project, released under the MIT License. *
* See LICENSE file or go to https://github.com/jongpie/NebulaFramework for full license details. *
*************************************************************************************************/

/**
*
* @group Utils
*
* @description A utility class to help with dealing with collections (lists, sets & maps)
*
*/
public without sharing class CollectionUtils {

/**
* @description Returns the last item in a list
* @param listOfItems the list to check
* @return The last Object in the provided list
* @example
* List<String> myList = new List<String>{'A', B', 'C'};
* String lastItem = CollectionUtils.getLastItem(myList);
* System.assertEquals('C', lastItem);
*/
public static Object getLastItem(List<Object> listOfItems) {
Integer indexOfItem = listOfItems.size() - 1;
return listOfItems[indexOfItem];
}

/**
* @description Removes the last item in the provided list & returns the item
* @param listOfItems the list to check
* @return The last Object in the provided list
* @example
* List<String> myList = new List<String>{'A', B', 'C'};
* System.assertEquals(3, myList.size());
* String lastItem = CollectionUtils.getLastItem(myList);
* System.assertEquals('C', lastItem);
* System.assertEquals(2, myList.size());
*/
public static Object pop(List<Object> listToSplice) {
return splice(listToSplice, listToSplice.size() - 1);
}

/**
* @description Removes the item in the specified index from the provided list & returns the item
* @param listOfItems The list to check
* @param indexOfItem The index of the item to remove
* @return The Object at the specified index
* @example
* List<String> myList = new List<String>{'A', B', 'C'};
* System.assertEquals(3, myList.size());
* String itemToRemove = CollectionUtils.splice(myList, 1);
* System.assertEquals('B', itemToRemove);
* System.assertEquals(2, myList.size());
*/
public static Object splice(List<Object> listToSplice, Integer indexOfItem) {
Object itemToRemove = listToSplice[indexOfItem];
listToSplice.remove(indexOfItem);
return itemToRemove;
}

/**
* @description Determines if the provided input is a type of collection (list, set or map)
* @param input The Object to check
* @return true if the item is a type of collection, otherwise returns false
* @example
* List<String> myList = new List<String>{'A', 'B', 'C'};
* System.assert(CollectionUtils.isCollection(myList));
*/
public static Boolean isCollection(Object input) {
return isList(input) || isSet(input) || isMap(input);
}
Expand Down
31 changes: 31 additions & 0 deletions src/classes/CollectionUtils_Tests.cls
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,37 @@
@isTest
private class CollectionUtils_Tests {

@isTest
static void it_should_get_the_last_item_in_a_list() {
List<String> collectionToCheck = new List<String>{'A', 'B', 'C'};
Integer originalCollectionSize = collectionToCheck.size();

System.assertEquals('C', CollectionUtils.getLastItem(collectionToCheck));
System.assertEquals(originalCollectionSize, collectionToCheck.size());
}

@isTest
static void it_should_pop_the_last_item_in_a_list() {
List<String> collectionToCheck = new List<String>{'A', 'B', 'C'};
Integer originalCollectionSize = collectionToCheck.size();

System.assertEquals('C', CollectionUtils.pop(collectionToCheck));
System.assertEquals(originalCollectionSize - 1, collectionToCheck.size());
// Verify that the last item has been removed
System.assertEquals(false, new Set<String>(collectionToCheck).contains('C'));
}

@isTest
static void it_should_splice_the_specified_item_in_a_list() {
List<String> collectionToCheck = new List<String>{'A', 'B', 'C'};
Integer originalCollectionSize = collectionToCheck.size();

System.assertEquals('B', CollectionUtils.splice(collectionToCheck, 1));
System.assertEquals(originalCollectionSize - 1, collectionToCheck.size());
// Verify that the specified item has been removed
System.assertEquals(false, new Set<String>(collectionToCheck).contains('B'));
}

// Tests for lists
@isTest
static void it_should_say_that_a_list_of_strings_is_a_list_and_a_collection() {
Expand Down
Loading

0 comments on commit 2948ab2

Please sign in to comment.