diff --git a/.travis.yml b/.travis.yml
index 020e8397..22f74c66 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,4 +4,4 @@ node_js:
install:
- npm install -g jsforce-metadata-tools
script:
- - jsforce-deploy --checkOnly -u $DEPLOYMENT_USERNAME -p $DEPLOYMENT_PASSWORD$DEPLOYMENT_TOKEN -D $TRAVIS_BUILD_DIR/src -l $DEPLOYMENT_LOGIN_URL --rollbackOnError true --testLevel $DEPLOYMENT_TEST_LEVEL --pollTimeout $POLL_TIMEOUT --pollInterval $POLL_INTERVAL--verbose
+ - jsforce-deploy --checkOnly -u $DEPLOYMENT_USERNAME -p $DEPLOYMENT_PASSWORD$DEPLOYMENT_TOKEN -D $TRAVIS_BUILD_DIR/src -l $DEPLOYMENT_LOGIN_URL --rollbackOnError true --testLevel $DEPLOYMENT_TEST_LEVEL --pollTimeout $POLL_TIMEOUT --pollInterval $POLL_INTERVAL--verbose
\ No newline at end of file
diff --git a/README.md b/README.md
index d8a4eab4..8da03f7a 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,16 @@
# Apex Query Generator
-
+
-## Issue
---Coming soon--
+## Overview
+This is an freestanding version of the [Nebula framework's](https://github.com/jongpie/NebulaFramework/) query engine - it has been updated to remove any dependencies on the rest of the Nebula framework.
-## Goals
-The overall goal of the project is to help auto-generate dynamic SOQL for commonly used queries
-* Provide a structure to centralise frequently used queries for each SObject
-* Provide a configurable way to change the query fields, while still preventing accidental deletion of fields being used
-* Provide a way to create a WHERE statement as a string in Apex, while still preventing accidental deletion of fields being used
-
-## Implementation
---Coming soon--
-
-### Example Implementation: LeadQueryRepository.cls
\ No newline at end of file
+## Features
+The overall goal of the project is to generate dynamic SOQL & SOSL queries. Features currently include
+* Leverage field-level security to dynamically include fields
+* Dynamically include filter conditions (not possible with standard SOQL/SOSL)
+* Retain Salesforce's compilation-time errors for invalid fields while still taking advantage of dynamic queries - this helps avoid issues with deleting fields, misspelled field names, etc that can occur when working with strings and dynamic queries
+* Support for nearly all SOQL & SOSL features & keywords, including date literals, aggregate results and more
+* Easy-to-use query caching
\ No newline at end of file
diff --git a/src/classes/AccountRepository_Tests.cls b/src/classes/AccountRepository_Tests.cls
deleted file mode 100644
index 603ae30d..00000000
--- a/src/classes/AccountRepository_Tests.cls
+++ /dev/null
@@ -1,118 +0,0 @@
-@isTest
-public class AccountRepository_Tests {
- @testSetup
- static void setupData() {
- List accounts = new List();
- for(Integer i =0; i <3; i++) {
- Account account = new Account();
- account.FirstName = 'George' + i;
- account.LastName = 'Washington';
-
- accounts.add(account);
- }
-
- insert accounts;
- }
-
- @isTest
- static void it_should_return_an_account_by_id() {
- //Given I have a known account Id
- //When I query for that record in particular
- //Then it should be returned
- Account account = [SELECT Id FROM Account LIMIT 1];
-
- Test.startTest();
- Account returnedAccount = new AccountRepository().getById(account.Id);
- Test.stopTest();
-
- System.assertEquals(account.Id,returnedAccount.Id);
- }
-
- @isTest
- static void it_should_return_accounts_by_id_list() {
- //Given that I have known accounts
- //When I query for them by Id
- //Then the accounts should be returned
-
- List expectedAccounts = [SELECT Id FROM Account];
- List expectedAccountIds = new List(new Map(expectedAccounts).keySet());
-
- Test.startTest();
- Map returnedAccountsMap = new Map(new AccountRepository().getById(expectedAccountIds));
- Test.stopTest();
-
- System.assertEquals(expectedAccounts.size(),returnedAccountsMap.size());
- }
-
- @isTest
- static void it_should_return_accounts_for_a_given_time_period() {
- //Given that I have accounts
- //When I query for them with a given field and time range
- //Then only accounts that match both those criteria should be returned
- List expectedAccounts = [SELECT Id FROM Account];
-
- //Now create an account that should not be returned.
- Account account = TestDataGenerator.createPersonAccount();
- account.CreatedDate = System.today().addDays(-1);
- insert account;
-
- Test.startTest();
- Schema.SObjectField source = Schema.Account.AccountSource;
- Map returnedAccountsMap = new Map(new AccountRepository().getByFieldAndTypeForGivenTimePeriod(source, 'Web', new DateLiterals().TODAY));
- Test.stopTest();
-
- System.assertEquals(expectedAccounts.size(),returnedAccountsMap.size());
- for(Account acc : returnedAccountsMap.values()) {
- System.assertNotEquals(account.Id,acc.Id);
- }
- }
-
- @isTest
- static void it_should_return_accounts_by_field_for_a_set_of_ids() {
- //Given I have a set of account Ids
- //When I query for those accounts and a specific field
- //Then the matching accounts should be returned
- List expectedAccounts = [SELECT Id FROM Account];
- Set accountIds = new Set(new List(new Map(expectedAccounts).keySet()));
-
- Test.startTest();
- Schema.SObjectField source = Schema.Account.AccountSource;
- List returnedAccounts = new AccountRepository().getByFieldForIds(source,'Web',accountIds);
- Test.stopTest();
-
- System.assertEquals(expectedAccounts.size(),returnedAccounts.size());
- }
-
- @isTest
- static void it_should_return_accounts_by_field_for_a_list_of_ids() {
- //Given I have a list of account Ids
- //When I query for those accounts and a specific field
- //Then the matching accounts should be returned
- List expectedAccounts = [SELECT Id FROM Account];
- List accountIds = new List(new Map(expectedAccounts).keySet());
-
- Test.startTest();
- Schema.SObjectField source = Schema.Account.AccountSource;
- List returnedAccounts = new AccountRepository().getByFieldForIds(source,'Web',accountIds);
- Test.stopTest();
-
- System.assertEquals(expectedAccounts.size(),returnedAccounts.size());
- }
-
- @isTest
- static void it_should_return_accounts_that_match_sosl_search_term() {
- //Given that I have a string
- //When I search accounts for that string
- //Then the accounts with a matching string should be returned
-
- List expectedAccounts = (List)[FIND 'Web' IN ALL FIELDS RETURNING Account][0];
-
- Test.startTest();
- Map returnedAccountsMap = new Map(new AccountRepository().searchInAllFields('Web'));
- Test.stopTest();
-
- for(Account account : expectedAccounts) {
- System.assert(returnedAccountsMap.containsKey(account.Id));
- }
- }
-}
\ No newline at end of file
diff --git a/src/classes/CollectionUtils.cls b/src/classes/CollectionUtils.cls
new file mode 100644
index 00000000..213be765
--- /dev/null
+++ b/src/classes/CollectionUtils.cls
@@ -0,0 +1,108 @@
+/*************************************************************************************************
+* 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 myList = new List{'A', B', 'C'};
+ * String lastItem = CollectionUtils.getLastItem(myList);
+ * System.assertEquals('C', lastItem);
+ */
+ public static Object getLastItem(List 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 myList = new List{'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 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 myList = new List{'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 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 myList = new List{'A', 'B', 'C'};
+ * System.assert(CollectionUtils.isCollection(myList));
+ */
+ public static Boolean isCollection(Object input) {
+ return isList(input) || isSet(input) || isMap(input);
+ }
+
+ public static Boolean isList(Object input) {
+ // If we can cast the object to a list of objects, then it's a list
+ try {
+ Object convertedInput = (List)input;
+ return true;
+ } catch(System.TypeException ex) {
+ return false;
+ }
+ }
+
+ public static Boolean isSet(Object input) {
+ // We can't cast the object to a set of objects
+ // But if we try to cast it to a list of objects & it's a set,
+ // then a TypeException is thrown so we know it's a set
+ try {
+ Object convertedInput = (List)input;
+ return false;
+ } catch(System.TypeException ex) {
+ return ex.getMessage().contains('Set<');
+ }
+ }
+
+ public static Boolean isMap(Object input) {
+ // We can't cast the object to a map of objects
+ // But if we try to cast it to a list of objects & it's a map,
+ // then a TypeException is thrown so we know it's a map
+ try {
+ Object convertedInput = (List)input;
+ return false;
+ } catch(System.TypeException ex) {
+ return ex.getMessage().contains('Map<');
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/AccountRepository.cls-meta.xml b/src/classes/CollectionUtils.cls-meta.xml
similarity index 80%
rename from src/classes/AccountRepository.cls-meta.xml
rename to src/classes/CollectionUtils.cls-meta.xml
index cbddff8c..94f6f064 100644
--- a/src/classes/AccountRepository.cls-meta.xml
+++ b/src/classes/CollectionUtils.cls-meta.xml
@@ -1,5 +1,5 @@
- 38.0
+ 40.0
Active
diff --git a/src/classes/CollectionUtils_Tests.cls b/src/classes/CollectionUtils_Tests.cls
new file mode 100644
index 00000000..3e654f8f
--- /dev/null
+++ b/src/classes/CollectionUtils_Tests.cls
@@ -0,0 +1,171 @@
+/*************************************************************************************************
+* 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 CollectionUtils_Tests {
+
+ @isTest
+ static void it_should_get_the_last_item_in_a_list() {
+ List collectionToCheck = new List{'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 collectionToCheck = new List{'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(collectionToCheck).contains('C'));
+ }
+
+ @isTest
+ static void it_should_splice_the_specified_item_in_a_list() {
+ List collectionToCheck = new List{'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(collectionToCheck).contains('B'));
+ }
+
+ // Tests for lists
+ @isTest
+ static void it_should_say_that_a_list_of_strings_is_a_list_and_a_collection() {
+ List collectionToCheck = new List{'A', 'B', 'C'};
+
+ System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck));
+ System.assertEquals(true, CollectionUtils.isList(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isSet(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isMap(collectionToCheck));
+ }
+
+ @isTest
+ static void it_should_say_that_a_list_of_integers_is_a_list_and_a_collection() {
+ List collectionToCheck = new List{1, 2, 3};
+
+ System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck));
+ System.assertEquals(true, CollectionUtils.isList(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isSet(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isMap(collectionToCheck));
+ }
+
+ @isTest
+ static void it_should_say_that_a_list_of_users_is_a_list_and_a_collection() {
+ List collectionToCheck = [SELECT Id FROM User LIMIT 10];
+
+ System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck));
+ System.assertEquals(true, CollectionUtils.isList(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isSet(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isMap(collectionToCheck));
+ }
+
+ // Tests for sets
+ @isTest
+ static void it_should_say_that_a_set_of_strings_is_a_set_and_a_collection() {
+ Set collectionToCheck = new Set{'A', 'B', 'C'};
+
+ System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck));
+ System.assertEquals(true, CollectionUtils.isSet(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isList(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isMap(collectionToCheck));
+ }
+
+ @isTest
+ static void it_should_say_that_a_set_of_integers_is_a_set_and_a_collection() {
+ Set collectionToCheck = new Set{1, 2, 3};
+
+ System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck));
+ System.assertEquals(true, CollectionUtils.isSet(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isList(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isMap(collectionToCheck));
+ }
+
+ @isTest
+ static void it_should_say_that_a_set_of_users_is_a_set_and_a_collection() {
+ Set collectionToCheck = new Set([SELECT Id FROM User LIMIT 10]);
+
+ System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck));
+ System.assertEquals(true, CollectionUtils.isSet(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isList(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isMap(collectionToCheck));
+ }
+
+ // Tests for maps
+ @isTest
+ static void it_should_say_that_a_map_of_strings_is_a_map_and_a_collection() {
+ Map collectionToCheck = new Map{
+ 'First' => 1,
+ 'Second' => 2,
+ 'Third' => 3
+ };
+
+ System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck));
+ System.assertEquals(true, CollectionUtils.isMap(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isList(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isSet(collectionToCheck));
+ }
+
+ @isTest
+ static void it_should_say_that_a_map_of_integers_is_a_map_and_a_collection() {
+ Map collectionToCheck = new Map{
+ 1 => 'First',
+ 2 => 'Second',
+ 3 => 'Third'
+ };
+
+ System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck));
+ System.assertEquals(true, CollectionUtils.isMap(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isList(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isSet(collectionToCheck));
+ }
+
+ @isTest
+ static void it_should_say_that_a_map_of_users_is_a_map_and_a_collection() {
+ Map collectionToCheck = new Map([SELECT Id FROM User LIMIT 10]);
+
+ System.assertEquals(true, CollectionUtils.isCollection(collectionToCheck));
+ System.assertEquals(true, CollectionUtils.isMap(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isList(collectionToCheck));
+ System.assertEquals(false, CollectionUtils.isSet(collectionToCheck));
+ }
+
+ // Negative tests
+ @isTest
+ static void it_should_say_that_a_string_is_not_collection() {
+ String valueToCheck = 'test string';
+
+ System.assertEquals(false, CollectionUtils.isCollection(valueToCheck));
+ System.assertEquals(false, CollectionUtils.isList(valueToCheck));
+ System.assertEquals(false, CollectionUtils.isSet(valueToCheck));
+ System.assertEquals(false, CollectionUtils.isMap(valueToCheck));
+ }
+
+ @isTest
+ static void it_should_say_that_an_integer_is_not_collection() {
+ Integer valueToCheck = 1;
+
+ System.assertEquals(false, CollectionUtils.isCollection(valueToCheck));
+ System.assertEquals(false, CollectionUtils.isList(valueToCheck));
+ System.assertEquals(false, CollectionUtils.isSet(valueToCheck));
+ System.assertEquals(false, CollectionUtils.isMap(valueToCheck));
+ }
+
+ @isTest
+ static void it_should_say_that_a_user_is_not_collection() {
+ User valueToCheck = [SELECT Id FROM User WHERE Id = :UserInfo.getUserId()];
+
+ System.assertEquals(false, CollectionUtils.isCollection(valueToCheck));
+ System.assertEquals(false, CollectionUtils.isList(valueToCheck));
+ System.assertEquals(false, CollectionUtils.isSet(valueToCheck));
+ System.assertEquals(false, CollectionUtils.isMap(valueToCheck));
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/DateLiterals_UnitTests.cls-meta.xml b/src/classes/CollectionUtils_Tests.cls-meta.xml
similarity index 80%
rename from src/classes/DateLiterals_UnitTests.cls-meta.xml
rename to src/classes/CollectionUtils_Tests.cls-meta.xml
index cbddff8c..94f6f064 100644
--- a/src/classes/DateLiterals_UnitTests.cls-meta.xml
+++ b/src/classes/CollectionUtils_Tests.cls-meta.xml
@@ -1,5 +1,5 @@
- 38.0
+ 40.0
Active
diff --git a/src/classes/DateLiterals.cls b/src/classes/DateLiterals.cls
deleted file mode 100644
index 4134b331..00000000
--- a/src/classes/DateLiterals.cls
+++ /dev/null
@@ -1,178 +0,0 @@
-public class DateLiterals {
- private final String LAST_N = 'LAST_N_{0}: {1}';
- private final String NEXT_N = 'NEXT_N_{0}: {1}';
-
- private final String DAYS = 'DAYS';
- private final String WEEKS = 'WEEKS';
- private final String MONTHS = 'MONTHS';
- private final String QUARTERS = 'QUARTERS';
- private final String FISCAL_QUARTERS = 'FISCAL_QUARTERS';
- private final String YEARS = 'YEARS';
- private final String FISCAL_YEARS = 'FISCAL_YEARS';
-
- public String value {get; private set;}
-
- //Actual constant literals
- public DateLiterals YESTERDAY {
- get {return this.setValue('YESTERDAY');}
- }
-
- public DateLiterals TODAY {
- get {return this.setValue('TODAY');}
- }
-
- public DateLiterals TOMORROW {
- get {return this.setValue('TOMORROW');}
- }
-
- public DateLiterals LAST_WEEK {
- get {return this.setValue('LAST_WEEK');}
- }
-
- public DateLiterals THIS_WEEK {
- get {return this.setValue('THIS_WEEK');}
- }
-
- public DateLiterals NEXT_WEEK {
- get {return this.setValue('NEXT_WEEK');}
- }
-
- public DateLiterals LAST_MONTH {
- get {return this.setValue('LAST_MONTH');}
- }
-
- public DateLiterals THIS_MONTH {
- get {return this.setValue('THIS_MONTH');}
- }
-
- public DateLiterals NEXT_MONTH {
- get {return this.setValue('NEXT_MONTH');}
- }
-
- public DateLiterals LAST_90_DAYS {
- get {return this.setValue('LAST_90_DAYS');}
- }
-
- public DateLiterals NEXT_90_DAYS {
- get {return this.setValue('NEXT_90_DAYS');}
- }
-
- public DateLiterals THIS_QUARTER {
- get {return this.setValue('THIS_QUARTER');}
- }
-
- public DateLiterals THIS_FISCAL_QUARTER {
- get {return this.setValue('THIS_FISCAL_QUARTER');}
- }
-
- public DateLiterals LAST_QUARTER {
- get {return this.setValue('LAST_QUARTER');}
- }
-
- public DateLiterals LAST_FISCAL_QUARTER {
- get {return this.setValue('LAST_FISCAL_QUARTER');}
- }
-
- public DateLiterals NEXT_QUARTER {
- get {return this.setValue('NEXT_QUARTER');}
- }
-
- public DateLiterals NEXT_FISCAL_QUARTER {
- get {return this.setValue('NEXT_FISCAL_QUARTER');}
- }
-
- public DateLiterals THIS_YEAR {
- get {return this.setValue('THIS_YEAR');}
- }
-
- public DateLiterals THIS_FISCAL_YEAR {
- get {return this.setValue('THIS_FISCAL_YEAR');}
- }
-
- public DateLiterals LAST_YEAR {
- get {return this.setValue('LAST_YEAR');}
- }
-
- public DateLiterals LAST_FISCAL_YEAR {
- get {return this.setValue('LAST_FISCAL_YEAR');}
- }
-
- public DateLiterals NEXT_YEAR {
- get {return this.setValue('NEXT_YEAR');}
- }
-
- public DateLiterals NEXT_FISCAL_YEAR {
- get {return this.setValue('NEXT_FISCAL_YEAR');}
- }
-
- ////Buildable literals
- public DateLiterals LAST_N_DAYS(Integer num) {
- String parsedValue = String.format(LAST_N, new List{DAYS,String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals LAST_N_WEEKS(Integer num) {
- String parsedValue = String.format(LAST_N, new List{WEEKS, String.valueof(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals LAST_N_MONTHS(Integer num) {
- String parsedValue = String.format(LAST_N, new List{MONTHS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals LAST_N_QUARTERS(Integer num) {
- String parsedValue = String.format(LAST_N, new List{QUARTERS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals LAST_N_YEARS(Integer num) {
- String parsedValue = String.format(LAST_N, new List{YEARS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals LAST_N_FISCAL_YEARS(Integer num) {
- String parsedValue = String.format(LAST_N, new List{FISCAL_YEARS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals NEXT_N_DAYS(Integer num) {
- String parsedValue = String.format(NEXT_N, new List{DAYS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals NEXT_N_WEEKS(Integer num) {
- String parsedValue = String.format(NEXT_N, new List{WEEKS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals NEXT_N_MONTHS(Integer num) {
- String parsedValue = String.format(NEXT_N, new List{MONTHS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals NEXT_N_QUARTERS(Integer num) {
- String parsedValue = String.format(NEXT_N, new List{QUARTERS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals NEXT_N_FISCAL_QUARTERS(Integer num) {
- String parsedValue = String.format(NEXT_N, new List{FISCAL_QUARTERS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals NEXT_N_YEARS(Integer num) {
- String parsedValue = String.format(NEXT_N, new List{YEARS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- public DateLiterals NEXT_N_FISCAL_YEARS(Integer num) {
- String parsedValue = String.format(NEXT_N, new List{FISCAL_YEARS, String.valueOf(num)});
- return this.setValue(parsedValue);
- }
-
- private DateLiterals setValue(String value) {
- this.value = value;
- return this;
- }
-}
\ No newline at end of file
diff --git a/src/classes/DateLiterals_UnitTests.cls b/src/classes/DateLiterals_UnitTests.cls
deleted file mode 100644
index d78831de..00000000
--- a/src/classes/DateLiterals_UnitTests.cls
+++ /dev/null
@@ -1,220 +0,0 @@
-@isTest
-public class DateLiterals_UnitTests {
- private static integer n_number = 5;
-
- @isTest
- static void it_should_return_yesterday_string() {
- DateLiterals dateLiteral = new DateLiterals().YESTERDAY;
- System.assertEquals('YESTERDAY', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_today_string() {
- DateLiterals dateLiteral = new DateLiterals().TODAY;
- System.assertEquals('TODAY', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_tomorrow_string() {
- DateLiterals dateLiteral = new DateLiterals().TOMORROW;
- System.assertEquals('TOMORROW', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_week_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_WEEK;
- System.assertEquals('LAST_WEEK', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_this_week_string() {
- DateLiterals dateLiteral = new DateLiterals().THIS_WEEK;
- System.assertEquals('THIS_WEEK', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_week_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_WEEK;
- System.assertEquals('NEXT_WEEK', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_month_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_MONTH;
- System.assertEquals('LAST_MONTH', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_this_month_string() {
- DateLiterals dateLiteral = new DateLiterals().THIS_MONTH;
- System.assertEquals('THIS_MONTH', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_month_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_MONTH;
- System.assertEquals('NEXT_MONTH', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_ninety_days_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_90_DAYS;
- System.assertEquals('LAST_90_DAYS', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_ninety_days_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_90_DAYS;
- System.assertEquals('NEXT_90_DAYS', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_this_quarter_string() {
- DateLiterals dateLiteral = new DateLiterals().THIS_QUARTER;
- System.assertEquals('THIS_QUARTER', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_quarter_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_QUARTER;
- System.assertEquals('LAST_QUARTER', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_fiscal_quarter_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_FISCAL_QUARTER;
- System.assertEquals('LAST_FISCAL_QUARTER', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_this_fiscal_quarter_string() {
- DateLiterals dateLiteral = new DateLiterals().THIS_FISCAL_QUARTER;
- System.assertEquals('THIS_FISCAL_QUARTER', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_quarter_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_QUARTER;
- System.assertEquals('NEXT_QUARTER', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_fiscal_quarter_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_FISCAL_QUARTER;
- System.assertEquals('NEXT_FISCAL_QUARTER', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_this_year_string() {
- DateLiterals dateLiteral = new DateLiterals().THIS_YEAR;
- System.assertEquals('THIS_YEAR', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_this_fiscal_year_string() {
- DateLiterals dateLiteral = new DateLiterals().THIS_FISCAL_YEAR;
- System.assertEquals('THIS_FISCAL_YEAR', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_year_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_YEAR;
- System.assertEquals('LAST_YEAR', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_fiscal_year_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_FISCAL_YEAR;
- System.assertEquals('LAST_FISCAL_YEAR', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_year_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_YEAR;
- System.assertEquals('NEXT_YEAR', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_fiscal_year_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_FISCAL_YEAR;
- System.assertEquals('NEXT_FISCAL_YEAR', dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_n_days_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_N_DAYS(n_number);
- System.assertEquals('LAST_N_DAYS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_n_weeks_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_N_WEEKS(n_number);
- System.assertEquals('LAST_N_WEEKS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_n_months_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_N_MONTHS(n_number);
- System.assertEquals('LAST_N_MONTHS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_n_quarters_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_N_QUARTERS(n_number);
- System.assertEquals('LAST_N_QUARTERS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_n_years_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_N_YEARS(n_number);
- System.assertEquals('LAST_N_YEARS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_last_n_fiscal_years_string() {
- DateLiterals dateLiteral = new DateLiterals().LAST_N_FISCAL_YEARS(n_number);
- System.assertEquals('LAST_N_FISCAL_YEARS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_n_days_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_N_DAYS(n_number);
- System.assertEquals('NEXT_N_DAYS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_n_weeks_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_N_WEEKS(n_number);
- System.assertEquals('NEXT_N_WEEKS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_n_months_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_N_MONTHS(n_number);
- System.assertEquals('NEXT_N_MONTHS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_n_quarters_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_N_QUARTERS(n_number);
- System.assertEquals('NEXT_N_QUARTERS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_n_fiscal_quarters_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_N_FISCAL_QUARTERS(n_number);
- System.assertEquals('NEXT_N_FISCAL_QUARTERS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_n_years_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_N_YEARS(n_number);
- System.assertEquals('NEXT_N_YEARS: ' + n_number, dateLiteral.value);
- }
-
- @isTest
- static void it_should_return_next_n_fiscal_years_string() {
- DateLiterals dateLiteral = new DateLiterals().NEXT_N_FISCAL_YEARS(n_number);
- System.assertEquals('NEXT_N_FISCAL_YEARS: ' + n_number, dateLiteral.value);
- }
-}
\ No newline at end of file
diff --git a/src/classes/IQueryArgumentFormatter.cls b/src/classes/IQueryArgumentFormatter.cls
new file mode 100644
index 00000000..be402c78
--- /dev/null
+++ b/src/classes/IQueryArgumentFormatter.cls
@@ -0,0 +1,17 @@
+/*************************************************************************************************
+* 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 TODO
+*
+*/
+public interface IQueryArgumentFormatter {
+
+ String getValue();
+
+}
\ No newline at end of file
diff --git a/src/classes/ISObjectRepository.cls-meta.xml b/src/classes/IQueryArgumentFormatter.cls-meta.xml
similarity index 80%
rename from src/classes/ISObjectRepository.cls-meta.xml
rename to src/classes/IQueryArgumentFormatter.cls-meta.xml
index cbddff8c..94f6f064 100644
--- a/src/classes/ISObjectRepository.cls-meta.xml
+++ b/src/classes/IQueryArgumentFormatter.cls-meta.xml
@@ -1,5 +1,5 @@
- 38.0
+ 40.0
Active
diff --git a/src/classes/IQueryField.cls b/src/classes/IQueryField.cls
new file mode 100644
index 00000000..71ed982c
--- /dev/null
+++ b/src/classes/IQueryField.cls
@@ -0,0 +1,17 @@
+/*************************************************************************************************
+* 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 TODO
+*
+*/
+public interface IQueryField {
+
+ String getValue();
+
+}
\ No newline at end of file
diff --git a/src/classes/DateLiterals.cls-meta.xml b/src/classes/IQueryField.cls-meta.xml
similarity index 80%
rename from src/classes/DateLiterals.cls-meta.xml
rename to src/classes/IQueryField.cls-meta.xml
index cbddff8c..94f6f064 100644
--- a/src/classes/DateLiterals.cls-meta.xml
+++ b/src/classes/IQueryField.cls-meta.xml
@@ -1,5 +1,5 @@
- 38.0
+ 40.0
Active
diff --git a/src/classes/IQueryFilter.cls b/src/classes/IQueryFilter.cls
new file mode 100644
index 00000000..7df35808
--- /dev/null
+++ b/src/classes/IQueryFilter.cls
@@ -0,0 +1,25 @@
+/*************************************************************************************************
+* 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
+*
+*/
+public interface IQueryFilter {
+
+ // Setter methods
+ IQueryFilter filterByField(QueryField queryField, QueryOperator operator, Object providedValue);
+ IQueryFilter filterByQueryDate(QueryDate queryDateToFilter, QueryOperator operator, Integer providedValue);
+ IQueryFilter filterBySubquery(QueryOperator inOrNotIn, Schema.SObjectField lookupFieldOnRelatedSObject);
+ IQueryFilter filterBySubquery(Schema.SObjectField lookupField, QueryOperator inOrNotIn, Schema.SObjectField lookupFieldOnRelatedSObject);
+
+ IQueryFilter andFilterBy(List queryFilters);
+ IQueryFilter orFilterBy(List queryFilters);
+
+ // Getter methods
+ String getValue();
+
+}
\ No newline at end of file
diff --git a/src/classes/IQueryFilter.cls-meta.xml b/src/classes/IQueryFilter.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/IQueryFilter.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/ISObjectQueryBuilder.cls b/src/classes/ISObjectQueryBuilder.cls
new file mode 100644
index 00000000..d6a86e5a
--- /dev/null
+++ b/src/classes/ISObjectQueryBuilder.cls
@@ -0,0 +1,56 @@
+/*************************************************************************************************
+* 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
+*
+*/
+public interface ISObjectQueryBuilder {
+
+ ISObjectQueryBuilder cacheResults();
+
+ // Field methods
+ ISObjectQueryBuilder addAllFields();
+ ISObjectQueryBuilder addAllStandardFields();
+ ISObjectQueryBuilder addAllCustomFields();
+ ISObjectQueryBuilder addAllReadableFields();
+ ISObjectQueryBuilder addAllEditableFields();
+ ISObjectQueryBuilder addFields(List queryFields);
+ ISObjectQueryBuilder addFields(Schema.FieldSet fieldSet);
+ ISObjectQueryBuilder excludeFields(List queryFields);
+ ISObjectQueryBuilder excludeFields(Schema.FieldSet fieldSet);
+
+ // Parent-to-child relationship query methods
+ ISObjectQueryBuilder includeChildrenRecords(Schema.SObjectField childToParentRelationshipField, ISObjectQueryBuilder sobjectQueryBuilder);
+
+ // Filter methods
+ ISObjectQueryBuilder filterBy(IQueryFilter queryFilter);
+ ISObjectQueryBuilder filterBy(List queryFilters);
+
+ // Order By methods
+ ISObjectQueryBuilder orderBy(IQueryField orderByQueryField);
+ ISObjectQueryBuilder orderBy(IQueryField orderByQueryField, QuerySortOrder sortOrder);
+ ISObjectQueryBuilder orderBy(IQueryField orderByQueryField, QuerySortOrder sortOrder, QueryNullSortOrder nullsSortOrder);
+
+ // Additional query option methods
+ ISObjectQueryBuilder limitCount(Integer limitCount);
+ ISObjectQueryBuilder offset(Integer numberOfRowsToSkip);
+ ISObjectQueryBuilder forReference();
+ ISObjectQueryBuilder forUpdate();
+ ISObjectQueryBuilder forView();
+ ISObjectQueryBuilder usingScope(QueryFilterScope filterScope);
+
+ // Query string methods
+ Database.QueryLocator getQueryLocator();
+ String getQuery();
+ String getSearchQuery();
+ String getChildQuery(Schema.SObjectField childToParentRelationshipField);
+
+ // Query execution methods
+ SObject getFirstQueryResult();
+ List getQueryResults();
+
+}
\ No newline at end of file
diff --git a/src/classes/ISObjectQueryBuilder.cls-meta.xml b/src/classes/ISObjectQueryBuilder.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/ISObjectQueryBuilder.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/ISObjectRepository.cls b/src/classes/ISObjectRepository.cls
deleted file mode 100644
index 50b85bfc..00000000
--- a/src/classes/ISObjectRepository.cls
+++ /dev/null
@@ -1,8 +0,0 @@
-public interface ISObjectRepository {
- // SOQL
- SObject getById(Id recordId);
- List getById(List recordIdList);
- List getByFieldAndTypeForGivenTimePeriod(Schema.SObjectField field, String operator, Object value);
- // SOSL
- List searchInAllFields(String searchTerm);
-}
\ No newline at end of file
diff --git a/src/classes/ISearchQueryBuilder.cls b/src/classes/ISearchQueryBuilder.cls
new file mode 100644
index 00000000..61369836
--- /dev/null
+++ b/src/classes/ISearchQueryBuilder.cls
@@ -0,0 +1,14 @@
+public interface ISearchQueryBuilder {
+
+ ISearchQueryBuilder cacheResults();
+
+ ISearchQueryBuilder inQuerySearchGroup(QuerySearchGroup searchGroup);
+ ISearchQueryBuilder withHighlight(Boolean withHighlight);
+ ISearchQueryBuilder withSpellCorrection(Boolean withSpellCorrection);
+
+ String getQuery();
+
+ List getFirstSearchResult();
+ List> getSearchResults();
+
+}
\ No newline at end of file
diff --git a/src/classes/ISearchQueryBuilder.cls-meta.xml b/src/classes/ISearchQueryBuilder.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/ISearchQueryBuilder.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/LeadRepository.cls b/src/classes/LeadRepository.cls
deleted file mode 100644
index d68aced1..00000000
--- a/src/classes/LeadRepository.cls
+++ /dev/null
@@ -1,69 +0,0 @@
-public without sharing class LeadRepository extends SObjectRepository {
-
- private static final Schema.FieldSet DEFAULT_FIELD_SET = SObjectType.Lead.FieldSets.MyFieldSet;
-
- public LeadRepository() {
- super(LeadRepository.DEFAULT_FIELD_SET);
- // Any conditions added in the constructor will apply to all queries in this class
- this.whereIsConverted(false);
- }
-
- // Overload the constructor if you want to allow other code to specify the field set used
- public LeadRepository(Schema.FieldSet fieldSet, Boolean addCommonQueryFields) {
- super(fieldSet, addCommonQueryFields);
- }
-
- // ISObjectRepository requires at least 2 methods, getRecord & getList
- public Lead getRecord(Id leadId) {
- return (Lead)this
- .whereIdEquals(leadId)
- .setAsUpdate()
- .getFirstQueryResult();
- }
-
- public List getList(List leadIdList) {
- return (List)this
- .whereIdIn(leadIdList)
- .setAsUpdate()
- .getQueryResults();
- }
-
- // Add public methods needed that return the query results
- // Only methods that return an SObject or collection of SObjects should be made public
- public List getListForSources(List leadSourceList) {
- return (List)this
- .whereFieldIn(Schema.Lead.LeadSource, leadSourceList)
- .orderBy(Schema.Lead.CreatedDate)
- .getQueryResults();
- }
-
- public List getListForStatus(String status, Integer limitCount) {
- return (List)this
- .whereIsConverted(false)
- .whereStatusEquals(status)
- .limitCount(limitCount)
- .orderBy(Schema.Lead.LastModifiedDate, SObjectRepository.SortOrder.DESCENDING)
- .setAsUpdate()
- .getQueryResults();
- }
-
- public List searchInAllFields(String searchTerm) {
- return (List)this
- .whereIsConverted(false)
- .orderBy(Schema.Lead.CreatedDate, SObjectRepository.SortOrder.DESCENDING)
- .limitCount(10)
- .setAsUpdate() // SOSL cannot use FOR UPDATE. This will execute, but a warning debug statement will indicate that it is ignored
- .getSearchResults(searchTerm, SObjectRepository.SearchGroup.ALL_FIELDS);
- }
-
- // You can add additional builder methods for any commonly used filters for this SObject
- // All builder methods should be kept as private or protected
- private LeadRepository whereIsConverted(Boolean bool) {
- return (LeadRepository)this.whereFieldEquals(Schema.Lead.IsConverted, bool);
- }
-
- private LeadRepository whereStatusEquals(String status) {
- return (LeadRepository)this.whereFieldEquals(Schema.Lead.Status, status);
- }
-
-}
\ No newline at end of file
diff --git a/src/classes/LeadRepository.cls-meta.xml b/src/classes/LeadRepository.cls-meta.xml
deleted file mode 100644
index cbddff8c..00000000
--- a/src/classes/LeadRepository.cls-meta.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- 38.0
- Active
-
diff --git a/src/classes/LeadRepository_Tests.cls b/src/classes/LeadRepository_Tests.cls
deleted file mode 100644
index 293a349f..00000000
--- a/src/classes/LeadRepository_Tests.cls
+++ /dev/null
@@ -1,101 +0,0 @@
-@isTest
-private class LeadRepository_Tests {
-
- @testSetup
- static void setup() {
- List leadList = new List();
- for(Integer i = 0; i < 5; i++) {
- Lead lead = new Lead(
- Company = 'My Test Company',
- LastName = 'Gillespie'
- );
- leadList.add(lead);
- }
- insert leadList;
- }
-
- @isTest
- static void getRecord() {
- Lead expectedLead = [SELECT Id FROM Lead LIMIT 1];
-
- Test.startTest();
-
- Lead returnedLead = new LeadRepository().getRecord(expectedLead.Id);
- System.assertEquals(expectedLead.Id, returnedLead.Id);
-
- Test.stopTest();
- }
-
- @isTest
- static void getRecord_WHEN_fieldSetIsSpecified() {
- Schema.FieldSet expectedFieldSet = SObjectType.Lead.FieldSets.AnotherFieldSet;
- Lead expectedLead = [SELECT Id FROM Lead LIMIT 1];
-
- Test.startTest();
-
- Lead returnedLead = new LeadRepository(expectedFieldSet, false).getRecord(expectedLead.Id);
- System.assertEquals(expectedLead.Id, returnedLead.Id);
-
- Test.stopTest();
- }
-
- @isTest
- static void getList() {
- List expectedLeadList = [SELECT Id FROM Lead];
- List expectedLeadIdList = new List(new Map(expectedLeadList).keySet());
-
- Test.startTest();
-
- List returnedLeadList = new LeadRepository().getList(expectedLeadIdList);
- System.assertEquals(expectedLeadList.size(), returnedLeadList.size());
-
- Test.stopTest();
- }
-
- @isTest
- static void getListForSources() {
- String expectedLeadSource = 'GitHub';
-
- List leadList = [SELECT Id, LeadSource FROM Lead LIMIT 2];
- for(Lead lead : leadList) lead.LeadSource = expectedLeadSource;
- update leadList;
-
- Integer leadCountForExpectedLeadSource = [SELECT COUNT() FROM Lead WHERE LeadSource = :expectedLeadSource];
-
- Test.startTest();
-
- List returnedLeadList = new LeadRepository().getListForSources(new List{expectedLeadSource});
- System.assertEquals(leadCountForExpectedLeadSource, returnedLeadList.size());
-
- Test.stopTest();
- }
-
- @isTest
- static void getListForStatus() {
- String expectedStatus = [SELECT Status FROM Lead LIMIT 1].Status;
- Integer leadCountForExpectedStatus = [SELECT COUNT() FROM Lead WHERE Status = :expectedStatus];
- System.assert(leadCountForExpectedStatus > 0);
- Integer limitCount = leadCountForExpectedStatus - 1;
-
- Test.startTest();
-
- List returnedLeadList = new LeadRepository().getListForStatus(expectedStatus, limitCount);
- System.assertEquals(limitCount, returnedLeadList.size());
-
- Test.stopTest();
- }
-
- @isTest
- static void searchInAllFields() {
- String searchTerm = [SELECT LastName FROM Lead WHERE LastName != null LIMIT 1].LastName;
- List expectedLeadList = (List)[FIND :searchTerm IN ALL FIELDS RETURNING Lead(Id WHERE IsConverted = false)][0];
-
- Test.startTest();
-
- List returnedLeadList = new LeadRepository().searchInAllFields(searchTerm);
- System.assertEquals(expectedLeadList.size(), returnedLeadList.size());
-
- Test.stopTest();
- }
-
-}
\ No newline at end of file
diff --git a/src/classes/LeadRepository_Tests.cls-meta.xml b/src/classes/LeadRepository_Tests.cls-meta.xml
deleted file mode 100644
index cbddff8c..00000000
--- a/src/classes/LeadRepository_Tests.cls-meta.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- 38.0
- Active
-
diff --git a/src/classes/QueryArgumentFormatter.cls b/src/classes/QueryArgumentFormatter.cls
new file mode 100644
index 00000000..41958f54
--- /dev/null
+++ b/src/classes/QueryArgumentFormatter.cls
@@ -0,0 +1,90 @@
+/*************************************************************************************************
+* 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 TODO
+*
+*/
+public virtual class QueryArgumentFormatter implements IQueryArgumentFormatter {
+
+ private String value;
+
+ public QueryArgumentFormatter(Object valueToFormat) {
+ this.value = this.objectToQueryString(valueToFormat);
+ }
+
+ public virtual String getValue() {
+ return this.value;
+ }
+
+ protected virtual String objectToQueryString(Object valueToFormat) {
+ if(valueToFormat == null) return null;
+ else if(CollectionUtils.isList(valueToFormat)) return this.listToQueryString((List)valueToFormat);
+ else if(CollectionUtils.isSet(valueToFormat)) return this.setToQueryString(valueToFormat);
+ else if(CollectionUtils.isMap(valueToFormat)) return this.mapToQueryString(valueToFormat);
+ else if(valueToFormat instanceof QueryDateLiteral) {
+ QueryDateLiteral dateLiteral = (QueryDateLiteral)valueToFormat;
+ return dateLiteral.getValue();
+ }
+ else if(valueToFormat instanceof Boolean) return String.valueOf((Boolean)valueToFormat);
+ else if(valueToFormat instanceof Date) return String.valueOf((Date)valueToFormat);
+ else if(valueToFormat instanceof Datetime) {
+ Datetime datetimeValue = (Datetime)valueToFormat;
+ return datetimeValue.format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'', 'Greenwich Mean Time');
+ }
+ else if(valueToFormat instanceof Decimal) return String.valueOf((Decimal)valueToFormat);
+ else if(valueToFormat instanceof Double) return String.valueOf((Double)valueToFormat);
+ else if(valueToFormat instanceof Integer) return String.valueOf((Integer)valueToFormat);
+ else if(valueToFormat instanceof Long) return String.valueOf((Long)valueToFormat);
+ else if(valueToFormat instanceof SObject) {
+ SObject record = (SObject)valueToFormat;
+ return wrapInSingleQuotes(record.Id);
+ }
+ else if(valueToFormat instanceof Schema.SObjectType) {
+ Schema.SObjectType sobjectType = (Schema.SObjectType)valueToFormat;
+ return wrapInSingleQuotes(sobjectType.getDescribe().getName());
+ }
+ else if(valueToFormat instanceof String) {
+ // Escape single quotes to prevent SOQL/SOSL injection
+ String stringArgument = String.escapeSingleQuotes((String)valueToFormat);
+ return wrapInSingleQuotes(stringArgument);
+ }
+ else return String.valueOf(valueToFormat);
+ }
+
+ private String listToQueryString(List valueList) {
+ List parsedValueList = new List();
+ for(Object value : valueList) parsedValueList.add(this.objectToQueryString(value));
+ return '(' + String.join(parsedValueList, ', ') + ')';
+ }
+
+ private String setToQueryString(Object valueSet) {
+ String unformattedString = String.valueOf(valueSet).replace('{', '').replace('}', '');
+ List parsedValueList = new List();
+ for(String collectionItem : unformattedString.split(',')) {
+ parsedValueList.add(this.objectToQueryString(collectionItem));
+ }
+
+ return '(' + String.join(parsedValueList, ', ') + ')';
+ }
+
+ private String mapToQueryString(Object valueMap) {
+ Map m = (Map)JSON.deserializeUntyped(JSON.serialize(valueMap));
+
+ return this.setToQueryString(m.keySet());
+ }
+
+ private String wrapInSingleQuotes(String input) {
+ input = input.trim();
+
+ if(input.left(1) != '\'') input = '\'' + input;
+ if(input.right(1) != '\'') input = input + '\'';
+ return input;
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryArgumentFormatter.cls-meta.xml b/src/classes/QueryArgumentFormatter.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryArgumentFormatter.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryArgumentFormatter_Tests.cls b/src/classes/QueryArgumentFormatter_Tests.cls
new file mode 100644
index 00000000..949d3abb
--- /dev/null
+++ b/src/classes/QueryArgumentFormatter_Tests.cls
@@ -0,0 +1,202 @@
+/*************************************************************************************************
+* 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 QueryArgumentFormatter_Tests {
+
+ @isTest
+ static void it_should_return_query_string_for_null() {
+ Object providedValue = null;
+ String expectedString = null;
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_list() {
+ List providedValueList = new List{1, 2, 3};
+ String expectedString = '(1, 2, 3)';
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValueList).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_set() {
+ Set providedValueSet = new Set{'A', 'B', 'C'};
+ String expectedString = '(\'A\', \'B\', \'C\')';
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValueSet).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_map() {
+ Map providedValueMap = new Map([SELECT Id FROM User LIMIT 10]);
+ List sortedIdList = new List(providedValueMap.keySet());
+ sortedIdList.sort();
+ String expectedString = '(\'' + String.join(sortedIdList, '\', \'') + '\')';
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValueMap).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_query_date_literal() {
+ QueryDateLiteral providedValue = QueryDateLiteral.YESTERDAY;
+ String expectedString = providedValue.getValue();
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_boolean() {
+ Boolean providedValue = true;
+ String expectedString = String.valueOf(providedValue);
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_date() {
+ Date providedValue = System.today();
+ String expectedString = String.valueOf(providedValue);
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_datetime() {
+ Datetime providedValue = System.now();
+ String expectedString = providedValue.format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'', 'Greenwich Mean Time');
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_decimal() {
+ Decimal providedValue = 1.1;
+ String expectedString = String.valueOf(providedValue);
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_double() {
+ Double providedValue = 1.1;
+ String expectedString = String.valueOf(providedValue);
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_integer() {
+ Integer providedValue = 10;
+ String expectedString = String.valueOf(providedValue);
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_long() {
+ Long providedValue = 1234567890;
+ String expectedString = String.valueOf(providedValue);
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_sobject() {
+ User providedValue = [SELECT Id FROM User LIMIT 1];
+ String expectedString = '\'' + providedValue.Id + '\'';
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_sobject_type() {
+ Schema.SObjectType providedValue = Schema.Lead.SObjectType;
+ String expectedString = '\'' + providedValue.getDescribe().getName() + '\'';
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_string() {
+ String providedValue = 'test';
+ String expectedString = '\'' + providedValue + '\'';
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+ @isTest
+ static void it_should_return_query_string_for_string_containing_single_quotes() {
+ String providedValue = 'Jon\'s test';
+ String expectedString = '\'' + String.escapeSingleQuotes(providedValue) + '\'';
+
+ Test.startTest();
+ String returnedValue = new QueryArgumentFormatter(providedValue).getValue();
+ Test.stopTest();
+
+ System.assertEquals(expectedString, returnedValue);
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryArgumentFormatter_Tests.cls-meta.xml b/src/classes/QueryArgumentFormatter_Tests.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryArgumentFormatter_Tests.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryBuilder.cls b/src/classes/QueryBuilder.cls
new file mode 100644
index 00000000..85356b16
--- /dev/null
+++ b/src/classes/QueryBuilder.cls
@@ -0,0 +1,146 @@
+/*************************************************************************************************
+* 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 Abstract class that provides some shared properties & methods for SObjectQueryBuilder & AggregateResultQueryBuilder
+*
+*/
+public abstract class QueryBuilder {
+
+ private static Map> cachedQueryResultsByHashCode = new Map>();
+ private static Map>> cachedSearchResultsByHashCode = new Map>>();
+
+ protected List whereClauseList;
+ protected List orderByList;
+ protected Integer limitCount;
+
+ protected SObjectType sobjectType;
+ protected Map sobjectTypeFieldMap;
+
+ private Boolean cacheResults;
+
+ public QueryBuilder() {
+ this.whereClauseList = new List();
+ this.orderByList = new List();
+ this.cacheResults = false;
+ }
+
+ protected void doCacheResults() {
+ this.cacheResults = true;
+ }
+
+ protected void doFilterBy(IQueryFilter queryFilter) {
+ this.doFilterBy(new List{queryFilter});
+ }
+
+ protected void doFilterBy(List queryFilters) {
+ if(queryFilters == null) return;
+
+ for(IQueryFilter queryFilter : queryFilters) this.whereClauseList.add(queryFilter.getValue());
+ }
+
+ protected void doOrderBy(IQueryField orderByQueryField) {
+ this.doOrderBy(orderByQueryField, null, null);
+ }
+
+ protected void doOrderBy(IQueryField orderByQueryField, QuerySortOrder sortOrder) {
+ this.doOrderBy(orderByQueryField, sortOrder, null);
+ }
+
+ protected void doOrderBy(IQueryField orderByQueryField, QuerySortOrder sortOrder, QueryNullSortOrder nullsSortOrder) {
+ String sortOrderString = '';
+ if(sortOrder == QuerySortOrder.ASCENDING) sortOrderString = ' ASC';
+ else if(sortOrder == QuerySortOrder.DESCENDING) sortOrderString = ' DESC';
+
+ if(nullsSortOrder != null) sortOrderString += ' NULLS ' + nullsSortOrder;
+
+ this.orderByList.add(orderByQueryField.getValue() + sortOrderString);
+ }
+
+ protected void doLimitCount(Integer limitCount) {
+ this.limitCount = limitCount;
+ }
+
+ protected String doGetWhereClauseString() {
+ if(this.whereClauseList.isEmpty()) return '';
+
+ // Dedupe
+ this.whereClauseList = new List(new Set(this.whereClauseList));
+
+ this.whereClauseList.sort();
+ return '\nWHERE ' + String.join(this.whereClauseList, '\nAND ');
+ }
+
+ protected String doGetOrderByString() {
+ if(this.orderByList.isEmpty()) return '';
+
+ // Dedupe
+ this.orderByList = new List(new Set(this.orderByList));
+
+ this.orderByList.sort();
+ return '\nORDER BY ' + String.join(new List(orderByList), ', ');
+ }
+
+ protected String doGetLimitCountString() {
+ return this.limitCount != null ? '\nLIMIT '+ this.limitCount : '';
+ }
+
+ protected List doGetQueryResults(String query) {
+ if(this.cacheResults) return this.getCachedQuery(query);
+ else return this.executeQuery(query);
+ }
+
+ protected List> doGetSearchResults(String query) {
+ if(this.cacheResults) return this.getCachedSearch(query);
+ else return this.executeSearch(query);
+ }
+
+ private void filterByWithSeparator(List queryFilters, String separator) {
+ if(queryFilters == null) return;
+
+ List queryFilterValues = new List();
+ for(IQueryFilter queryFilter : queryFilters) queryFilterValues.add(queryFilter.getValue());
+
+ String orStatement = '(' + String.join(queryFilterValues, ' ' + separator + ' ') + ')';
+ this.whereClauseList.add(orStatement);
+ }
+
+ private List getCachedQuery(String query) {
+ Integer hashCode = query.hashCode();
+
+ Boolean isCached = cachedQueryResultsByHashCode.containsKey(hashCode);
+ if(!isCached) cachedQueryResultsByHashCode.put(hashCode, this.executeQuery(query));
+
+ // Always return a deep clone so the original cached version is never modified
+ return cachedQueryResultsByHashCode.get(hashCode).deepClone(true, true, true);
+ }
+
+ private List executeQuery(String query) {
+ List results = Database.query(query);
+ return results;
+ }
+
+ private List> getCachedSearch(String query) {
+ Integer hashCode = query.hashCode();
+
+ Boolean isCached = cachedSearchResultsByHashCode.containsKey(hashCode);
+ if(!isCached) cachedSearchResultsByHashCode.put(hashCode, this.executeSearch(query));
+
+ // Always return a deep clone so the original cached version is never modified
+ List> cachedResults = cachedSearchResultsByHashCode.get(hashCode);
+ List> deepClonedResults = new List>();
+ for(List cachedListOfResults : cachedResults) deepClonedResults.add(cachedListOfResults.deepClone(true, true, true));
+ return deepClonedResults;
+ }
+
+ private List> executeSearch(String query) {
+ List> results = Search.query(query);
+ return results;
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryBuilder.cls-meta.xml b/src/classes/QueryBuilder.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryBuilder.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryDate.cls b/src/classes/QueryDate.cls
new file mode 100644
index 00000000..94b5c2c8
--- /dev/null
+++ b/src/classes/QueryDate.cls
@@ -0,0 +1,93 @@
+/*************************************************************************************************
+* 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 Used to dynamically generate SOQL & SOSQL date functions.
+* "Date functions in SOQL queries allow you to group or filter data by date periods such as day, calendar month, or fiscal year."
+* Salesforce docs: developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_date_functions.htm
+*
+*/
+public without sharing class QueryDate {
+
+ private Schema.SObjectField sobjectField;
+ private Schema.SObjectType sobjectType;
+ private String value;
+
+ public String getValue() {
+ return this.value;
+ }
+
+ public Schema.SObjectType getSObjectType() {
+ return this.sobjectType;
+ }
+
+ private QueryDate setValue(Schema.SObjectField sobjectField, String value) {
+ this.sobjectField = sobjectField;
+ this.sobjectType = new SObjectFieldDescriber(sobjectField).getSObjectType();
+ this.value = value;
+ return this;
+ }
+
+ public static QueryDate CALENDAR_MONTH(Schema.SObjectField sobjectField) {
+ return buildQueryDate('CALENDAR_MONTH', sobjectField);
+ }
+
+ public static QueryDate CALENDAR_QUARTER(Schema.SObjectField sobjectField) {
+ return buildQueryDate('CALENDAR_QUARTER', sobjectField);
+ }
+
+ public static QueryDate CALENDAR_YEAR(Schema.SObjectField sobjectField) {
+ return buildQueryDate('CALENDAR_YEAR', sobjectField);
+ }
+
+ public static QueryDate DAY_IN_MONTH(Schema.SObjectField sobjectField) {
+ return buildQueryDate('DAY_IN_MONTH', sobjectField);
+ }
+
+ public static QueryDate DAY_IN_WEEK(Schema.SObjectField sobjectField) {
+ return buildQueryDate('DAY_IN_WEEK', sobjectField);
+ }
+
+ public static QueryDate DAY_IN_YEAR(Schema.SObjectField sobjectField) {
+ return buildQueryDate('DAY_IN_YEAR', sobjectField);
+ }
+
+ public static QueryDate DAY_ONLY(Schema.SObjectField sobjectField) {
+ return buildQueryDate('DAY_ONLY', sobjectField);
+ }
+
+ public static QueryDate FISCAL_MONTH(Schema.SObjectField sobjectField) {
+ return buildQueryDate('FISCAL_MONTH', sobjectField);
+ }
+
+ public static QueryDate FISCAL_QUARTER(Schema.SObjectField sobjectField) {
+ return buildQueryDate('FISCAL_QUARTER', sobjectField);
+ }
+
+ public static QueryDate FISCAL_YEAR(Schema.SObjectField sobjectField) {
+ return buildQueryDate('FISCAL_YEAR', sobjectField);
+ }
+
+ public static QueryDate HOUR_IN_DAY(Schema.SObjectField sobjectField) {
+ return buildQueryDate('HOUR_IN_DAY', sobjectField);
+ }
+
+ public static QueryDate WEEK_IN_MONTH(Schema.SObjectField sobjectField) {
+ return buildQueryDate('WEEK_IN_MONTH', sobjectField);
+ }
+
+ public static QueryDate WEEK_IN_YEAR(Schema.SObjectField sobjectField) {
+ return buildQueryDate('WEEK_IN_YEAR', sobjectField);
+ }
+
+ private static QueryDate buildQueryDate(String dateOperation, Schema.SObjectField sobjectField) {
+ String value = dateOperation + '(' + sobjectField.getDescribe().getName() + ')';
+ return new QueryDate().setValue(sobjectField, value);
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryDate.cls-meta.xml b/src/classes/QueryDate.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryDate.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryDateLiteral.cls b/src/classes/QueryDateLiteral.cls
new file mode 100644
index 00000000..1eae5e38
--- /dev/null
+++ b/src/classes/QueryDateLiteral.cls
@@ -0,0 +1,123 @@
+/*************************************************************************************************
+* 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
+*
+*/
+public without sharing class QueryDateLiteral {
+
+ private String value;
+
+ public String getValue() {
+ return this.value;
+ }
+
+ private QueryDateLiteral(String value) {
+ this.value = value;
+ }
+
+ // Actual constant literals
+ public static final QueryDateLiteral YESTERDAY = new QueryDateLiteral('YESTERDAY');
+ public static final QueryDateLiteral TODAY = new QueryDateLiteral('TODAY');
+ public static final QueryDateLiteral TOMORROW = new QueryDateLiteral('TOMORROW');
+ public static final QueryDateLiteral LAST_WEEK = new QueryDateLiteral('LAST_WEEK');
+ public static final QueryDateLiteral THIS_WEEK = new QueryDateLiteral('THIS_WEEK');
+ public static final QueryDateLiteral NEXT_WEEK = new QueryDateLiteral('NEXT_WEEK');
+ public static final QueryDateLiteral LAST_MONTH = new QueryDateLiteral('LAST_MONTH');
+ public static final QueryDateLiteral THIS_MONTH = new QueryDateLiteral('THIS_MONTH');
+ public static final QueryDateLiteral NEXT_MONTH = new QueryDateLiteral('NEXT_MONTH');
+ public static final QueryDateLiteral LAST_90_DAYS = new QueryDateLiteral('LAST_90_DAYS');
+ public static final QueryDateLiteral NEXT_90_DAYS = new QueryDateLiteral('NEXT_90_DAYS');
+ public static final QueryDateLiteral LAST_QUARTER = new QueryDateLiteral('LAST_QUARTER');
+ public static final QueryDateLiteral THIS_QUARTER = new QueryDateLiteral('THIS_QUARTER');
+ public static final QueryDateLiteral NEXT_QUARTER = new QueryDateLiteral('NEXT_QUARTER');
+ public static final QueryDateLiteral LAST_FISCAL_QUARTER = new QueryDateLiteral('LAST_FISCAL_QUARTER');
+ public static final QueryDateLiteral THIS_FISCAL_QUARTER = new QueryDateLiteral('THIS_FISCAL_QUARTER');
+ public static final QueryDateLiteral NEXT_FISCAL_QUARTER = new QueryDateLiteral('NEXT_FISCAL_QUARTER');
+ public static final QueryDateLiteral LAST_YEAR = new QueryDateLiteral('LAST_YEAR');
+ public static final QueryDateLiteral THIS_YEAR = new QueryDateLiteral('THIS_YEAR');
+ public static final QueryDateLiteral NEXT_YEAR = new QueryDateLiteral('NEXT_YEAR');
+ public static final QueryDateLiteral LAST_FISCAL_YEAR = new QueryDateLiteral('LAST_FISCAL_YEAR');
+ public static final QueryDateLiteral THIS_FISCAL_YEAR = new QueryDateLiteral('THIS_FISCAL_YEAR');
+ public static final QueryDateLiteral NEXT_FISCAL_YEAR = new QueryDateLiteral('NEXT_FISCAL_YEAR');
+
+ private static final String LAST_N = 'LAST_N_{0}:{1}';
+ private static final String NEXT_N = 'NEXT_N_{0}:{1}';
+ private static final String N_DAYS_AGO = 'N_DAYS_AGO:{0}';
+
+ private static final String DAYS = 'DAYS';
+ private static final String WEEKS = 'WEEKS';
+ private static final String MONTHS = 'MONTHS';
+ private static final String QUARTERS = 'QUARTERS';
+ private static final String YEARS = 'YEARS';
+ private static final String FISCAL_QUARTERS = 'FISCAL_QUARTERS';
+ private static final String FISCAL_YEARS = 'FISCAL_YEARS';
+
+ // Buildable literals
+ public static QueryDateLiteral N_DAYS_AGO(Integer num) {
+ String parsedValue = String.format(N_DAYS_AGO, new List{String.valueOf(num)});
+ return new QueryDateLiteral(parsedValue);
+ }
+
+ public static QueryDateLiteral LAST_N_DAYS(Integer num) {
+ return buildQueryDateLiteral(LAST_N, DAYS, num);
+ }
+
+ public static QueryDateLiteral LAST_N_WEEKS(Integer num) {
+ return buildQueryDateLiteral(LAST_N, WEEKS, num);
+ }
+
+ public static QueryDateLiteral LAST_N_MONTHS(Integer num) {
+ return buildQueryDateLiteral(LAST_N, MONTHS, num);
+ }
+
+ public static QueryDateLiteral LAST_N_QUARTERS(Integer num) {
+ return buildQueryDateLiteral(LAST_N, QUARTERS, num);
+ }
+
+ public static QueryDateLiteral LAST_N_YEARS(Integer num) {
+ return buildQueryDateLiteral(LAST_N, YEARS, num);
+ }
+
+ public static QueryDateLiteral LAST_N_FISCAL_YEARS(Integer num) {
+ return buildQueryDateLiteral(LAST_N, FISCAL_YEARS, num);
+ }
+
+ public static QueryDateLiteral NEXT_N_DAYS(Integer num) {
+ return buildQueryDateLiteral(NEXT_N, DAYS, num);
+ }
+
+ public static QueryDateLiteral NEXT_N_WEEKS(Integer num) {
+ return buildQueryDateLiteral(NEXT_N, WEEKS, num);
+ }
+
+ public static QueryDateLiteral NEXT_N_MONTHS(Integer num) {
+ return buildQueryDateLiteral(NEXT_N, MONTHS, num);
+ }
+
+ public static QueryDateLiteral NEXT_N_QUARTERS(Integer num) {
+ return buildQueryDateLiteral(NEXT_N, QUARTERS, num);
+ }
+
+ public static QueryDateLiteral NEXT_N_FISCAL_QUARTERS(Integer num) {
+ return buildQueryDateLiteral(NEXT_N, FISCAL_QUARTERS, num);
+ }
+
+ public static QueryDateLiteral NEXT_N_YEARS(Integer num) {
+ return buildQueryDateLiteral(NEXT_N, YEARS, num);
+ }
+
+ public static QueryDateLiteral NEXT_N_FISCAL_YEARS(Integer num) {
+ return buildQueryDateLiteral(NEXT_N, FISCAL_YEARS, num);
+ }
+
+ private static QueryDateLiteral buildQueryDateLiteral(String base, String period, Integer num) {
+ String parsedValue = String.format(base, new List{period, String.valueOf(num)});
+ return new QueryDateLiteral(parsedValue);
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryDateLiteral.cls-meta.xml b/src/classes/QueryDateLiteral.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryDateLiteral.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryDateLiteral_Tests.cls b/src/classes/QueryDateLiteral_Tests.cls
new file mode 100644
index 00000000..e06ea6c1
--- /dev/null
+++ b/src/classes/QueryDateLiteral_Tests.cls
@@ -0,0 +1,232 @@
+/*************************************************************************************************
+* 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 QueryDateLiteral_Tests {
+
+ private static Integer offsetNumber = 5;
+
+ @isTest
+ static void it_should_return_yesterday_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.YESTERDAY;
+ System.assertEquals('YESTERDAY', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_today_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.TODAY;
+ System.assertEquals('TODAY', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_tomorrow_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.TOMORROW;
+ System.assertEquals('TOMORROW', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_week_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_WEEK;
+ System.assertEquals('LAST_WEEK', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_this_week_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_WEEK;
+ System.assertEquals('THIS_WEEK', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_week_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_WEEK;
+ System.assertEquals('NEXT_WEEK', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_month_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_MONTH;
+ System.assertEquals('LAST_MONTH', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_this_month_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_MONTH;
+ System.assertEquals('THIS_MONTH', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_month_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_MONTH;
+ System.assertEquals('NEXT_MONTH', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_ninety_days_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_90_DAYS;
+ System.assertEquals('LAST_90_DAYS', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_ninety_days_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_90_DAYS;
+ System.assertEquals('NEXT_90_DAYS', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_this_quarter_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_QUARTER;
+ System.assertEquals('THIS_QUARTER', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_quarter_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_QUARTER;
+ System.assertEquals('LAST_QUARTER', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_fiscal_quarter_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_FISCAL_QUARTER;
+ System.assertEquals('LAST_FISCAL_QUARTER', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_this_fiscal_quarter_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_FISCAL_QUARTER;
+ System.assertEquals('THIS_FISCAL_QUARTER', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_quarter_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_QUARTER;
+ System.assertEquals('NEXT_QUARTER', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_fiscal_quarter_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_FISCAL_QUARTER;
+ System.assertEquals('NEXT_FISCAL_QUARTER', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_this_year_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_YEAR;
+ System.assertEquals('THIS_YEAR', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_this_fiscal_year_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.THIS_FISCAL_YEAR;
+ System.assertEquals('THIS_FISCAL_YEAR', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_year_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_YEAR;
+ System.assertEquals('LAST_YEAR', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_fiscal_year_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_FISCAL_YEAR;
+ System.assertEquals('LAST_FISCAL_YEAR', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_year_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_YEAR;
+ System.assertEquals('NEXT_YEAR', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_fiscal_year_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_FISCAL_YEAR;
+ System.assertEquals('NEXT_FISCAL_YEAR', dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_n_days_ago_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.N_DAYS_AGO(offsetNumber);
+ System.assertEquals('N_DAYS_AGO:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_n_days_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_DAYS(offsetNumber);
+ System.assertEquals('LAST_N_DAYS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_n_weeks_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_WEEKS(offsetNumber);
+ System.assertEquals('LAST_N_WEEKS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_n_months_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_MONTHS(offsetNumber);
+ System.assertEquals('LAST_N_MONTHS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_n_quarters_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_QUARTERS(offsetNumber);
+ System.assertEquals('LAST_N_QUARTERS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_n_years_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_YEARS(offsetNumber);
+ System.assertEquals('LAST_N_YEARS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_last_n_fiscal_years_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.LAST_N_FISCAL_YEARS(offsetNumber);
+ System.assertEquals('LAST_N_FISCAL_YEARS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_n_days_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_DAYS(offsetNumber);
+ System.assertEquals('NEXT_N_DAYS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_n_weeks_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_WEEKS(offsetNumber);
+ System.assertEquals('NEXT_N_WEEKS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_n_months_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_MONTHS(offsetNumber);
+ System.assertEquals('NEXT_N_MONTHS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_n_quarters_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_QUARTERS(offsetNumber);
+ System.assertEquals('NEXT_N_QUARTERS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_n_fiscal_quarters_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_FISCAL_QUARTERS(offsetNumber);
+ System.assertEquals('NEXT_N_FISCAL_QUARTERS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_n_years_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_YEARS(offsetNumber);
+ System.assertEquals('NEXT_N_YEARS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+ @isTest
+ static void it_should_return_next_n_fiscal_years_string() {
+ QueryDateLiteral dateLiteral = QueryDateLiteral.NEXT_N_FISCAL_YEARS(offsetNumber);
+ System.assertEquals('NEXT_N_FISCAL_YEARS:' + offsetNumber, dateLiteral.getValue());
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryDateLiteral_Tests.cls-meta.xml b/src/classes/QueryDateLiteral_Tests.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryDateLiteral_Tests.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryDate_Tests.cls b/src/classes/QueryDate_Tests.cls
new file mode 100644
index 00000000..d6c4fbfd
--- /dev/null
+++ b/src/classes/QueryDate_Tests.cls
@@ -0,0 +1,92 @@
+/*************************************************************************************************
+* 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 QueryDate_Tests {
+
+ @isTest
+ static void it_should_return_sobject_type() {
+ QueryDate dt = QueryDate.CALENDAR_MONTH(Schema.User.CreatedDate);
+ System.assertEquals(Schema.User.SObjectType, dt.getSObjectType());
+ }
+
+ @isTest
+ static void it_should_return_calendar_month_string() {
+ QueryDate dt = QueryDate.CALENDAR_MONTH(Schema.User.CreatedDate);
+ System.assertEquals('CALENDAR_MONTH(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_calendar_quarter_string() {
+ QueryDate dt = QueryDate.CALENDAR_QUARTER(Schema.User.CreatedDate);
+ System.assertEquals('CALENDAR_QUARTER(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_calendar_year_string() {
+ QueryDate dt = QueryDate.CALENDAR_YEAR(Schema.User.CreatedDate);
+ System.assertEquals('CALENDAR_YEAR(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_day_in_month_string() {
+ QueryDate dt = QueryDate.DAY_IN_MONTH(Schema.User.CreatedDate);
+ System.assertEquals('DAY_IN_MONTH(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_day_in_week_string() {
+ QueryDate dt = QueryDate.DAY_IN_WEEK(Schema.User.CreatedDate);
+ System.assertEquals('DAY_IN_WEEK(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_day_in_year_string() {
+ QueryDate dt = QueryDate.DAY_IN_YEAR(Schema.User.CreatedDate);
+ System.assertEquals('DAY_IN_YEAR(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_day_only_string() {
+ QueryDate dt = QueryDate.DAY_ONLY(Schema.User.CreatedDate);
+ System.assertEquals('DAY_ONLY(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_fiscal_month_string() {
+ QueryDate dt = QueryDate.FISCAL_MONTH(Schema.User.CreatedDate);
+ System.assertEquals('FISCAL_MONTH(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_fiscal_quarter_string() {
+ QueryDate dt = QueryDate.FISCAL_QUARTER(Schema.User.CreatedDate);
+ System.assertEquals('FISCAL_QUARTER(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_fiscal_year_string() {
+ QueryDate dt = QueryDate.FISCAL_YEAR(Schema.User.CreatedDate);
+ System.assertEquals('FISCAL_YEAR(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_hour_in_day_string() {
+ QueryDate dt = QueryDate.HOUR_IN_DAY(Schema.User.CreatedDate);
+ System.assertEquals('HOUR_IN_DAY(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_week_in_month_string() {
+ QueryDate dt = QueryDate.WEEK_IN_MONTH(Schema.User.CreatedDate);
+ System.assertEquals('WEEK_IN_MONTH(CreatedDate)', dt.getValue());
+ }
+
+ @isTest
+ static void it_should_return_week_in_year_string() {
+ QueryDate dt = QueryDate.WEEK_IN_YEAR(Schema.User.CreatedDate);
+ System.assertEquals('WEEK_IN_YEAR(CreatedDate)', dt.getValue());
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryDate_Tests.cls-meta.xml b/src/classes/QueryDate_Tests.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryDate_Tests.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryField.cls b/src/classes/QueryField.cls
new file mode 100644
index 00000000..2743e96e
--- /dev/null
+++ b/src/classes/QueryField.cls
@@ -0,0 +1,56 @@
+/*************************************************************************************************
+* 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 Used to dynamically generate field string for SObject fields, including parent fields.
+*
+*/
+public without sharing class QueryField implements IQueryField, Comparable {
+
+ private final List fields;
+ private final String value;
+
+ public QueryField(SObjectField field) {
+ this(new List{field});
+ }
+
+ public QueryField(List fields) {
+ this.fields = fields;
+ this.value = this.parseFields();
+ }
+
+ public Integer compareTo(Object compareTo) {
+ QueryField compareToQueryField = (QueryField)compareTo;
+ if(this.getValue() == compareToQueryField.getValue()) return 0;
+ if(this.getValue() > compareToQueryField.getValue()) return 1;
+ return -1;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+
+ public Schema.SObjectType getSObjectType() {
+ return new SObjectFieldDescriber(this.fields[0]).getSObjectType();
+ }
+
+ private String parseFields() {
+ if(this.fields.size() == 1) return String.valueOf(this.fields[0]);
+
+ //Remove the last field from the list to iterate through so only the parent relationships are hopped
+ List fieldsToIterate = this.fields.clone();
+ SObjectField lastField = (SObjectField) CollectionUtils.pop(fieldsToIterate);
+ List fieldChain = new List();
+ for(SObjectField parentField : fieldsToIterate) {
+ fieldChain.add(parentField.getDescribe().getRelationshipName());
+ }
+ // Return the fully qualified field name
+ return String.join(fieldChain, '.') + '.' + lastField.getDescribe().getName();
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryField.cls-meta.xml b/src/classes/QueryField.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryField.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryField_Tests.cls b/src/classes/QueryField_Tests.cls
new file mode 100644
index 00000000..a5cbe5aa
--- /dev/null
+++ b/src/classes/QueryField_Tests.cls
@@ -0,0 +1,29 @@
+@isTest
+private class QueryField_Tests {
+
+ @isTest
+ static void it_should_return_string_for_sobject_field_name() {
+ System.assertEquals('CreatedDate', new QueryField(Schema.Lead.CreatedDate).getValue());
+ }
+
+ @isTest
+ static void it_should_return_string_for_parent_sobject_field_name() {
+ List fieldChain = new List{
+ Schema.Contact.AccountId, Schema.Account.CreatedById, Schema.User.Name
+ };
+ System.assertEquals('Account.CreatedBy.Name', new QueryField(fieldChain).getValue());
+ }
+
+ @isTest
+ static void it_should_be_callable_multiple_times_without_pop_removing_field_references() {
+ List fieldChain = new List{
+ Schema.Contact.AccountId, Schema.Account.Name
+ };
+ QueryField queryField = new QueryField(fieldChain);
+ String expected = 'Account.Name';
+ for(Integer i = 0; i < 5; i++) {
+ System.assertEquals(expected, queryField.getValue());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryField_Tests.cls-meta.xml b/src/classes/QueryField_Tests.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryField_Tests.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryFilter.cls b/src/classes/QueryFilter.cls
new file mode 100644
index 00000000..23cb7f36
--- /dev/null
+++ b/src/classes/QueryFilter.cls
@@ -0,0 +1,134 @@
+/*************************************************************************************************
+* 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 Handles generating any query conditions (the WHERE statement in a query)
+* Each part of a WHERE statement is a separate instance of query filter.
+*
+*/
+public class QueryFilter implements IQueryFilter, Comparable {
+
+ private String value;
+
+ /**
+ * @description Creates a filter for a field on a parent sobject
+ * @param queryField An instance of QueryField, containg the field or chain of fields that should be filtered
+ * @param operator The instance of QueryOperator to use in the filter the list to check
+ * @param providedValue The value to compare to in the filter
+ * @return The instance of IQueryFilter, to allow chaining methods
+ * @example
+ * List parentFieldChain = new List{Schema.Lead.CreatedById, Schema.User.Email};
+ * QueryFilter filter = new QueryFilter().setValue(parentFieldChain, QueryOperator.NOT_EQUAL_TO, null);
+ * System.assertEquals('CreatedBy.Email != null', filter.getValue());
+ */
+ public IQueryFilter filterByField(QueryField queryField, QueryOperator operator, Object providedValue) {
+ this.value = queryField.getValue()
+ + ' ' + operator.getValue()
+ + ' ' + new QueryArgumentFormatter(providedValue).getValue();
+
+ return this;
+ }
+
+ /**
+ * @description Creates a filter for a date function
+ * @param queryDateToFilter An instance of QueryDate, created by supplying a date or datetime field to filter on
+ * @param operator The instance of QueryOperator to use in the filter the list to check
+ * @param providedValue The value to compare to in the filter
+ * @return The instance of IQueryFilter, to allow chaining methods
+ * @example
+ * QueryDate qd = QueryDate.CALENDAR_MONTH(Schema.Lead.CreatedDate);
+ * QueryFilter filter = new QueryFilter().setValue(qd, QueryOperator.EQUALS, 2);
+ * System.assertEquals('CALENDAR_MONTH(CreatedDate) = 2', filter.getValue());
+ */
+ public IQueryFilter filterByQueryDate(QueryDate queryDateToFilter, QueryOperator operator, Integer providedValue) {
+ this.value = queryDateToFilter.getValue()
+ + ' ' + operator.getValue()
+ + ' ' + providedValue;
+
+ return this;
+ }
+
+ /**
+ * @description Creates a filter for a subquery on the sobject's ID
+ * @param inOrNotIn An instance of QueryOperator - it must be QueryOperator.IS_IN or QueryOperator.IS_NOT_IN
+ * @param lookupFieldOnRelatedSObject The lookup field on a related object that contains the ID of the current sobject
+ * @return The instance of IQueryFilter, to allow chaining methods
+ * @example
+ * QueryFilter filter = new QueryFilter().setValue(QueryOperator.IS_IN, Schema.Lead.ConvertedAccountId);
+ * System.assertEquals('Id IN (SELECT ConvertedAccountId FROM Lead)', filter.getValue());
+ */
+ // TODO figure out a better solution for inOrNotIn
+ public IQueryFilter filterBySubquery(QueryOperator inOrNotIn, Schema.SObjectField lookupFieldOnRelatedSObject) {
+ return this.setValueForSubquery('Id', inOrNotIn, lookupFieldOnRelatedSObject);
+ }
+
+ /**
+ * @description Creates a filter for a subquery on an ID field for the current sobject
+ * @param lookupField The lookup field on the current sobject that contains an ID
+ * @param inOrNotIn An instance of QueryOperator - it must be QueryOperator.IS_IN or QueryOperator.IS_NOT_IN
+ * @param lookupFieldOnRelatedSObject The lookup field on a related object that contains the value of the lookupField paraemter
+ * @return The instance of IQueryFilter, to allow chaining methods
+ * @example
+ * QueryFilter filter = new QueryFilter().setValue(Schema.Lead.OwnerId, QueryOperator.IS_IN, Schema.User.Id);
+ * System.assertEquals('OwnerId IN (SELECT Id FROM User)', filter.getValue());
+ */
+ public IQueryFilter filterBySubquery(Schema.SObjectField lookupField, QueryOperator inOrNotIn, Schema.SObjectField lookupFieldOnRelatedSObject) {
+ return this.setValueForSubquery(lookupField.getDescribe().getName(), inOrNotIn, lookupFieldOnRelatedSObject);
+ }
+
+ /**
+ * @description Adds several filters together as a set of 'AND' filters
+ * @param queryFilters The filters to group together
+ * @return The instance of IQueryFilter, to allow chaining methods
+ */
+ public IQueryFilter andFilterBy(List queryFilters) {
+ return this.filterByWithSeparator(queryFilters, 'AND');
+ }
+
+ /**
+ * @description Adds several filters together as a set of 'OR' filters
+ * @param queryFilters The filters to group together
+ * @return The instance of IQueryFilter, to allow chaining methods
+ */
+ public IQueryFilter orFilterBy(List queryFilters) {
+ return this.filterByWithSeparator(queryFilters, 'OR');
+ }
+
+ /**
+ * @description Returns the calculated value, based on the method & parameters provided
+ * @return The string of the query filter, ready to be used in dynamic SOQL/SOSL
+ */
+ public String getValue() {
+ return this.value;
+ }
+
+ public Integer compareTo(Object compareTo) {
+ QueryFilter compareToQueryFilter = (QueryFilter)compareTo;
+ if(this.getValue() == compareToQueryFilter.getValue()) return 0;
+ if(this.getValue() > compareToQueryFilter.getValue()) return 1;
+ return -1;
+ }
+
+ private IQueryFilter setValueForSubquery(String idFieldName, QueryOperator inOrNotIn, Schema.SObjectField lookupFieldOnRelatedSObject) {
+ String relatedSObjectTypeName = new SObjectFieldDescriber(lookupFieldOnRelatedSObject).getSObjectType().getDescribe().getName();
+ String lookupFieldOnRelatedSObjectName = lookupFieldOnRelatedSObject.getDescribe().getName();
+
+ this.value = idFieldName + ' ' + inOrNotIn.getValue() + ' (SELECT ' + lookupFieldOnRelatedSObjectName + ' FROM ' + relatedSObjectTypeName + ')';
+
+ return this;
+ }
+
+ private IQueryFilter filterByWithSeparator(List queryFilters, String separator) {
+ List queryFilterValues = new List();
+ for(IQueryFilter queryFilter : queryFilters) queryFilterValues.add(queryFilter.getValue());
+
+ this.value = '(' + String.join(queryFilterValues, ' ' + separator + ' ') + ')';
+ return this;
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryFilter.cls-meta.xml b/src/classes/QueryFilter.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryFilter.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryFilterScope.cls b/src/classes/QueryFilterScope.cls
new file mode 100644
index 00000000..55dc3da6
--- /dev/null
+++ b/src/classes/QueryFilterScope.cls
@@ -0,0 +1,14 @@
+/*************************************************************************************************
+* 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 Enum of possible values for SOQL's optional USING SCOPE
+* Salesforce docs: developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_using_scope.htm
+*
+*/
+public enum QueryFilterScope { EVERYTHING, DELEGATED, TEAM, MINE, MY_TERRITORY, MY_TEAM_TERRITORY }
\ No newline at end of file
diff --git a/src/classes/QueryFilterScope.cls-meta.xml b/src/classes/QueryFilterScope.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryFilterScope.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryFilter_Tests.cls b/src/classes/QueryFilter_Tests.cls
new file mode 100644
index 00000000..c87958a8
--- /dev/null
+++ b/src/classes/QueryFilter_Tests.cls
@@ -0,0 +1,95 @@
+/*************************************************************************************************
+* 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 QueryFilter_Tests {
+
+ @isTest
+ static void it_should_return_the_query_filter_for_a_field() {
+ Schema.SObjectField sobjectField = Schema.User.CompanyName;
+ QueryOperator operator = QueryOperator.IS_IN;
+ List providedValues = new List{'derp', 'herp'};
+
+ Test.startTest();
+ QueryFilter queryFilter = (QueryFilter)new QueryFilter().filterByField(new QueryField(sobjectField), operator, providedValues);
+ Test.stopTest();
+
+ String expectedQueryFilter = 'CompanyName ' + operator.getValue() + ' (\'' + String.join(providedValues, '\', \'') + '\')';
+ System.assertEquals(expectedQueryFilter, queryFilter.getValue());
+ }
+
+ @isTest
+ static void it_should_return_the_query_filter_for_a_parent_field() {
+ QueryField parentFieldToFilter = new QueryField(new List{
+ Schema.Lead.CreatedById, Schema.User.Email
+ });
+ QueryOperator operator = QueryOperator.EQUALS;
+ String providedValue = 'derp@test.com';
+
+ Test.startTest();
+ QueryFilter queryFilter = (QueryFilter)new QueryFilter().filterByField(parentFieldToFilter, operator, providedValue);
+ Test.stopTest();
+
+ String expectedQueryFilter = 'CreatedBy.Email ' + operator.getValue() + ' \'' + providedValue + '\'';
+ System.assertEquals(expectedQueryFilter, queryFilter.getValue());
+ }
+
+ @isTest
+ static void it_should_return_the_query_filter_for_a_grandparent_field() {
+ QueryField grandparentFieldToFilter = new QueryField(new List{
+ Schema.Lead.OwnerId, Schema.User.ManagerId, Schema.User.ProfileId, Schema.Profile.Name
+ });
+ QueryOperator operator = QueryOperator.EQUALS;
+ String providedValue = 'derp';
+
+ Test.startTest();
+ QueryFilter queryFilter = (QueryFilter)new QueryFilter().filterByField(grandparentFieldToFilter, operator, providedValue);
+ Test.stopTest();
+
+ String expectedQueryFilter = 'Owner.Manager.Profile.Name ' + operator.getValue() + ' \'' + providedValue + '\'';
+ System.assertEquals(expectedQueryFilter, queryFilter.getValue());
+ }
+
+ @isTest
+ static void it_should_return_the_query_filter_for_a_query_date() {
+ QueryDate qd = QueryDate.CALENDAR_MONTH(Schema.Lead.CreatedDate);
+ QueryOperator operator = QueryOperator.EQUALS;
+ Integer providedValue = 3;
+
+ Test.startTest();
+ QueryFilter queryFilter = (QueryFilter)new QueryFilter().filterByQueryDate(qd, operator, providedValue);
+ Test.stopTest();
+
+ String expectedQueryFilter = 'CALENDAR_MONTH(CreatedDate) ' + operator.getValue() + ' ' + providedValue;
+ System.assertEquals(expectedQueryFilter, queryFilter.getValue());
+ }
+
+ @isTest
+ static void it_should_return_the_query_filter_for_a_subquery() {
+ QueryOperator operator = QueryOperator.IS_IN;
+ Schema.SObjectField lookupFieldOnRelatedSObject = Schema.Lead.ConvertedAccountId;
+
+ Test.startTest();
+ QueryFilter queryFilter = (QueryFilter)new QueryFilter().filterBySubquery(operator, lookupFieldOnRelatedSObject);
+ Test.stopTest();
+
+ String expectedQueryFilter = 'Id ' + operator.getValue() + ' (SELECT ' + lookupFieldOnRelatedSObject.getDescribe().getName() + ' FROM Lead)';
+ System.assertEquals(expectedQueryFilter, queryFilter.getValue());
+ }
+
+ @isTest
+ static void it_should_return_the_query_filter_for_a_subquery_with_a_specified_field() {
+ QueryOperator operator = QueryOperator.IS_IN;
+ Schema.SObjectField lookupField = Schema.Lead.OwnerId;
+ Schema.SObjectField lookupFieldOnRelatedSObject = Schema.User.Id;
+
+ Test.startTest();
+ QueryFilter queryFilter = (QueryFilter)new QueryFilter().filterBySubquery(lookupField, operator, lookupFieldOnRelatedSObject);
+ Test.stopTest();
+
+ String expectedQueryFilter = 'OwnerId ' + operator.getValue() + ' (SELECT ' + lookupFieldOnRelatedSObject.getDescribe().getName() + ' FROM User)';
+ System.assertEquals(expectedQueryFilter, queryFilter.getValue());
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryFilter_Tests.cls-meta.xml b/src/classes/QueryFilter_Tests.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryFilter_Tests.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryNullSortOrder.cls b/src/classes/QueryNullSortOrder.cls
new file mode 100644
index 00000000..42855b8f
--- /dev/null
+++ b/src/classes/QueryNullSortOrder.cls
@@ -0,0 +1,11 @@
+/*************************************************************************************************
+* 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
+*
+*/
+public enum QueryNullSortOrder { FIRST, LAST }
\ No newline at end of file
diff --git a/src/classes/QueryNullSortOrder.cls-meta.xml b/src/classes/QueryNullSortOrder.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryNullSortOrder.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryOperator.cls b/src/classes/QueryOperator.cls
new file mode 100644
index 00000000..3dcec1f3
--- /dev/null
+++ b/src/classes/QueryOperator.cls
@@ -0,0 +1,39 @@
+/*************************************************************************************************
+* 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 Provides all of the operators needed for SOQL/SOSL queries and minimizes the use of strings within the framework
+* Salesforce docs: developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_comparisonoperators.htm
+*
+*/
+public without sharing class QueryOperator {
+
+ private String value;
+
+ private QueryOperator(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+
+ public static final QueryOperator EQUALS = new QueryOperator('=');
+ public static final QueryOperator NOT_EQUAL_TO = new QueryOperator('!=');
+ public static final QueryOperator GREATER_THAN = new QueryOperator('>');
+ public static final QueryOperator GREATER_THAN_OR_EQUAL_TO = new QueryOperator('>=');
+ public static final QueryOperator LESS_THAN = new QueryOperator('<');
+ public static final QueryOperator LESS_THAN_OR_EQUAL_TO = new QueryOperator('<=');
+ public static final QueryOperator IS_IN = new QueryOperator('IN');
+ public static final QueryOperator IS_NOT_IN = new QueryOperator('NOT IN');
+ public static final QueryOperator INCLUDES = new QueryOperator('INCLUDES');
+ public static final QueryOperator EXCLUDES = new QueryOperator('EXCLUDES');
+ public static final QueryOperator IS_LIKE = new QueryOperator('LIKE');
+ public static final QueryOperator IS_NOT_LIKE = new QueryOperator('NOT LIKE');
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryOperator.cls-meta.xml b/src/classes/QueryOperator.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryOperator.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QueryOperator_Tests.cls b/src/classes/QueryOperator_Tests.cls
new file mode 100644
index 00000000..1645beb6
--- /dev/null
+++ b/src/classes/QueryOperator_Tests.cls
@@ -0,0 +1,68 @@
+/*************************************************************************************************
+* 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 QueryOperator_Tests {
+
+ @isTest
+ static void it_should_return_EQUALS_string() {
+ System.assertEquals('=', QueryOperator.EQUALS.getValue());
+ }
+
+ @isTest
+ static void it_should_return_NOT_EQUAL_TO_string() {
+ System.assertEquals('!=', QueryOperator.NOT_EQUAL_TO.getValue());
+ }
+
+ @isTest
+ static void it_should_return_GREATER_THAN_string() {
+ System.assertEquals('>', QueryOperator.GREATER_THAN.getValue());
+ }
+
+ @isTest
+ static void it_should_return_GREATER_THAN_OR_EQUAL_TO_string() {
+ System.assertEquals('>=', QueryOperator.GREATER_THAN_OR_EQUAL_TO.getValue());
+ }
+
+ @isTest
+ static void it_should_return_LESS_THAN_string() {
+ System.assertEquals('<', QueryOperator.LESS_THAN.getValue());
+ }
+
+ @isTest
+ static void it_should_return_LESS_THAN_OR_EQUAL_TO_string() {
+ System.assertEquals('<=', QueryOperator.LESS_THAN_OR_EQUAL_TO.getValue());
+ }
+
+ @isTest
+ static void it_should_return_IS_IN_string() {
+ System.assertEquals('IN', QueryOperator.IS_IN.getValue());
+ }
+
+ @isTest
+ static void it_should_return_IS_NOT_IN_string() {
+ System.assertEquals('NOT IN', QueryOperator.IS_NOT_IN.getValue());
+ }
+
+ @isTest
+ static void it_should_return_INCLUDES_string() {
+ System.assertEquals('INCLUDES', QueryOperator.INCLUDES.getValue());
+ }
+
+ @isTest
+ static void it_should_return_EXCLUDES_string() {
+ System.assertEquals('EXCLUDES', QueryOperator.EXCLUDES.getValue());
+ }
+
+ @isTest
+ static void it_should_return_IS_LIKE_string() {
+ System.assertEquals('LIKE', QueryOperator.IS_LIKE.getValue());
+ }
+
+ @isTest
+ static void it_should_return_IS_NOT_LIKE_string() {
+ System.assertEquals('NOT LIKE', QueryOperator.IS_NOT_LIKE.getValue());
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/QueryOperator_Tests.cls-meta.xml b/src/classes/QueryOperator_Tests.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QueryOperator_Tests.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QuerySearchGroup.cls b/src/classes/QuerySearchGroup.cls
new file mode 100644
index 00000000..519e33af
--- /dev/null
+++ b/src/classes/QuerySearchGroup.cls
@@ -0,0 +1,11 @@
+/*************************************************************************************************
+* 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
+*
+*/
+public enum QuerySearchGroup { ALL_FIELDS, NAME_FIELDS, EMAIL_FIELDS, PHONE_FIELDS, SIDEBAR_FIELDS }
\ No newline at end of file
diff --git a/src/classes/QuerySearchGroup.cls-meta.xml b/src/classes/QuerySearchGroup.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QuerySearchGroup.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/QuerySortOrder.cls b/src/classes/QuerySortOrder.cls
new file mode 100644
index 00000000..0a25d39d
--- /dev/null
+++ b/src/classes/QuerySortOrder.cls
@@ -0,0 +1,11 @@
+/*************************************************************************************************
+* 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
+*
+*/
+public enum QuerySortOrder { ASCENDING, DESCENDING }
\ No newline at end of file
diff --git a/src/classes/QuerySortOrder.cls-meta.xml b/src/classes/QuerySortOrder.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/QuerySortOrder.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/SOQLUtils.cls b/src/classes/SOQLUtils.cls
deleted file mode 100644
index 48273f39..00000000
--- a/src/classes/SOQLUtils.cls
+++ /dev/null
@@ -1,41 +0,0 @@
-public without sharing class SOQLUtils {
-
- public static String toSOQLString(List valueList) {
- List parsedValueList = new List();
- for(Object value : valueList) parsedValueList.add(toSOQLString(value));
- return '(' + String.join(parsedValueList, ',') + ')';
- }
-
- public static String toSOQLString(Object value) {
- if(value == null) return null;
- else if(value instanceof DateLiterals) {
- DateLiterals dateLiteral = (DateLiterals)value;
- return dateLiteral.value;
- }
- else if(value instanceof Boolean) return String.valueOf((Boolean)value);
- else if(value instanceof Date) return String.valueOf((Date)value);
- else if(value instanceof Datetime) {
- Datetime datetimeValue = (Datetime)value;
- return datetimeValue.format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'', 'Greenwich Mean Time');
- }
- else if(value instanceof Decimal) return String.valueOf((Decimal) value);
- else if(value instanceof Double) return String.valueOf((Double) value);
- else if(value instanceof Integer) return String.valueOf((Integer) value);
- else if(value instanceof Long) return String.valueOf((Long) value);
- else if(value instanceof SObject) {
- SObject record = (SObject)value;
- return wrapInSingleQuotes(record.Id);
- }
- else if(value instanceof String) return wrapInSingleQuotes((String)value);
- else return String.valueOf(value);
- }
-
- public static String wrapInSingleQuotes(String input) {
- if(input.toLowerCase() == 'null') return input;
-
- if(input.left(1) != '\'') input = '\'' + input;
- if(input.right(1) != '\'') input = input + '\'';
- return input;
- }
-
-}
\ No newline at end of file
diff --git a/src/classes/SOQLUtils.cls-meta.xml b/src/classes/SOQLUtils.cls-meta.xml
deleted file mode 100644
index cbddff8c..00000000
--- a/src/classes/SOQLUtils.cls-meta.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- 38.0
- Active
-
diff --git a/src/classes/SObjectFieldDescriber.cls b/src/classes/SObjectFieldDescriber.cls
new file mode 100644
index 00000000..5a56dc44
--- /dev/null
+++ b/src/classes/SObjectFieldDescriber.cls
@@ -0,0 +1,91 @@
+/*************************************************************************************************
+* 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 Metadata
+*
+*/
+public without sharing class SObjectFieldDescriber {
+ // We would love for Schema.SObjectField to have a way to return the SObject Type
+ // Sadly, it doesn't, so we have this class to fill the void.
+ // If a proper method is ever added to Apex to get the field's SObject Type,
+ // then we should consider removing this class.
+
+ private static Map sobjectFieldHashCodeToSObjectTypeMap;
+
+ private Schema.SObjectField sobjectField;
+ private Schema.SObjectType sobjectType;
+
+ public SObjectFieldDescriber(Schema.SObjectField sobjectField) {
+ this.cacheSObjectTypeFieldHashCodes();
+
+ this.sobjectField = sobjectField;
+ this.setSObjectType();
+ }
+
+ public String getFieldName() {
+ return this.sobjectField.getDescribe().getName();
+ }
+
+ public String getFullFieldName() {
+ return this.sobjectType + '.' + this.getFieldName();
+ }
+
+ public Schema.SObjectType getSObjectType() {
+ return this.sobjectType;
+ }
+
+ public Schema.SObjectType getParentSObjectType() {
+ Schema.DescribeFieldResult fieldDescribe = this.SObjectField.getDescribe();
+
+ Schema.SObjectType parentSObjectType;
+ if(!fieldDescribe.isNamePointing() && !fieldDescribe.getReferenceTo().isEmpty()) parentSObjectType = fieldDescribe.getReferenceTo()[0];
+
+ return parentSObjectType;
+ }
+
+ public List getParentSObjectTypes() {
+ Schema.DescribeFieldResult fieldDescribe = this.SObjectField.getDescribe();
+
+ List parentSObjectTypes;
+ if(!fieldDescribe.isNamePointing() && !fieldDescribe.getReferenceTo().isEmpty()) parentSObjectTypes = fieldDescribe.getReferenceTo();
+
+ return parentSObjectTypes;
+ }
+
+ public Boolean validateSObjectType(Schema.SObjectType expectedSObjectType) {
+ return this.SObjectType == expectedSObjectType;
+ }
+
+ private void setSObjectType() {
+ Integer sobjectFieldHashCode = this.getHashCode(this.sobjectField);
+ this.SObjectType = sobjectFieldHashCodeToSObjectTypeMap.get(sobjectFieldHashCode);
+ }
+
+ private void cacheSObjectTypeFieldHashCodes() {
+ // Describe calls are "free" but still add CPU time, so let's cache them
+ if(sobjectFieldHashCodeToSObjectTypeMap != null) return;
+
+ sobjectFieldHashCodeToSObjectTypeMap = new Map();
+
+ // Build a map of hash codes for each fieldDescribe taken from Schema Global Describe
+ Map globalDescribe = Schema.getGlobalDescribe();
+ for(String sobjTypeName : globalDescribe.keySet()) {
+ SObjectType sobjType = globalDescribe.get(sobjTypeName);
+
+ for(Schema.SObjectField sobjField : sobjType.getDescribe().fields.getMap().values()) {
+ Integer sobjFieldHashCode = getHashCode(sobjField);
+
+ sobjectFieldHashCodeToSObjectTypeMap.put(sobjFieldHashCode, sobjType);
+ }
+ }
+ }
+
+ private Integer getHashCode(Schema.SObjectField sobjField) {
+ return ((Object)sobjField).hashCode();
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/SObjectFieldDescriber.cls-meta.xml b/src/classes/SObjectFieldDescriber.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/SObjectFieldDescriber.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/SObjectFieldDescriber_Tests.cls b/src/classes/SObjectFieldDescriber_Tests.cls
new file mode 100644
index 00000000..3117e271
--- /dev/null
+++ b/src/classes/SObjectFieldDescriber_Tests.cls
@@ -0,0 +1,61 @@
+/*************************************************************************************************
+* 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 SObjectFieldDescriber_Tests {
+
+ @isTest
+ static void it_should_return_the_sobject_type() {
+ Schema.SObjectType expectedSObjectType = Schema.Lead.SObjectType;
+ Schema.SObjectField sobjectField = Schema.Lead.Id;
+
+ Test.startTest();
+
+ SObjectFieldDescriber sobjectFieldDescriber = new SObjectFieldDescriber(sobjectField);
+ System.assertEquals(expectedSObjectType, sobjectFieldDescriber.getSObjectType());
+
+ Test.stopTest();
+ }
+
+ @isTest
+ static void it_should_validate_the_expected_sobject_type() {
+ Schema.SObjectType expectedSObjectType = Schema.Lead.SObjectType;
+ Schema.SObjectField sobjectField = Schema.Lead.Id;
+
+ Test.startTest();
+
+ SObjectFieldDescriber sobjectFieldDescriber = new SObjectFieldDescriber(sobjectField);
+ System.assert(sobjectFieldDescriber.validateSObjectType(expectedSObjectType));
+
+ Test.stopTest();
+ }
+
+ @isTest
+ static void it_should_return_the_full_field_name() {
+ Schema.SObjectType expectedSObjectType = Schema.Lead.SObjectType;
+ Schema.SObjectField sobjectField = Schema.Lead.Id;
+ String expectedFullFieldName = String.valueOf(expectedSObjectType) + '.' + String.valueOf(sobjectField);
+
+ Test.startTest();
+
+ SObjectFieldDescriber sobjectFieldDescriber = new SObjectFieldDescriber(sobjectField);
+ System.assertEquals(expectedFullFieldName, sobjectFieldDescriber.getFullFieldName());
+
+ Test.stopTest();
+ }
+
+ @isTest
+ static void it_should_return_the_parent_field_sobject_type() {
+ Schema.SObjectField sobjectField = Schema.Lead.ConvertedAccountId;
+ Schema.SObjectType expectedParentSObjectType = Schema.Account.SObjectType;
+
+ Test.startTest();
+
+ SObjectFieldDescriber sobjectFieldDescriber = new SObjectFieldDescriber(sobjectField);
+ System.assertEquals(expectedParentSObjectType, sobjectFieldDescriber.getParentSObjectType());
+
+ Test.stopTest();
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/SObjectFieldDescriber_Tests.cls-meta.xml b/src/classes/SObjectFieldDescriber_Tests.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/SObjectFieldDescriber_Tests.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/SObjectQueryBuilder.cls b/src/classes/SObjectQueryBuilder.cls
new file mode 100644
index 00000000..5b4b5c5a
--- /dev/null
+++ b/src/classes/SObjectQueryBuilder.cls
@@ -0,0 +1,350 @@
+/*************************************************************************************************
+* 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 SOQL & returns an SObject or list of SObjects
+*
+*/
+public class SObjectQueryBuilder extends QueryBuilder implements ISObjectQueryBuilder {
+
+ private Set queryFields;
+ private Set queryFieldsToExclude;
+ private List childRelationshipQueries;
+ private QueryFilterScope filterScope;
+ private Integer offset;
+ private Boolean forReference;
+ private Boolean forUpdate;
+ private Boolean forView;
+ private SObjectType sobjectType;
+ private Map sobjectTypeFieldMap;
+
+ public SObjectQueryBuilder(Schema.SObjectType sobjectType) {
+ this.sobjectType = sobjectType;
+ this.sobjectTypeFieldMap = sobjectType.getDescribe().fields.getMap();
+
+ this.queryFields = new Set();
+ this.queryFieldsToExclude = new Set();
+ this.childRelationshipQueries = new List();
+ this.forReference = false;
+ this.forUpdate = false;
+ this.forView = false;
+
+ this.addCommonQueryFields();
+ }
+
+ public ISObjectQueryBuilder cacheResults() {
+ super.doCacheResults();
+ return this;
+ }
+
+ public ISObjectQueryBuilder addFields(List queryFields) {
+ for(IQueryField queryField : queryFields) this.addField(queryField);
+ return this;
+ }
+
+ public ISObjectQueryBuilder addFields(Schema.FieldSet fieldSet) {
+ for(Schema.FieldSetMember field : fieldSet.getFields()) {
+ this.addField(this.sobjectTypeFieldMap.get(field.getFieldPath()));
+ }
+ return this;
+ }
+
+ public ISObjectQueryBuilder addAllFields() {
+ for(Schema.SObjectField field : this.sobjectTypeFieldMap.values()) this.addField(field);
+ return this;
+ }
+
+ public ISObjectQueryBuilder addAllStandardFields() {
+ for(Schema.SObjectField field : this.sobjectTypeFieldMap.values()) {
+ if(field.getDescribe().isCustom()) continue;
+
+ this.addField(field);
+ }
+ return this;
+ }
+
+ public ISObjectQueryBuilder addAllCustomFields() {
+ for(Schema.SObjectField field : this.sobjectTypeFieldMap.values()) {
+ if(!field.getDescribe().isCustom()) continue;
+
+ this.addField(field);
+ }
+ return this;
+ }
+
+ public ISObjectQueryBuilder addAllReadableFields() {
+ for(Schema.SObjectField field : this.sobjectTypeFieldMap.values()) {
+ if(!field.getDescribe().isAccessible()) continue;
+
+ this.addField(field);
+ }
+ return this;
+ }
+
+ public ISObjectQueryBuilder addAllEditableFields() {
+ for(Schema.SObjectField field : this.sobjectTypeFieldMap.values()) {
+ if(!field.getDescribe().isUpdateable()) continue;
+
+ this.addField(field);
+ }
+ return this;
+ }
+
+ public ISObjectQueryBuilder excludeFields(List queryFields) {
+ for(IQueryField queryField : queryFields) this.excludeField(queryField);
+ return this;
+ }
+
+ public ISObjectQueryBuilder excludeFields(Schema.FieldSet fieldSet) {
+ for(Schema.FieldSetMember field : fieldSet.getFields()) {
+ this.excludeField(this.sobjectTypeFieldMap.get(field.getFieldPath()));
+ }
+ return this;
+ }
+
+ public ISObjectQueryBuilder includeChildrenRecords(Schema.SObjectField childToParentRelationshipField, ISObjectQueryBuilder sobjectQueryBuilder) {
+ childRelationshipQueries.add(sobjectQueryBuilder.getChildQuery(childToParentRelationshipField));
+ return this;
+ }
+
+ public ISObjectQueryBuilder filterBy(IQueryFilter queryFilter) {
+ super.doFilterBy(queryFilter);
+ return this;
+ }
+
+ public ISObjectQueryBuilder filterBy(List queryFilters) {
+ super.doFilterBy(queryFilters);
+ return this;
+ }
+
+ public ISObjectQueryBuilder orderBy(IQueryField orderByQueryField) {
+ super.doOrderBy(orderByQueryField);
+ return this;
+ }
+
+ public ISObjectQueryBuilder orderBy(IQueryField orderByQueryField, QuerySortOrder sortOrder) {
+ super.doOrderBy(orderByQueryField, sortOrder);
+ return this;
+ }
+
+ public ISObjectQueryBuilder orderBy(IQueryField orderByQueryField, QuerySortOrder sortOrder, QueryNullSortOrder nullsSortOrder) {
+ super.doOrderBy(orderByQueryField, sortOrder, nullsSortOrder);
+ return this;
+ }
+
+ public ISObjectQueryBuilder limitCount(Integer limitCount) {
+ super.doLimitCount(limitCount);
+ return this;
+ }
+
+ public ISObjectQueryBuilder offset(Integer numberOfRowsToSkip) {
+ this.offset = numberOfRowsToSkip;
+ return this;
+ }
+
+ public ISObjectQueryBuilder forReference() {
+ this.forReference = true;
+ return this;
+ }
+
+ public ISObjectQueryBuilder forUpdate() {
+ this.forUpdate = true;
+ return this;
+ }
+
+ public ISObjectQueryBuilder forView() {
+ this.forView = true;
+ return this;
+ }
+
+ public ISObjectQueryBuilder usingScope(QueryFilterScope filterScope) {
+ this.filterScope = filterScope;
+ return this;
+ }
+
+ public Database.QueryLocator getQueryLocator() {
+ return Database.getQueryLocator(this.getQuery());
+ }
+
+ public String getQuery() {
+ String query = 'SELECT ' + this.getQueryFieldString() + this.getChildRelationshipsQueryString()
+ + '\nFROM ' + this.sobjectType
+ + this.getUsingScopeString()
+ + super.doGetWhereClauseString()
+ + super.doGetOrderByString()
+ + super.doGetLimitCountString()
+ + this.getOffSetString()
+ + this.getForReferenceString()
+ + this.getForUpdateString()
+ + this.getForViewString();
+
+ return query;
+ }
+
+ public String getSearchQuery() {
+ String sobjectTypeOptions = this.getQueryFieldString()
+ + super.doGetWhereClauseString()
+ + super.doGetOrderByString()
+ + super.doGetLimitCountString();
+ // If we have any sobject-specific options, then wrap the options in parentheses
+ sobjectTypeOptions = String.isEmpty(sobjectTypeOptions) ? '' : '(' + sobjectTypeOptions + ')';
+ String query = this.sobjectType + sobjectTypeOptions;
+
+ return query;
+ }
+
+ public String getChildQuery(Schema.SObjectField childToParentRelationshipField) {
+ Schema.SObjectType parentSObjectType = childToParentRelationshipField.getDescribe().getReferenceTo()[0];
+ // Get the relationship name
+ String childRelationshipName;
+ for(Schema.ChildRelationship childRelationship : parentSObjectType.getDescribe().getChildRelationships()) {
+ if(childRelationship.getField() != childToParentRelationshipField) continue;
+
+ childRelationshipName = childRelationship.getRelationshipName();
+ }
+
+ String query = 'SELECT ' + this.getQueryFieldString()
+ + '\nFROM ' + childRelationshipName
+ + this.getUsingScopeString()
+ + super.doGetWhereClauseString()
+ + super.doGetOrderByString()
+ + super.doGetLimitCountString();
+
+ return query;
+ }
+
+ // Query execution methods
+ public SObject getFirstQueryResult() {
+ return this.getQueryResults()[0];
+ }
+
+ public List getQueryResults() {
+ return super.doGetQueryResults(this.getQuery());
+ }
+
+ private void addField(IQueryField queryField) {
+ // If it's a parent field, add it immediately
+ if(queryField.getValue().contains('.')) this.queryFields.add(queryField.getValue());
+ // Otherwise, parse it to an SObjectField so we can reused the logic in addField(Schema.SObjectField field)
+ else {
+ Schema.SObjectField field = this.sobjectType.getDescribe().fields.getMap().get(queryField.getValue());
+ this.addField(field);
+ }
+ }
+
+ private void addField(Schema.SObjectField field) {
+ this.queryFields.add(field.getDescribe().getName());
+
+ // If the field is a lookup, then we need to get the name field from the parent object
+ if(field.getDescribe().getType().name() == 'Reference') {
+ this.queryFields.add(this.getParentSObjectNameField(field));
+ }
+ }
+
+ private void excludeField(IQueryField queryField) {
+ // If it's a parent field, add it immediately
+ if(queryField.getValue().contains('.')) this.queryFieldsToExclude.add(queryField.getValue());
+ // Otherwise, parse it to an SObjectField so we can reused the logic in excludeField(Schema.SObjectField field)
+ else {
+ Schema.SObjectField field = this.sobjectType.getDescribe().fields.getMap().get(queryField.getValue());
+ this.excludeField(field);
+ }
+ }
+
+ private void excludeField(Schema.SObjectField field) {
+ this.queryFieldsToExclude.add(field.getDescribe().getName());
+
+ // If the field is a lookup, then we need to get the name field from the parent object
+ if(field.getDescribe().getType().name() == 'Reference') {
+ this.queryFieldsToExclude.add(this.getParentSObjectNameField(field));
+ }
+ }
+
+ private String getParentSObjectNameField(Schema.SObjectField field) {
+ String relationshipName = field.getDescribe().getRelationshipName();
+ Schema.SObjectType parentSObjectType = field.getDescribe().getReferenceTo()[0];
+
+ return relationshipName + '.' + this.getSObjectNameField(parentSObjectType);
+ }
+
+ private String getSObjectNameField(Schema.SObjectType sobjectType) {
+ String nameField;
+ for(Schema.SObjectField parentField : sobjectType.getDescribe().fields.getMap().values()) {
+ if(parentField.getDescribe().isNameField()) {
+ nameField = parentField.getDescribe().getName();
+ break;
+ }
+ }
+ return nameField;
+ }
+
+ private String getQueryFieldString() {
+ // Remove any fields to be excluded
+ this.queryFields.removeAll(this.queryFieldsToExclude);
+
+ // If no fields have been added, then add just the ID & name fields
+ if(this.queryFields.isEmpty()) {
+ Schema.SObjectField idField = this.sobjectTypeFieldMap.get('Id');
+ this.addField(idField);
+
+ Schema.SObjectField nameField = this.sobjectTypeFieldMap.get(this.getSObjectNameField(this.sobjectType));
+ if(nameField != null) this.addField(nameField);
+ }
+
+ Set cleanedQueryField = new Set();
+ for(String queryField : new List(this.queryFields)) {
+ cleanedQueryField.add(queryField.toLowerCase());
+ }
+ this.queryFields = cleanedQueryField;
+ List queryFieldList = new List(this.queryFields);
+ queryFieldList.sort();
+ return String.join(queryFieldList, ', ');
+ }
+
+ private String getChildRelationshipsQueryString() {
+ if(this.childRelationshipQueries.isEmpty()) return '';
+
+ this.childRelationshipQueries.sort();
+ return ',\n(' + String.join(this.childRelationshipQueries, '), (') + ')';
+ }
+
+ private String getOffSetString() {
+ return this.offset == null ? '' : '\nOFFSET ' + this.offset;
+ }
+
+ private String getForReferenceString() {
+ return !this.forReference ? '' : '\nFOR REFERENCE';
+ }
+
+ private String getForUpdateString() {
+ return !this.forUpdate ? '' : '\nFOR UPDATE';
+ }
+
+ private String getForViewString() {
+ return !this.forView ? '' : '\nFOR VIEW';
+ }
+
+ private String getUsingScopeString() {
+ return this.filterScope == null ? '' : '\nUSING SCOPE ' + this.filterScope.name();
+ }
+
+ private void addCommonQueryFields() {
+ // Auto-add the common fields that are available for the SObject Type
+ List commonFieldNameList = new List{
+ 'Id', 'CaseNumber', 'CreatedById', 'CreatedDate', 'IsClosed', 'LastModifiedById', 'LastModifiedDate',
+ 'Name', 'OwnerId', 'ParentId', 'Subject', 'RecordTypeId', 'SystemModStamp'
+ };
+ for(String commonFieldName : commonFieldNameList) {
+ this.sobjectTypeFieldMap = this.sobjectType.getDescribe().fields.getMap();
+ if(!this.sobjectTypeFieldMap.containsKey(commonFieldName)) continue;
+
+ this.queryFields.add(commonFieldName);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/classes/SObjectQueryBuilder.cls-meta.xml b/src/classes/SObjectQueryBuilder.cls-meta.xml
new file mode 100644
index 00000000..94f6f064
--- /dev/null
+++ b/src/classes/SObjectQueryBuilder.cls-meta.xml
@@ -0,0 +1,5 @@
+
+
+ 40.0
+ Active
+
diff --git a/src/classes/SObjectQueryBuilder_Tests.cls b/src/classes/SObjectQueryBuilder_Tests.cls
new file mode 100644
index 00000000..53081a23
--- /dev/null
+++ b/src/classes/SObjectQueryBuilder_Tests.cls
@@ -0,0 +1,432 @@
+/*************************************************************************************************
+* 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 SObjectQueryBuilder_Tests {
+
+ static String getFirstLineThatStartsWith(String stringToSearch, String stringToCheckFor) {
+ String matchingLine;
+ for(String stringPartToSearch : stringToSearch.split('\n')) {
+ if(!stringPartToSearch.startsWith(stringToCheckFor)) continue;
+
+ matchingLine = stringPartToSearch;
+ break;
+ }
+
+ return matchingLine;
+ }
+
+ static Set getQueryQueryFieldStringSet(String queryString) {
+ String selectString = getFirstLineThatStartsWith(queryString, 'SELECT');
+ Set queryFieldStringSet = new Set();
+ for(String unparsedString : selectString.remove('SELECT ').split(',')) {
+ queryFieldStringSet.add(unparsedString.trim());
+ }
+ return queryFieldStringSet;
+ }
+
+ static String getParentSObjectNameField(Schema.SObjectField field) {
+ if(field.getDescribe().getType().name() != 'Reference') return null;
+
+ String relationshipName = field.getDescribe().getRelationshipName();
+ Schema.SObjectType parentSObjectType = field.getDescribe().getReferenceTo()[0];
+ String nameField;
+ for(Schema.SObjectField parentField : parentSObjectType.getDescribe().fields.getMap().values()) {
+ if(parentField.getDescribe().isNameField()) {
+ nameField = parentField.getDescribe().getName();
+ break;
+ }
+ }
+ return relationshipName + '.' + nameField;
+ }
+
+ static List buildExpectedQueryFieldStrings(List sobjectFields) {
+ List expectedQueryFieldStrings = new List();
+ for(Schema.SObjectField field : sobjectFields) {
+ expectedQueryFieldStrings.add(field.getDescribe().getName().toLowerCase());
+
+ String parentNameField = getParentSObjectNameField(field);
+ if(parentNameField != null) expectedQueryFieldStrings.add(parentNameField.toLowerCase());
+ }
+ expectedQueryFieldStrings.sort();
+ return expectedQueryFieldStrings;
+ }
+
+ static List convertToQueryFields(List fields) {
+ List queryFields = new List();
+ for(Schema.SObjectField field : fields) queryFields.add(new QueryField(field));
+ return queryFields;
+ }
+
+ @isTest
+ static void it_should_be_usable_after_construction() {
+ // Query builders should be usable as soon as it's constructed - it should be able to execute a query with some default values
+ ISObjectQueryBuilder opportunityQueryBuilder = new SObjectQueryBuilder(Schema.Opportunity.SObjectType);
+
+ Test.startTest();
+
+ List results = (List)opportunityQueryBuilder.getQueryResults();
+
+ Test.stopTest();
+ }
+
+ @isTest
+ static void it_should_cache_results() {
+ ISObjectQueryBuilder opportunityQueryBuilder = new SObjectQueryBuilder(Schema.Opportunity.SObjectType);
+ opportunityQueryBuilder.cacheResults();
+
+ Test.startTest();
+
+ System.assertEquals(0, Limits.getQueries());
+ for(Integer i = 0; i < 10; i++) {
+ System.debug(opportunityQueryBuilder.getQueryResults());
+ }
+
+ System.assertEquals(1, Limits.getQueries());
+
+ Test.stopTest();
+ }
+
+ @isTest
+ static void it_should_add_a_list_of_fields() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List fields = new List{Schema.Contact.Id, Schema.Contact.AccountId, Schema.Contact.CreatedDate};
+ List expectedQueryFieldStrings = buildExpectedQueryFieldStrings(fields);
+ expectedQueryFieldStrings.sort();
+
+ Test.startTest();
+ SObjectQueryBuilder query = (SObjectQueryBuilder)new SObjectQueryBuilder(sobjectType);
+ query.addFields(convertToQueryFields(fields));
+ Test.stopTest();
+
+ Set queryFieldStringSet = getQueryQueryFieldStringSet(query.getQuery());
+
+ // Verify that only our expected field name strings are included in the query
+ //TODO fix, broken because common fields are always added
+ //System.assertEquals(expectedQueryFieldStrings.size(), queryFieldStringSet.size());
+ for(String expectedQueryFieldString : expectedQueryFieldStrings) {
+ System.assert(queryFieldStringSet.contains(expectedQueryFieldString), expectedQueryFieldString + queryFieldStringSet);
+ }
+
+ // Execute the query to make sure it's executable
+ query.getQueryResults();
+ }
+
+ @isTest
+ static void it_should_add_all_fields() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List expectedQueryFieldStrings = buildExpectedQueryFieldStrings(sobjectType.getDescribe().fields.getMap().values());
+
+ Test.startTest();
+ SObjectQueryBuilder query = (SObjectQueryBuilder)new SObjectQueryBuilder(sobjectType);
+ query.addAllFields();
+ Test.stopTest();
+
+ Set queryFieldStringSet = getQueryQueryFieldStringSet(query.getQuery());
+
+ // Verify that only our expected field name strings are included in the query
+ //TODO fix, broken because common fields are always added
+ //System.assertEquals(expectedQueryFieldStrings.size(), queryFieldStringSet.size());
+ for(String expectedQueryFieldString : expectedQueryFieldStrings) {
+ System.assert(queryFieldStringSet.contains(expectedQueryFieldString), expectedQueryFieldString + queryFieldStringSet);
+ }
+
+ // Execute the query to make sure it's executable
+ query.getQueryResults();
+ }
+
+ @isTest
+ static void it_should_add_all_standard_fields() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List standardFields = new List();
+ for(Schema.SObjectField field : sobjectType.getDescribe().fields.getMap().values()) {
+ if(field.getDescribe().isCustom()) continue;
+
+ standardFields.add(field);
+ }
+ List expectedQueryFieldStrings = buildExpectedQueryFieldStrings(standardFields);
+
+ Test.startTest();
+ SObjectQueryBuilder query = (SObjectQueryBuilder)new SObjectQueryBuilder(sobjectType);
+ query.addAllStandardFields();
+ Test.stopTest();
+
+ Set queryFieldStringSet = getQueryQueryFieldStringSet(query.getQuery());
+
+ // Verify that only our expected field name strings are included in the query
+ //TODO fix, broken because common fields are always added
+ //System.assertEquals(expectedQueryFieldStrings.size(), queryFieldStringSet.size());
+ for(String expectedQueryFieldString : expectedQueryFieldStrings) {
+ System.assert(queryFieldStringSet.contains(expectedQueryFieldString), expectedQueryFieldString + queryFieldStringSet);
+ }
+
+ // Execute the query to make sure it's executable
+ query.getQueryResults();
+ }
+
+ @isTest
+ static void it_should_add_all_custom_fields() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List customFields = new List();
+ for(Schema.SObjectField field : sobjectType.getDescribe().fields.getMap().values()) {
+ if(!field.getDescribe().isCustom()) continue;
+
+ customFields.add(field);
+ }
+ List expectedQueryFieldStrings = buildExpectedQueryFieldStrings(customFields);
+
+ Test.startTest();
+ SObjectQueryBuilder query = (SObjectQueryBuilder)new SObjectQueryBuilder(sobjectType);
+ query.addAllCustomFields();
+ Test.stopTest();
+
+ Set queryFieldStringSet = getQueryQueryFieldStringSet(query.getQuery());
+
+ // Verify that only our expected field name strings are included in the query
+ //TODO fix, broken because common fields are always added
+ //System.assertEquals(expectedQueryFieldStrings.size(), queryFieldStringSet.size());
+ for(String expectedQueryFieldString : expectedQueryFieldStrings) {
+ System.assert(queryFieldStringSet.contains(expectedQueryFieldString), expectedQueryFieldString + queryFieldStringSet);
+ }
+
+ // Execute the query to make sure it's executable
+ query.getQueryResults();
+ }
+
+ @isTest
+ static void it_should_add_all_readable_fields() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List standardFields = new List();
+ for(Schema.SObjectField field : sobjectType.getDescribe().fields.getMap().values()) {
+ if(!field.getDescribe().isAccessible()) continue;
+
+ standardFields.add(field);
+ }
+ List expectedQueryFieldStrings = buildExpectedQueryFieldStrings(standardFields);
+
+ Test.startTest();
+ SObjectQueryBuilder query = (SObjectQueryBuilder)new SObjectQueryBuilder(sobjectType);
+ query.addAllReadableFields();
+ Test.stopTest();
+
+ Set queryFieldStringSet = getQueryQueryFieldStringSet(query.getQuery());
+
+ // Verify that only our expected field name strings are included in the query
+ System.assertEquals(expectedQueryFieldStrings.size(), queryFieldStringSet.size());
+ for(String expectedQueryFieldString : expectedQueryFieldStrings) {
+ System.assert(queryFieldStringSet.contains(expectedQueryFieldString), expectedQueryFieldString + queryFieldStringSet);
+ }
+
+ // Execute the query to make sure it's executable
+ query.getQueryResults();
+ }
+
+ @isTest
+ static void it_should_add_all_editable_fields() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List standardFields = new List();
+ for(Schema.SObjectField field : sobjectType.getDescribe().fields.getMap().values()) {
+ if(!field.getDescribe().isUpdateable()) continue;
+
+ standardFields.add(field);
+ }
+ List expectedQueryFieldStrings = buildExpectedQueryFieldStrings(standardFields);
+
+ Test.startTest();
+ SObjectQueryBuilder query = (SObjectQueryBuilder)new SObjectQueryBuilder(sobjectType);
+ query.addAllEditableFields();
+ Test.stopTest();
+
+ Set queryFieldStringSet = getQueryQueryFieldStringSet(query.getQuery());
+
+ // Verify that only our expected field name strings are included in the query
+ //TODO fix, broken because common fields are always added
+ //System.assertEquals(expectedQueryFieldStrings.size(), queryFieldStringSet.size());
+ for(String expectedQueryFieldString : expectedQueryFieldStrings) {
+ System.assert(queryFieldStringSet.contains(expectedQueryFieldString), expectedQueryFieldString + queryFieldStringSet);
+ }
+
+ // Execute the query to make sure it's executable
+ query.getQueryResults();
+ }
+
+ @isTest
+ static void it_should_order_by_field() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List fields = new List{Schema.Contact.CreatedDate};
+
+ Test.startTest();
+ SObjectQueryBuilder query = (SObjectQueryBuilder)new SObjectQueryBuilder(sobjectType).addFields(convertToQueryFields(fields));
+ query.orderBy(new QueryField(Schema.Contact.CreatedDate));
+ Test.stopTest();
+
+ String orderByString = getFirstLineThatStartsWith(query.getQuery(), 'ORDER BY');
+ System.assert(orderByString.contains(Contact.CreatedDate.getDescribe().getName()), orderByString);
+
+ // Execute the query to make sure it's executable
+ query.getQueryResults();
+ }
+
+ @isTest
+ static void it_should_order_by_field_ascending() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List fields = new List{Schema.Contact.CreatedDate};
+
+ Test.startTest();
+ SObjectQueryBuilder query = (SObjectQueryBuilder)new SObjectQueryBuilder(sobjectType).addFields(convertToQueryFields(fields));
+ query.orderBy(new QueryField(Schema.Contact.CreatedDate), QuerySortOrder.ASCENDING);
+ Test.stopTest();
+
+ String orderByString = getFirstLineThatStartsWith(query.getQuery(), 'ORDER BY');
+ System.assert(orderByString.contains(Contact.CreatedDate.getDescribe().getName() + ' ASC'), orderByString);
+
+ // Execute the query to make sure it's executable
+ query.getQueryResults();
+ }
+
+ @isTest
+ static void it_should_order_by_field_descending() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List fields = new List{Schema.Contact.CreatedDate};
+
+ Test.startTest();
+ SObjectQueryBuilder query = (SObjectQueryBuilder)new SObjectQueryBuilder(sobjectType).addFields(convertToQueryFields(fields));
+ query.orderBy(new QueryField(Schema.Contact.CreatedDate), QuerySortOrder.DESCENDING);
+ Test.stopTest();
+
+ String orderByString = getFirstLineThatStartsWith(query.getQuery(), 'ORDER BY');
+ System.assert(orderByString.contains(Contact.CreatedDate.getDescribe().getName() + ' DESC'), orderByString);
+
+ // Execute the query to make sure it's executable
+ query.getQueryResults();
+ }
+
+ @isTest
+ static void it_should_order_by_field_ascending_nulls_first() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List fields = new List{Schema.Contact.FirstName};
+
+ Test.startTest();
+ SObjectQueryBuilder query = (SObjectQueryBuilder)new SObjectQueryBuilder(sobjectType).addFields(convertToQueryFields(fields));
+ query.orderBy(new QueryField(Schema.Contact.FirstName), QuerySortOrder.ASCENDING, QueryNullSortOrder.FIRST);
+ Test.stopTest();
+
+ String orderByString = getFirstLineThatStartsWith(query.getQuery(), 'ORDER BY');
+ System.assert(orderByString.contains(Contact.FirstName.getDescribe().getName() + ' ASC NULLS FIRST'), orderByString);
+
+ // Execute the query to make sure it's executable
+ query.getQueryResults();
+ }
+
+ @isTest
+ static void it_should_order_by_field_descending_nulls_first() {
+ Schema.SObjectType sobjectType = Schema.Contact.SObjectType;
+ List fields = new List