diff --git a/force-app/main/default/classes/SOQL.cls b/force-app/main/default/classes/SOQL.cls index e95a275..8549a1d 100644 --- a/force-app/main/default/classes/SOQL.cls +++ b/force-app/main/default/classes/SOQL.cls @@ -14,30 +14,6 @@ **/ @SuppressWarnings('PMD.ExcessivePublicCount, PMD.ExcessiveClassLength, PMD.CyclomaticComplexity, PMD.CognitiveComplexity, PMD.PropertyNamingConventions, PMD.FieldDeclarationsShouldBeAtStart, PMD.ApexDoc, PMD.ExcessiveParameterList') public virtual inherited sharing class SOQL implements Queryable { - public static SubQuery SubQuery { - get { - return new SoqlSubQuery(); - } - } - - public static FilterGroup FilterGroup { - get { - return new SoqlFilterGroup(); - } - } - - public static Filter Filter { - get { - return new SoqlFilter(); - } - } - - public static InnerJoin InnerJoin { - get { - return new SoqlJoinQuery(); - } - } - public interface Selector { Queryable query(); } @@ -115,6 +91,12 @@ public virtual inherited sharing class SOQL implements Queryable { Queryable groupByRollup(String relationshipName, SObjectField field); Queryable groupByCube(SObjectField field); Queryable groupByCube(String relationshipName, SObjectField field); + // HAVING + Queryable have(HavingFilterGroup havingFilterGroup); + Queryable have(HavingFilter havingFilter); + Queryable have(String havingConditions); + Queryable havingConditionLogic(String havingConditionsOrder); + Queryable anyHavingConditionMatching(); // ORDER BY Queryable orderBy(String field); Queryable orderBy(String field, String direction); @@ -225,18 +207,18 @@ public virtual inherited sharing class SOQL implements Queryable { Filter equal(Object value); Filter notEqual(Object value); Filter lessThan(Object value); - Filter greaterThan(Object value); Filter lessOrEqual(Object value); + Filter greaterThan(Object value); Filter greaterOrEqual(Object value); Filter containsSome(Iterable values); Filter contains(String value); + Filter contains(String prefix, String value, String suffix); Filter notContains(String value); + Filter notContains(String prefix, String value, String suffix); Filter endsWith(String value); Filter notEndsWith(String value); Filter startsWith(String value); Filter notStartsWith(String value); - Filter contains(String prefix, String value, String suffix); - Filter notContains(String prefix, String value, String suffix); Filter isIn(Iterable iterable); Filter isIn(InnerJoin joinQuery); Filter notIn(Iterable iterable); @@ -261,6 +243,73 @@ public virtual inherited sharing class SOQL implements Queryable { InnerJoin whereAre(Filter filter); } + public interface HavingFilterGroup { + // ADD CONDITION + HavingFilterGroup add(HavingFilterGroup havingFilterGroup); + HavingFilterGroup add(HavingFilter havingFilter); + HavingFilterGroup add(String dynamicHaving); + // ORDER + HavingFilterGroup anyConditionMatching(); + HavingFilterGroup conditionLogic(String order); + } + + public interface HavingFilter { + // FIELDS + HavingFilter with(SObjectField field); + HavingFilter with(String field); + HavingFilter count(SObjectField field); + HavingFilter avg(SObjectField field); + HavingFilter countDistinct(SObjectField field); + HavingFilter min(SObjectField field); + HavingFilter max(SObjectField field); + HavingFilter sum(SObjectField field); + // COMPERATORS + HavingFilter isNull(); + HavingFilter isNotNull(); + HavingFilter isTrue(); + HavingFilter isFalse(); + HavingFilter equal(Object value); + HavingFilter notEqual(Object value); + HavingFilter lessThan(Object value); + HavingFilter lessOrEqual(Object value); + HavingFilter greaterThan(Object value); + HavingFilter greaterOrEqual(Object value); + HavingFilter contains(String value); + HavingFilter contains(String prefix, String value, String suffix); + HavingFilter notContains(String value); + HavingFilter notContains(String prefix, String value, String suffix); + HavingFilter startsWith(String value); + HavingFilter notStartsWith(String value); + HavingFilter endsWith(String value); + HavingFilter notEndsWith(String value); + HavingFilter isIn(Iterable iterable); + HavingFilter notIn(Iterable iterable); + } + + public static SubQuery SubQuery { + get { return new SoqlSubQuery(); } + } + + public static FilterGroup FilterGroup { + get { return new SoqlFilterGroup(); } + } + + public static Filter Filter { + get { return new SoqlFilter(); } + } + + public static InnerJoin InnerJoin { + get { return new SoqlJoinQuery(); } + } + + public static HavingFilterGroup HavingFilterGroup { + get { return new SoqlHavingFilterGroup(); } + } + + public static HavingFilter HavingFilter { + get { return new SoqlHavingFilter(); } + } + @TestVisible private static void setMock(String mockId, SObject record) { setMock(mockId, new List{ record }); @@ -276,7 +325,7 @@ public virtual inherited sharing class SOQL implements Queryable { mock.setCountMock(mockId, amount); } - // Config + // Implementation private static Mock mock = new Mock(); private static Binder binder = new Binder(); @@ -603,6 +652,31 @@ public virtual inherited sharing class SOQL implements Queryable { return this; } + public SOQL have(HavingFilterGroup havingFilterGroup) { + builder.havingClause.add(havingFilterGroup); + return this; + } + + public SOQL have(HavingFilter havingFilter) { + builder.havingClause.add(havingFilter); + return this; + } + + public SOQL have(String havingConditions) { + builder.havingClause.add(havingConditions); + return this; + } + + public SOQL havingConditionLogic(String havingConditionsOrder) { + builder.havingClause.conditionLogic(havingConditionsOrder); + return this; + } + + public SOQL anyHavingConditionMatching() { + builder.havingClause.anyConditionMatching(); + return this; + } + public SOQL orderBy(String field) { builder.orderBys.newOrderBy().with(field); return this; @@ -803,12 +877,12 @@ public virtual inherited sharing class SOQL implements Queryable { return whereAre(Filter.recordType().equal(recordTypeDeveloperName)); } - public interface QueryClause { + private interface QueryClause { String toString(); } private class QueryBuilder implements QueryClause { - private List clauses = new QueryClause[10]; + private List clauses = new QueryClause[11]; public QueryBuilder(String ofObject) { clauses.set(0, new SoqlFields(ofObject)); @@ -816,77 +890,54 @@ public virtual inherited sharing class SOQL implements Queryable { } public SoqlFields fields { - get { - return (SoqlFields) clauses[0]; - } + get { return (SoqlFields) clauses[0]; } } public SoqlSubQueries subQueries { - get { - createWhenNotExist(1, new SoqlSubQueries()); - return (SoqlSubQueries) clauses[1]; - } + get { return (SoqlSubQueries) getQueryClause(1, SoqlSubQueries.class); } } public SoqlScope scope { - get { - createWhenNotExist(3, new SoqlScope()); - return (SoqlScope) clauses[3]; - } + get { return (SoqlScope) getQueryClause(3, SoqlScope.class); } } public MainFilterGroup conditions { - get { - createWhenNotExist(4, new MainFilterGroup()); - return (MainFilterGroup) clauses[4]; - } + get { return (MainFilterGroup) getQueryClause(4, MainFilterGroup.class); } } public SoqlGroupBy groupBy { - get { - createWhenNotExist(5, new SoqlGroupBy()); - return (SoqlGroupBy) clauses[5]; - } + get { return (SoqlGroupBy) getQueryClause(5, SoqlGroupBy.class); } + } + + public MainHavingGroup havingClause { + get { return (MainHavingGroup) getQueryClause(6, MainHavingGroup.class); } } public SoqlOrderBy latestOrderBy { - get { - return orderBys.latestOrderBy(); - } + get { return orderBys.latestOrderBy(); } } public SoqlOrderBys orderBys { - get { - createWhenNotExist(6, new SoqlOrderBys()); - return (SoqlOrderBys) clauses[6]; - } + get { return (SoqlOrderBys) getQueryClause(7, SoqlOrderBys.class); } } public SoqlLimit soqlLimit { - get { - createWhenNotExist(7, new SoqlLimit()); - return (SoqlLimit) clauses[7]; - } + get { return (SoqlLimit) getQueryClause(8, SoqlLimit.class); } } public SoqlOffset soqlOffset { - get { - createWhenNotExist(8, new SoqlOffset()); - return (SoqlOffset) clauses[8]; - } + get { return (SoqlOffset) getQueryClause(9, SoqlOffset.class); } } public SoqlFor soqlFor { - get { - createWhenNotExist(9, new SoqlFor()); - return (SoqlFor) clauses[9]; - } + get { return (SoqlFor) getQueryClause(10, SoqlFor.class); } } - public void createWhenNotExist(Integer position, QueryClause clause) { + private QueryClause getQueryClause(Integer position, System.Type queryClause) { if (clauses[position] == null) { - clauses.set(position, clause); + clauses.set(position, (QueryClause) queryClause.newInstance()); } + return clauses[position]; } public override String toString() { @@ -1256,6 +1307,7 @@ public virtual inherited sharing class SOQL implements Queryable { } private interface FilterClause { + String toString(); Boolean hasValue(); } @@ -1265,15 +1317,15 @@ public virtual inherited sharing class SOQL implements Queryable { private String connector = 'AND'; public FilterGroup add(FilterGroup filterGroup) { - return add(new QFilterGroupAdapter(filterGroup)); + return add(new FilterGroupAdapter(filterGroup)); } public FilterGroup add(Filter filter) { - return add(new QFilterAdapter(filter)); + return add(new FilterAdapter(filter)); } public FilterGroup add(String dynamicCondition) { - return add(new QStringAdapter(dynamicCondition)); + return add(new StringConditionAdapter(dynamicCondition)); } public FilterGroup add(FilterClause condition) { @@ -1316,7 +1368,7 @@ public virtual inherited sharing class SOQL implements Queryable { String orderWithSpecialCharacters = getConditionsLogic(); for (Integer i = 0; i < queryConditions.size(); i++) { - orderWithSpecialCharacters = orderWithSpecialCharacters.replaceFirst(String.valueOf(i + 1), '{' + i + '}'); + orderWithSpecialCharacters = orderWithSpecialCharacters.replaceAll('\\b' + String.valueOf(i + 1) + '\\b', '{' + i + '}'); } return orderWithSpecialCharacters; // e.g ({0} AND ({1} AND {2})) @@ -1347,10 +1399,10 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QFilterGroupAdapter implements FilterClause { + private class FilterGroupAdapter implements FilterClause { private FilterGroup filterGroup; - public QFilterGroupAdapter(FilterGroup filterGroup) { + public FilterGroupAdapter(FilterGroup filterGroup) { this.filterGroup = filterGroup; } @@ -1363,10 +1415,10 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QFilterAdapter implements FilterClause { + private class FilterAdapter implements FilterClause { private Filter filter; - public QFilterAdapter(Filter filter) { + public FilterAdapter(Filter filter) { this.filter = filter; } @@ -1379,10 +1431,10 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QStringAdapter implements FilterClause { + private class StringConditionAdapter implements FilterClause { private String conditionString; - public QStringAdapter(String dynamicCondition) { + public StringConditionAdapter(String dynamicCondition) { conditionString = dynamicCondition; } @@ -1658,7 +1710,7 @@ public virtual inherited sharing class SOQL implements Queryable { public void setGroupByFunction(String newGroupByFunction) { if (String.isNotEmpty(groupByFunction) && groupByFunction != newGroupByFunction) { - throw new QueryException('You cant use GROUP BY, GROUP BY ROLLUP and GROUP BY CUBE in the same query.'); + throw new QueryException('You can\'t use GROUP BY, GROUP BY ROLLUP and GROUP BY CUBE in the same query.'); } this.groupByFunction = newGroupByFunction; } @@ -1691,6 +1743,271 @@ public virtual inherited sharing class SOQL implements Queryable { } } + private virtual class SoqlHavingFilterGroup implements HavingFilterGroup, HavingFilterClause { + private List havingConditions = new List(); + private String customOrder; + private String connector = 'AND'; + + public HavingFilterGroup add(HavingFilterGroup filterGroup) { + return add(new HavingFilterGroupAdapter(filterGroup)); + } + + public HavingFilterGroup add(HavingFilter filter) { + return add(new HavingFilterAdapter(filter)); + } + + public HavingFilterGroup add(String filter) { + return add(new HavingStringAdapter(filter)); + } + + public HavingFilterGroup add(HavingFilterClause condition) { + havingConditions.add(condition); + return this; + } + + public HavingFilterGroup anyConditionMatching() { + connector = 'OR'; + return this; + } + + public HavingFilterGroup conditionLogic(String order) { + customOrder = order; + return this; + } + + public virtual override String toString() { + return '(' + buildNested() + ')'; + } + + public String buildNested() { + return String.format(getOrderWithSpecialCharacters(), havingConditions); + } + + private String getOrderWithSpecialCharacters() { + String orderWithSpecialCharacters = getConditionsLogic(); + + for (Integer i = 0; i < havingConditions.size(); i++) { + orderWithSpecialCharacters = orderWithSpecialCharacters.replaceFirst(String.valueOf(i + 1), '{' + i + '}'); + } + + return orderWithSpecialCharacters; // e.g ({0} AND ({1} AND {2})) + } + + private String getConditionsLogic() { + if (String.isNotEmpty(customOrder)) { + return customOrder; + } + + List defaultOrder = new List(); + + for (Integer i = 0; i < havingConditions.size(); i++) { + defaultOrder.add(String.valueOf(i + 1)); + } + + return String.join(defaultOrder, ' ' + connector + ' '); // e.g (0 AND 1 AND 2) + } + } + + private class MainHavingGroup extends SoqlHavingFilterGroup implements QueryClause { + public override String toString() { + return 'HAVING ' + buildNested(); + } + } + + private interface HavingFilterClause { + String toString(); + } + + private class HavingFilterGroupAdapter implements HavingFilterClause { + private HavingFilterGroup havingFilterGroup; + + public HavingFilterGroupAdapter(HavingFilterGroup havingFilterGroup) { + this.havingFilterGroup = havingFilterGroup; + } + + public override String toString() { + return havingFilterGroup.toString(); + } + } + + private class HavingFilterAdapter implements HavingFilterClause { + private HavingFilter havingFilter; + + public HavingFilterAdapter(HavingFilter havingFilter) { + this.havingFilter = havingFilter; + } + + public override String toString() { + return havingFilter.toString(); + } + } + + private class HavingStringAdapter implements HavingFilterClause { + private String condition; + + public HavingStringAdapter(String condition) { + this.condition = condition; + } + + public override String toString() { + return condition.toString(); + } + } + + private class SoqlHavingFilter implements HavingFilter, HavingFilterClause { + private String field; + private String comperator; + private Object value; + private String wrapper = '{0}'; + + public HavingFilter with(SObjectField field) { + return with(field.getDescribe().getName()); + } + + public HavingFilter with(String field) { + this.field = field; + return this; + } + + public HavingFilter count(SObjectField field) { + return withAggregateFunction('COUNT', field); + } + + public HavingFilter avg(SObjectField field) { + return withAggregateFunction('AVG', field); + } + + public HavingFilter countDistinct(SObjectField field) { + return withAggregateFunction('COUNT_DISTINCT', field); + } + + public HavingFilter min(SObjectField field) { + return withAggregateFunction('MIN', field); + } + + public HavingFilter max(SObjectField field) { + return withAggregateFunction('MAX', field); + } + + public HavingFilter sum(SObjectField field) { + return withAggregateFunction('SUM', field); + } + + private HavingFilter withAggregateFunction(String aggregateFunction, SObjectField field) { + return withAggregateFunction(aggregateFunction, field.getDescribe().getName()); + } + + private HavingFilter withAggregateFunction(String aggregateFunction, String field) { + this.field = aggregateFunction + '(' + field + ')'; + return this; + } + + public HavingFilter isNull() { + return equal(null); + } + + public HavingFilter isNotNull() { + return notEqual(null); + } + + public HavingFilter isTrue() { + return equal(true); + } + + public HavingFilter isFalse() { + return equal(false); + } + + public HavingFilter equal(Object value) { + return set('=', value); + } + + public HavingFilter notEqual(Object value) { + return set('!=', value); + } + + public HavingFilter lessThan(Object value) { + return set('<', value); + } + + public HavingFilter greaterThan(Object value) { + return set('>', value); + } + + public HavingFilter lessOrEqual(Object value) { + return set('<=', value); + } + + public HavingFilter greaterOrEqual(Object value) { + return set('>=', value); + } + + public HavingFilter contains(String value) { + return contains('%', formattedString(value), '%'); + } + + public HavingFilter notContains(String value) { + return notLike().contains(value); + } + + public HavingFilter endsWith(String value) { + return contains('%', formattedString(value), ''); + } + + public HavingFilter notEndsWith(String value) { + return notLike().endsWith(value); + } + + public HavingFilter startsWith(String value) { + return contains('', formattedString(value), '%'); + } + + public HavingFilter notStartsWith(String value) { + return notLike().startsWith(value); + } + + public HavingFilter contains(String prefix, String value, String suffix) { + return set('LIKE', prefix + formattedString(value) + suffix); + } + + public HavingFilter notContains(String prefix, String value, String suffix) { + return notLike().contains(prefix, value, suffix); + } + + public HavingFilter isIn(Iterable iterable) { + return set('IN', iterable, '\', \''); + } + + public HavingFilter notIn(Iterable iterable) { + return set('NOT IN', iterable, '\', \''); + } + + private String formattedString(String value) { + return value ?? value?.trim(); + } + + private HavingFilter notLike() { + this.wrapper = '(NOT {0})'; + return this; + } + + public HavingFilter set(String operator, Iterable iterable, String separator) { + this.comperator = operator; + this.value = '(\'' + String.join(iterable, separator) + '\')'; + return this; + } + + private HavingFilter set(String comperator, Object value) { + this.comperator = comperator; + this.value = value instanceof String ? '\'' + value + '\'' : value; + return this; + } + + public override String toString() { + return String.format(wrapper, new List{ field + ' ' + comperator + ' ' + value }); + } + } + private class SoqlOrderBy implements QueryClause { private String orderField; private String sortingOrder = 'ASC'; @@ -1996,14 +2313,14 @@ public virtual inherited sharing class SOQL implements Queryable { return Database.queryWithBinds(query, binding, accessLevel); } - public Database.QueryLocator toQueryLocator(String query, Map binding, AccessLevel accessLevel) { - return Database.getQueryLocatorWithBinds(query, binding, accessLevel); - } - public Integer toInteger(String query, Map binding, AccessLevel accessLevel) { return Database.countQueryWithBinds(query, binding, accessLevel); } + public Database.QueryLocator toQueryLocator(String query, Map binding, AccessLevel accessLevel) { + return Database.getQueryLocatorWithBinds(query, binding, accessLevel); + } + public Database.Cursor toCursor(String query, Map binding, AccessLevel accessLevel) { return Database.getCursorWithBinds(query, binding, accessLevel); } @@ -2014,14 +2331,14 @@ public virtual inherited sharing class SOQL implements Queryable { return Database.queryWithBinds(query, binding, accessLevel); } - public Database.QueryLocator toQueryLocator(String query, Map binding, AccessLevel accessLevel) { - return Database.getQueryLocatorWithBinds(query, binding, accessLevel); - } - public Integer toInteger(String query, Map binding, AccessLevel accessLevel) { return Database.countQueryWithBinds(query, binding, accessLevel); } + public Database.QueryLocator toQueryLocator(String query, Map binding, AccessLevel accessLevel) { + return Database.getQueryLocatorWithBinds(query, binding, accessLevel); + } + public Database.Cursor toCursor(String query, Map binding, AccessLevel accessLevel) { return Database.getCursorWithBinds(query, binding, accessLevel); } diff --git a/force-app/main/default/classes/SOQL_Test.cls b/force-app/main/default/classes/SOQL_Test.cls index cdee63d..3d9a31f 100644 --- a/force-app/main/default/classes/SOQL_Test.cls +++ b/force-app/main/default/classes/SOQL_Test.cls @@ -53,7 +53,7 @@ private class SOQL_Test { } @IsTest - static void countField() { + static void countSObjectField() { // Test String soql = SOQL.of(Opportunity.SObjectType) .count(Opportunity.Id) @@ -65,7 +65,7 @@ private class SOQL_Test { } @IsTest - static void countFieldWithDefaultFields() { + static void countSObjectFieldWithDefaultFields() { // Test String soql = SOQL.of(Opportunity.SObjectType) .with(Opportunity.LeadSource) @@ -397,7 +397,7 @@ private class SOQL_Test { } @IsTest - static void withField() { + static void withSObjectField() { // Test String soql = SOQL.of(Account.SObjectType) .with(Account.Name) @@ -409,7 +409,7 @@ private class SOQL_Test { } @IsTest - static void withTwoFields() { + static void withTwoSObjectFields() { // Test String soql = SOQL.of(Account.SObjectType) .with(Account.Name, Account.BillingCity) @@ -420,7 +420,7 @@ private class SOQL_Test { } @IsTest - static void withThreeFields() { + static void withThreeSObjectFields() { // Test String soql = SOQL.of(Account.SObjectType) .with(Account.Id, Account.Name, Account.BillingCity) @@ -431,7 +431,7 @@ private class SOQL_Test { } @IsTest - static void withFourFields() { + static void withFourSObjectFields() { // Test String soql = SOQL.of(Account.SObjectType) .with(Account.Id, Account.Name, Account.BillingCity, Account.AccountNumber) @@ -442,7 +442,7 @@ private class SOQL_Test { } @IsTest - static void withFiveFields() { + static void withFiveSObjectFields() { // Test String soql = SOQL.of(Account.SObjectType) .with(Account.Id, Account.Name, Account.BillingCity, Account.AccountNumber, Account.AccountSource) @@ -453,7 +453,7 @@ private class SOQL_Test { } @IsTest - static void withListOfFields() { + static void withListOfSObjectFields() { // Test String soql = SOQL.of(Account.SObjectType) .with(new List{ @@ -727,9 +727,7 @@ private class SOQL_Test { .with(Account.Name) .with(SOQL.SubQuery.of('Contacts') .with(Contact.LastName) - .with(SOQL.SubQuery.of('Assets') - .with(Asset.AssetLevel) - ) + .with(SOQL.SubQuery.of('Assets').with(Asset.AssetLevel)) ) .toString(); @@ -965,7 +963,7 @@ private class SOQL_Test { } @IsTest - static void id() { + static void filterId() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.id().isNotNull()); @@ -979,7 +977,7 @@ private class SOQL_Test { } @IsTest - static void recordType() { + static void filterRecordType() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.recordType().isNotNull()); @@ -993,7 +991,7 @@ private class SOQL_Test { } @IsTest - static void name() { + static void filterName() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.name().isNotNull()); @@ -1007,7 +1005,7 @@ private class SOQL_Test { } @IsTest - static void withRelatedFieldFilter() { + static void filterWithRelatedField() { // Test SOQL builder = SOQL.of(Contact.SObjectType) .whereAre(SOQL.Filter.with('Account', Account.Name).equal('Test')); @@ -1021,7 +1019,7 @@ private class SOQL_Test { } @IsTest - static void equalString() { + static void filterEqualString() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).equal('Test')); @@ -1035,7 +1033,7 @@ private class SOQL_Test { } @IsTest - static void notEqualString() { + static void filterNotEqualString() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).notEqual('Test')); @@ -1049,7 +1047,7 @@ private class SOQL_Test { } @IsTest - static void lessThan() { + static void filterLessThan() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.NumberOfEmployees).lessThan(10)); @@ -1063,7 +1061,7 @@ private class SOQL_Test { } @IsTest - static void greaterThan() { + static void filterGreaterThan() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.NumberOfEmployees).greaterThan(10)); @@ -1077,7 +1075,7 @@ private class SOQL_Test { } @IsTest - static void lessOrEqual() { + static void filterLessOrEqual() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.NumberOfEmployees).lessOrEqual(10)); @@ -1091,7 +1089,7 @@ private class SOQL_Test { } @IsTest - static void greaterOrEqual() { + static void filterGreaterOrEqual() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.NumberOfEmployees).greaterOrEqual(10)); @@ -1105,7 +1103,7 @@ private class SOQL_Test { } @IsTest - static void contains() { + static void filterContains() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).contains('Test')); @@ -1119,7 +1117,7 @@ private class SOQL_Test { } @IsTest - static void containsNull() { + static void filterContainsNull() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).contains(null)); @@ -1133,7 +1131,7 @@ private class SOQL_Test { } @IsTest - static void notContains() { + static void filterNotContains() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).notContains('Test')); @@ -1147,7 +1145,7 @@ private class SOQL_Test { } @IsTest - static void containsValues() { + static void filterContainsValues() { // Setup List names = new List{ 'Acc', 'My' }; @@ -1164,7 +1162,7 @@ private class SOQL_Test { } @IsTest - static void endsWith() { + static void filterEndsWith() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).endsWith('Test')); @@ -1178,7 +1176,7 @@ private class SOQL_Test { } @IsTest - static void notEndsWith() { + static void filterNotEndsWith() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).notEndsWith('Test')); @@ -1192,7 +1190,7 @@ private class SOQL_Test { } @IsTest - static void startsWith() { + static void filterStartsWith() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).startsWith('Test')); @@ -1206,7 +1204,7 @@ private class SOQL_Test { } @IsTest - static void notStartsWith() { + static void filterNotStartsWith() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).notStartsWith('Test')); @@ -1220,7 +1218,7 @@ private class SOQL_Test { } @IsTest - static void customContains() { + static void filterCustomContains() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).contains('_', 'Test', '%')); @@ -1234,7 +1232,7 @@ private class SOQL_Test { } @IsTest - static void customNotContains() { + static void filterCustomNotContains() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).notContains('_', 'Test', '%')); @@ -1248,7 +1246,7 @@ private class SOQL_Test { } @IsTest - static void isInSet() { + static void filterIsInSet() { // Setup Set names = new Set{ 'Test 1', 'Test 2' }; @@ -1265,7 +1263,7 @@ private class SOQL_Test { } @IsTest - static void isInList() { + static void filterIsInList() { // Setup List names = new List{ 'Test 1', 'Test 2' }; @@ -1282,7 +1280,7 @@ private class SOQL_Test { } @IsTest - static void notInSet() { + static void filterNotInSet() { // Setup Set names = new Set{ 'Test 1', 'Test 2' }; @@ -1299,7 +1297,7 @@ private class SOQL_Test { } @IsTest - static void notInList() { + static void filterNotInList() { // Setup List names = new List{ 'Test 1', 'Test 2' }; @@ -1317,7 +1315,7 @@ private class SOQL_Test { } @IsTest - static void inlcudesAll() { + static void filterInlcudesAll() { // Setup List ratings = new List{ 'Hot', 'Warm' }; @@ -1332,7 +1330,7 @@ private class SOQL_Test { } @IsTest - static void inlcudesSome() { + static void filterInlcudesSome() { // Setup List ratings = new List{ 'Hot', 'Warm' }; @@ -1347,7 +1345,7 @@ private class SOQL_Test { } @IsTest - static void excludesAll() { + static void filterExcludesAll() { // Setup List ratings = new List{ 'Hot', 'Warm' }; @@ -1362,7 +1360,7 @@ private class SOQL_Test { } @IsTest - static void excludesSome() { + static void filterExcludesSome() { // Setup List ratings = new List{ 'Hot', 'Warm' }; @@ -1377,7 +1375,7 @@ private class SOQL_Test { } @IsTest - static void isNull() { + static void filterIsNull() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).isNull()); @@ -1391,7 +1389,7 @@ private class SOQL_Test { } @IsTest - static void isNotNull() { + static void filterIsNotNull() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Name).isNotNull()); @@ -1405,7 +1403,7 @@ private class SOQL_Test { } @IsTest - static void isTrue() { + static void filterIsTrue() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.IsDeleted).isTrue()); @@ -1419,7 +1417,7 @@ private class SOQL_Test { } @IsTest - static void isFalse() { + static void filterIsFalse() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.IsDeleted).isFalse()); @@ -1433,7 +1431,7 @@ private class SOQL_Test { } @IsTest - static void dateLiteral() { + static void filterDateLiteral() { // Test String soql = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.CreatedDate).greaterThan('LAST_N_QUARTERS:2').asDateLiteral()) @@ -1495,12 +1493,6 @@ private class SOQL_Test { @IsTest static void dynamicFiltersGroupOnSoqlInstance() { - // Setup - SOQL.FilterGroup filterGroup = SOQL.FilterGroup; - - filterGroup.add(SOQL.Filter.with(Account.Name).equal('Test')); - filterGroup.add(SOQL.Filter.with(Account.BillingCity).equal('Krakow')); - // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.Filter.with(Account.Industry).equal('IT')); @@ -1523,7 +1515,7 @@ private class SOQL_Test { } @IsTest - static void multipleFiltersGroups() { + static void multipleFilterGroups() { // Test SOQL builder = SOQL.of(Account.SObjectType) .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('1'))) @@ -1559,6 +1551,44 @@ private class SOQL_Test { Assert.areEqual('%11%', binding.get('v11')); } + @IsTest + static void duplicatedConditionsInConditionOrder() { + // Test + SOQL builder = SOQL.of(Account.SObjectType) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('1'))) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('2'))) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('3'))) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('4'))) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('5'))) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('6'))) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('7'))) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('8'))) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('9'))) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('10'))) + .whereAre(SOQL.FilterGroup.add(SOQL.Filter.with(Account.Name).contains('11'))) + .conditionLogic('(1 AND 2 AND 3 AND 4 AND 5 AND 5 AND 7 AND 8 AND 9 AND 10 AND 11) OR (11 AND 1 AND 10)'); + + // Verify + String soql = builder.toString(); + Assert.areEqual( + 'SELECT Id FROM Account WHERE ((Name LIKE :v1) AND (Name LIKE :v2) AND (Name LIKE :v3) AND (Name LIKE :v4) AND (Name LIKE :v5) AND (Name LIKE :v5) AND (Name LIKE :v7) AND (Name LIKE :v8) AND (Name LIKE :v9) AND (Name LIKE :v10) AND (Name LIKE :v11)) OR ((Name LIKE :v11) AND (Name LIKE :v1) AND (Name LIKE :v10))', + soql + ); + + Map binding = builder.binding(); + Assert.areEqual('%1%', binding.get('v1')); + Assert.areEqual('%2%', binding.get('v2')); + Assert.areEqual('%3%', binding.get('v3')); + Assert.areEqual('%4%', binding.get('v4')); + Assert.areEqual('%5%', binding.get('v5')); + Assert.areEqual('%6%', binding.get('v6')); + Assert.areEqual('%7%', binding.get('v7')); + Assert.areEqual('%8%', binding.get('v8')); + Assert.areEqual('%9%', binding.get('v9')); + Assert.areEqual('%10%', binding.get('v10')); + Assert.areEqual('%11%', binding.get('v11')); + } + @IsTest static void anyConditionMatchingForInnerGroup() { // Test @@ -1743,7 +1773,7 @@ private class SOQL_Test { } @IsTest - static void ignoreWhenFilter() { + static void filterIgnoreWhen() { // Setup String accountName = ''; @@ -1760,7 +1790,7 @@ private class SOQL_Test { } @IsTest - static void ignoreWhenFilterGroup() { + static void filterGroupIgnoreWhen() { // Setup Boolean isPartnerUser = false; @@ -1917,7 +1947,7 @@ private class SOQL_Test { // Verify Assert.areEqual( - 'You cant use GROUP BY, GROUP BY ROLLUP and GROUP BY CUBE in the same query.', + 'You can\'t use GROUP BY, GROUP BY ROLLUP and GROUP BY CUBE in the same query.', queryException.getMessage() ); } @@ -1935,6 +1965,635 @@ private class SOQL_Test { Assert.areEqual('SELECT LeadSource FROM Lead GROUP BY LeadSource', soql); } + @IsTest + static void havingFilterWithSObjectField() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY City HAVING City LIKE \'San%\'', soql); + } + + @IsTest + static void havingFilterWithStringField() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with('City').startsWith('San')) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY City HAVING City LIKE \'San%\'', soql); + } + + @IsTest + static void havingFilterCount() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource HAVING COUNT(Name) > 100', soql); + } + + @IsTest + static void havingFilterAvg() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .avg(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.avg(Lead.Name).greaterThan(100)) + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, AVG(Name) FROM Lead GROUP BY LeadSource HAVING AVG(Name) > 100', soql); + } + + @IsTest + static void havingFilterCountDistinct() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .countDistinct(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.countDistinct(Lead.Name).greaterThan(100)) + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT_DISTINCT(Name) FROM Lead GROUP BY LeadSource HAVING COUNT_DISTINCT(Name) > 100', soql); + } + + @IsTest + static void havingFilterMin() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .min(Lead.NumberOfEmployees) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.min(Lead.NumberOfEmployees).greaterThan(100)) + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, MIN(NumberOfEmployees) FROM Lead GROUP BY LeadSource HAVING MIN(NumberOfEmployees) > 100', soql); + } + + @IsTest + static void havingFilterMax() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .max(Lead.NumberOfEmployees) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.max(Lead.NumberOfEmployees).greaterThan(100)) + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, MAX(NumberOfEmployees) FROM Lead GROUP BY LeadSource HAVING MAX(NumberOfEmployees) > 100', soql); + } + + @IsTest + static void havingFilterSum() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).greaterThan(100)) + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, SUM(AnnualRevenue) FROM Lead GROUP BY LeadSource HAVING SUM(AnnualRevenue) > 100', soql); + } + + @IsTest + static void havingFilterIsNull() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.with(Lead.LeadSource).isNull()) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY LeadSource HAVING LeadSource = null', soql); + } + + @IsTest + static void havingFilterIsNotNull() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.with(Lead.LeadSource).isNotNull()) + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource FROM Lead GROUP BY LeadSource HAVING LeadSource != null', soql); + } + + @IsTest + static void havingFilterIsTrue() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.IsConverted) + .have(SOQL.HavingFilter.with(Lead.IsConverted).isTrue()) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY IsConverted HAVING IsConverted = true', soql); + } + + @IsTest + static void havingFilterIsFalse() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.IsConverted) + .have(SOQL.HavingFilter.with(Lead.IsConverted).isFalse()) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY IsConverted HAVING IsConverted = false', soql); + } + + @IsTest + static void havingFilterEqualString() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.with(Lead.LeadSource).equal('Web')) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY LeadSource HAVING LeadSource = \'Web\'', soql); + } + + @IsTest + static void havingFilterEqualInteger() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).equal(10000)) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY LeadSource HAVING SUM(AnnualRevenue) = 10000', soql); + } + + @IsTest + static void havingFilterNotEqualString() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.with(Lead.LeadSource).notEqual('Web')) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY LeadSource HAVING LeadSource != \'Web\'', soql); + } + + @IsTest + static void havingFilterNotEqualInteger() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).notEqual(10000)) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY LeadSource HAVING SUM(AnnualRevenue) != 10000', soql); + } + + @IsTest + static void havingFilterLessThan() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).lessThan(10000)) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY LeadSource HAVING SUM(AnnualRevenue) < 10000', soql); + } + + @IsTest + static void havingFilterGreaterThan() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).greaterThan(10000)) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY LeadSource HAVING SUM(AnnualRevenue) > 10000', soql); + } + + @IsTest + static void havingFilterLessOrEqual() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).lessOrEqual(10000)) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY LeadSource HAVING SUM(AnnualRevenue) <= 10000', soql); + } + + @IsTest + static void havingFilterGreaterOrEqual() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).greaterOrEqual(10000)) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Name) FROM Lead GROUP BY LeadSource HAVING SUM(AnnualRevenue) >= 10000', soql); + } + + @IsTest + static void havingFilterContains() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).contains('San')) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING City LIKE \'%San%\'', soql); + } + + @IsTest + static void havingFilterCustomContains() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).contains('_', 'San', '%')) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING City LIKE \'_San%\'', soql); + } + + @IsTest + static void havingFilterNotContains() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notContains('San')) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING (NOT City LIKE \'%San%\')', soql); + } + + @IsTest + static void havingFilterCustomNotContains() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notContains('_', 'San', '%')) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING (NOT City LIKE \'_San%\')', soql); + } + + @IsTest + static void havingFilterStartsWith() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING City LIKE \'San%\'', soql); + } + + @IsTest + static void havingFilterNotStartsWith() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notStartsWith('San')) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING (NOT City LIKE \'San%\')', soql); + } + + @IsTest + static void havingFilterEndsWith() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).endsWith('San')) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING City LIKE \'%San\'', soql); + } + + @IsTest + static void havingFilterNotEndsWith() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notEndsWith('San')) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING (NOT City LIKE \'%San\')', soql); + } + + @IsTest + static void havingFilterIsInSet() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).isIn(new Set{ 'San Francisco', 'Los Angeles' })) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING City IN (\'San Francisco\', \'Los Angeles\')', soql); + } + + @IsTest + static void havingFilterIsInList() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).isIn(new List{ 'San Francisco', 'Los Angeles' })) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING City IN (\'San Francisco\', \'Los Angeles\')', soql); + } + + @IsTest + static void havingFilterIsNotInSet() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notIn(new Set{ 'San Francisco', 'Los Angeles' })) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING City NOT IN (\'San Francisco\', \'Los Angeles\')', soql); + } + + @IsTest + static void havingFilterIsNotInList() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notIn(new List{ 'San Francisco', 'Los Angeles' })) + .toString(); + + // Verify + Assert.areEqual('SELECT SUM(AnnualRevenue) FROM Lead GROUP BY City HAVING City NOT IN (\'San Francisco\', \'Los Angeles\')', soql); + } + + @IsTest + static void havingFilterGroup() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .add(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + ).toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource, City HAVING (COUNT(Name) > 100 AND City LIKE \'San%\')', soql); + } + + @IsTest + static void dynamicHavingFilterGroup() { + // Setup + SOQL.HavingFilterGroup havingFilterGroup = SOQL.HavingFilterGroup; + + havingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)); + havingFilterGroup.add(SOQL.HavingFilter.with(Lead.City).startsWith('San')); + + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(havingFilterGroup) + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource, City HAVING (COUNT(Name) > 100 AND City LIKE \'San%\')', soql); + } + + @IsTest + static void dynamicHavingFilterGroupOnSoqlInstance() { + // Test + SOQL builder = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.WITH(Lead.LeadSource).equal('Web')); + + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource, City HAVING LeadSource = \'Web\'', builder.toString()); + + builder.have( + SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .add(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + ); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource, City HAVING LeadSource = \'Web\' AND (COUNT(Name) > 100 AND City LIKE \'San%\')', builder.toString()); + } + + @IsTest + static void multipleHavingFilterGroups() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(1))) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(2))) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(3))) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(4))) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(5))) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(6))) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(7))) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(8))) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(9))) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(10))) + .have(SOQL.HavingFilterGroup.add(SOQL.HavingFilter.count(Lead.Name).greaterThan(11))) + .anyHavingConditionMatching() + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource, City HAVING (COUNT(Name) > 1) OR (COUNT(Name) > 2) OR (COUNT(Name) > 3) OR (COUNT(Name) > 4) OR (COUNT(Name) > 5) OR (COUNT(Name) > 6) OR (COUNT(Name) > 7) OR (COUNT(Name) > 8) OR (COUNT(Name) > 9) OR (COUNT(Name) > 10) OR (COUNT(Name) > 11)', soql); + } + + @IsTest + static void anyConditionMatchingForInnerHavingFilterGroups() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .add(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .anyConditionMatching() + ).toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource, City HAVING (COUNT(Name) > 100 OR City LIKE \'San%\')', soql); + } + + @IsTest + static void anyonditionMatchingForMainHavingFilterGroup() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .have(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .anyHavingConditionMatching() + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource, City HAVING COUNT(Name) > 100 OR City LIKE \'San%\'', soql); + } + + @IsTest + static void conditionLogicForMainHavingFilterGroup() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .have(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .havingConditionLogic('1 OR 2') + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource, City HAVING COUNT(Name) > 100 OR City LIKE \'San%\'', soql); + } + + @IsTest + static void nestedHavingFilterGroups() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilterGroup + .add(SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .add(SOQL.HavingFilter.count(Lead.Name).lessThan(200)) + ) + .add(SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(400)) + .add(SOQL.HavingFilter.count(Lead.Name).lessThan(500)) + ) + ).toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource HAVING ((COUNT(Name) > 100 AND COUNT(Name) < 200) AND (COUNT(Name) > 400 AND COUNT(Name) < 500))', soql); + } + + @IsTest + static void evaluateStringHavingCondition() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have('COUNT(Name) > 100 AND COUNT(Name) < 200') + .toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource HAVING COUNT(Name) > 100 AND COUNT(Name) < 200', soql); + } + + @IsTest + static void evaluateStringHavingConditionAndHavingFilterGroup() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have('(COUNT(Name) > 100 AND COUNT(Name) < 200)') + .have(SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(400)) + .add(SOQL.HavingFilter.count(Lead.Name).lessThan(500)) + ).toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource HAVING (COUNT(Name) > 100 AND COUNT(Name) < 200) AND (COUNT(Name) > 400 AND COUNT(Name) < 500)', soql); + } + + @IsTest + static void evaluateStringInHavingFilterGroupAndHavingFilter() { + // Test + String soql = SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilterGroup + .add('(COUNT(Name) > 100 AND COUNT(Name) < 200)') + .add(SOQL.HavingFilter.count(Lead.Name).lessThan(500)) + ).toString(); + + // Verify + Assert.areEqual('SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource HAVING ((COUNT(Name) > 100 AND COUNT(Name) < 200) AND COUNT(Name) < 500)', soql); + } + @IsTest static void orderByString() { // Test @@ -2676,50 +3335,6 @@ private class SOQL_Test { } } - @IsTest - static void toCursor() { - // Setup - insertAccounts(); - - // Test - Database.Cursor queryCursor = SOQL.of(Account.SObjectType) - .with(Account.Id) - .toCursor(); - - // Verify - Assert.areEqual(2, queryCursor.getNumRecords()); - } - - @IsTest - static void toCursorWithSharing() { - // Setup - insertTasks(); - - System.runAs(minimumAccessUser()) { - insert new Task(Subject = 'Test', Type = 'Other'); - - // Test - Database.Cursor queryCursor = SOQL.of(Task.SObjectType).systemMode().withSharing().toCursor(); - - // Verify - Assert.areEqual(1, queryCursor.getNumRecords()); - } - } - - @IsTest - static void toCursorWithoutSharing() { - // Setup - insertTasks(); - - System.runAs(minimumAccessUser()) { - // Test - Database.Cursor queryCursor = SOQL.of(Task.SObjectType).systemMode().withoutSharing().toCursor(); - - // Verify - Assert.areEqual(2, queryCursor.getNumRecords()); - } - } - static List insertAccounts() { List accounts = new List{ new Account(Name = 'Test 1'), diff --git a/website/docs/api/soql-filter.md b/website/docs/api/soql-filter.md index 88bfa09..d793b89 100644 --- a/website/docs/api/soql-filter.md +++ b/website/docs/api/soql-filter.md @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 30 --- # Filter @@ -33,13 +33,13 @@ The following are methods for `Filter`. - [`greaterOrEqual(Object value)`](#greaterorequal) - [`containsSome(List values)`](#containssome) - [`contains(String value)`](#contains) +- [`contains(String prefix, String value, String suffix)`](#contains) - [`notContains(String value)`](#notcontains) +- [`notContains(String prefix, String value, String suffix)`](#notcontains) - [`endsWith(String value)`](#endswith) - [`notEndsWith(String value)`](#notendswith) - [`startsWith(String value)`](#startswith) - [`notStartsWith(String value)`](#notstartswith) -- [`contains(String prefix, String value, String suffix)`](#contains) -- [`notContains(String prefix, String value, String suffix)`](#notcontains) - [`isIn(Iterable iterable)`](#isin) - [`isIn(List inList)`](#isin) - [`isIn(InnerJoin joinQuery)`](#isin-join-query) @@ -506,7 +506,7 @@ SOQL.of(Contact.SObjectType) .toList(); ``` -### notcontains +### notContains - `WHERE NOT Name LIKE '%My%'` diff --git a/website/docs/api/soql-filters-group.md b/website/docs/api/soql-filters-group.md index b5652ec..7abce29 100644 --- a/website/docs/api/soql-filters-group.md +++ b/website/docs/api/soql-filters-group.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 40 --- # FilterGroup diff --git a/website/docs/api/soql-having-filter-group.md b/website/docs/api/soql-having-filter-group.md new file mode 100644 index 0000000..75c3ee7 --- /dev/null +++ b/website/docs/api/soql-having-filter-group.md @@ -0,0 +1,164 @@ +--- +sidebar_position: 70 +--- + +# HavingFilterGroup + +Create group of having conditions. + +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.City) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .add(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + ) + .toAggregated(); +``` + + +## Methods + +The following are methods for `HavingFilterGroup`. + +[**ADD CONDITION**](#add-condition) + +- [`add(HavingFilterGroup havingFilterGroup)`](#add) +- [`add(HavingFilter havingFilter)`](#add) +- [`add(String dynamicCondition)`](#add) + +[**ORDER**](#order) + +- [`anyConditionMatching()`](#anyconditionmatching) +- [`conditionLogic(String order)`](#conditionlogic) + +## ADD CONDITION +### add + +Allows to add multiple conditions. +Add a [`SOQL.HavingFilter`](soql-having-filter.md) or [`SOQL.HavingFilterGroup`](soql-having-filter-group.md) or `String`. + +**Signature** + +```apex +HavingFilterGroup add(HavingFilterGroup havingFilterGroup) +HavingFilterGroup add(HavingFilter havingFilter) +HavingFilterGroup add(String dynamicCondition) +``` + +**Example** + +```sql +SELECT LeadSource, COUNT(Name) +FROM Lead +GROUP BY LeadSource, City +HAVING (COUNT(Name) > 100 AND City LIKE 'San%') +``` + +```apex +// build conditions on fly +SOQL.HavingFilterGroup havingFilterGroup = SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .add(SOQL.HavingFilter.with(Lead.City).startsWith('San')); + +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(havingFilterGroup) + .toAggregated(); +``` + +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .add(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + ).toAggregated(); +``` + +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have('(COUNT(Name) > 100 AND COUNT(Name) < 200)') + .have(SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(400)) + .add(SOQL.HavingFilter.count(Lead.Name).lessThan(500)) + ).toAggregated(); +``` + +## ORDER +### conditionLogic + +Set conditions order for SOQL query. +When not specify all conditions will be with `AND`. + +**Signature** + +```apex +HavingFilterGroup conditionLogic(String order) +``` + +**Example** + +```sql +SELECT LeadSource, COUNT(Name) +FROM Lead +GROUP BY LeadSource, City +HAVING (COUNT(Name) > 100 OR City LIKE 'San%') +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .add(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .conditionLogic('1 OR 2') + ).toAggregated(); +``` + +### anyConditionMatching + +When the [conditionLogic](#conditionlogic) is not specified, all conditions are joined using the `AND` operator by default. + +To change the default condition logic, you can utilize the `anyConditionMatching` method, which joins conditions using the `OR` operator. + +**Signature** + +```apex +HavingFilterGroup anyConditionMatching() +``` + +**Example** + +```sql +SELECT LeadSource, COUNT(Name) +FROM Lead +GROUP BY LeadSource, City +HAVING (COUNT(Name) > 100 OR City LIKE 'San%') +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilterGroup + .add(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .add(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .anyConditionMatching() + ).toAggregated(); +``` diff --git a/website/docs/api/soql-having-filter.md b/website/docs/api/soql-having-filter.md new file mode 100644 index 0000000..fb7dbf8 --- /dev/null +++ b/website/docs/api/soql-having-filter.md @@ -0,0 +1,747 @@ +--- +sidebar_position: 60 +--- + +# HavingFilter + +Specify and adjust single having condition. + +```apex +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).greaterThan(1000000)) + .toAggregated(); +``` + +## Methods + +The following are methods for `HavingFilter`. + +[**FIELDS**](#fields) + +- [`with(SObjectField field)`](#with-sobject-field) +- [`with(String field)`](#with-string-field) +- [`count(SObjectField field)`](#count) +- [`countDistinct(SObjectField field)`](#countdistinct) +- [`min(SObjectField field)`](#min) +- [`max(SObjectField field)`](#max) +- [`sum(SObjectField field)`](#sum) + +[**COMPERATORS**](#comperators) + +- [`isNull()`](#isnull) +- [`isNotNull()`](#isnotnull) +- [`isTrue()`](#istrue) +- [`isFalse()`](#isfalse) +- [`equal(Object value)`](#equal) +- [`notEqual(Object value)`](#notequal) +- [`lessThan(Object value)`](#lessthan) +- [`greaterThan(Object value)`](#greaterthan) +- [`lessOrEqual(Object value)`](#lessorequal) +- [`greaterOrEqual(Object value)`](#greaterorequal) +- [`contains(String value)`](#contains) +- [`contains(String prefix, String value, String suffix)`](#contains) +- [`notContains(String value)`](#notcontains) +- [`notContains(String prefix, String value, String suffix)`](#notcontains) +- [`endsWith(String value)`](#endswith) +- [`notEndsWith(String value)`](#notendswith) +- [`startsWith(String value)`](#startswith) +- [`notStartsWith(String value)`](#notstartswith) +- [`isIn(Iterable iterable)`](#isin) +- [`notIn(Iterable iterable)`](#notin) + +## FIELDS + +### with sobject field + +Specify field that should be used in the having condition. + +**Signature** + +```apex +HavingFilter with(SObjectField field) +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY City +HAVING City LIKE 'San%' +``` +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .toAggregated(); +``` + +### with string field + +Specify fields that should be used in the condition. + +**Signature** + +```apex +HavingFilter with(String field) +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY City +HAVING City LIKE 'San%' +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.with('City').startsWith('San')) + .toAggregated(); +``` + +### count + +Returns the number of rows matching the query criteria. + +**Signature** + +```apex +HavingFilter count(SObjectField field) +``` + +**Example** + +```sql +SELECT LeadSource +FROM Lead +GROUP BY LeadSource +HAVING COUNT(Name) > 100 +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .toAggregated(); +``` + +### countDistinct + +Returns the number of distinct non-null field values matching the query criteria. + +**Signature** + +```apex +HavingFilter countDistinct(SObjectField field) +``` + +**Example** + +```sql +SELECT LeadSource +FROM Lead +GROUP BY LeadSource +HAVING COUNT_DISTINCT(Name) > 100 +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.countDistinct(Lead.Name).greaterThan(100)) + .toAggregated(); +``` + +### min + +Returns the minimum value of a field. + +**Signature** + +```apex +HavingFilter min(SObjectField field) +``` + +**Example** + +```sql +SELECT LeadSource +FROM Lead +GROUP BY LeadSource +HAVING MIN(NumberOfEmployees) > 100 +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.min(Lead.NumberOfEmployees).greaterThan(100)) + .toAggregated(); +``` + +### max + +Returns the maximum value of a field. + +**Signature** + +```apex +HavingFilter min(SObjectField field) +``` + +**Example** + +```sql +SELECT LeadSource +FROM Lead +GROUP BY LeadSource +HAVING MAX(NumberOfEmployees) < 100 +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.max(Lead.NumberOfEmployees).lessThan(100)) + .toAggregated(); +``` + +### sum + +Returns the total sum of a numeric field. + +**Signature** + +```apex +HavingFilter min(SObjectField field) +``` + +**Example** + +```sql +SELECT LeadSource +FROM Lead +GROUP BY LeadSource +HAVING SUM(AnnualRevenue) > 1000000 +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).greaterThan(1000000)) + .toAggregated(); +``` + +## COMPERATORS + +### isNull + +- `HAVING LeadSource = NULL` + +**Signature** + +```apex +HavingFilter isNull() +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING LeadSource = NULL +``` +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.with(Lead.LeadSource).isNull()) + .toAggregated(); +``` + +### isNotNull + +- `HAVING LeadSource != NULL` + +**Signature** + +```apex +HavingFilter isNotNull() +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING LeadSource != NULL +``` +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.with(Lead.LeadSource).isNotNull()) + .toAggregated(); +``` + +### isTrue + +- `HAVING IsConverted = TRUE` + +**Signature** + +```apex +HavingFilter isTrue() +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY IsConverted +HAVING IsConverted = TRUE +``` +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.IsConverted) + .have(SOQL.HavingFilter.with(Lead.IsConverted).isTrue()) + .toAggregated(); +``` + +### isFalse + +- `HAVING IsConverted = FALSE` + +**Signature** + +```apex +HavingFilter isFalse() +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY IsConverted +HAVING IsConverted = FALSE +``` +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.IsConverted) + .have(SOQL.HavingFilter.with(Lead.IsConverted).isFalse()) + .toAggregated(); +``` + +### equal + +- `HAVING City = 'Los Angeles'` +- `HAVING SUM(AnnualRevenue) = 100000` + +**Signature** + +```apex +HavingFilter equal(Object value) +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING LeadSource = 'Web' + +SELECT COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING SUM(AnnualRevenue) = 10000 +``` + +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).equal(10000)) + .toAggregated(); + +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.with(Lead.LeadSource).equal('Web')) + .toAggregated(); +``` + +### notEqual + +- `HAVING City != 'Los Angeles'` +- `HAVING SUM(AnnualRevenue) != 100000` + +**Signature** + +```apex +HavingFilter notEqual(Object value) +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING LeadSource != 'Web' + +SELECT COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING SUM(AnnualRevenue) != 10000 +``` + +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).notEqual(10000)) + .toAggregated(); + +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.with(Lead.LeadSource).notEqual('Web')) + .toAggregated(); +``` + +### lessThan + +- `HAVING SUM(AnnualRevenue) < 10000` + +**Signature** + +```apex +HavingFilter lessThan(Object value) +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING SUM(AnnualRevenue) < 10000 +``` +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).lessThan(10000)) + .toAggregated(); +``` + +### greaterThan + +- `HAVING SUM(AnnualRevenue) > 10000` + +**Signature** + +```apex +HavingFilter greaterThan(Object value) +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING SUM(AnnualRevenue) > 10000 +``` +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).greaterThan(10000)) + .toAggregated(); +``` + +### lessOrEqual + +- `HAVING SUM(AnnualRevenue) <= 10000` + +**Signature** + +```apex +HavingFilter lessOrEqual(Object value) +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING SUM(AnnualRevenue) <= 10000 +``` +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).lessOrEqual(10000)) + .toAggregated(); +``` + +### greaterOrEqual + +- `HAVING SUM(AnnualRevenue) >= 10000` + +**Signature** + +```apex +HavingFilter greaterOrEqual(Object value) +``` + +**Example** + +```sql +SELECT COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING SUM(AnnualRevenue) >= 10000 +``` +```apex +SOQL.of(Lead.SObjectType) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have(SOQL.HavingFilter.sum(Lead.AnnualRevenue).greaterOrEqual(10000)) + .toAggregated(); +``` + +### contains + +- `HAVING Name LIKE '%San%'` + +**Signature** + +```apex +HavingFilter contains(String value) +HavingFilter contains(String prefix, String value, String suffix); +``` + +**Example** + +```sql +SELECT SUM(AnnualRevenue) +FROM Lead +GROUP BY City +HAVING City LIKE '%San%' +``` +```apex +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).contains('San')) + .toAggregated(); + +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).contains('_', 'San', '%')) + .toAggregated(); +``` + +### notContains + +- `HAVING NOT Name LIKE '%San%'` + +**Signature** + +```apex +HavingFilter notContains(String value) +HavingFilter notContains(String prefix, String value, String suffix); +``` + +**Example** + +```sql +SELECT SUM(AnnualRevenue) +FROM Lead +GROUP BY City +HAVING (NOT City LIKE '%San%') +``` +```apex +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notContains('San')) + .toAggregated(); + +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notContains('_', 'San', '%')) + .toAggregated(); +``` + +### startsWith + +- `HAVING City LIKE 'San%'` + +**Signature** + +```apex +HavingFilter startsWith(String value) +``` + +**Example** + +```sql +SELECT SUM(AnnualRevenue) +FROM Lead +GROUP BY City +HAVING City LIKE 'San%' +``` +```apex +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .toAggregated(); +``` + +### notStartsWith + +- `HAVING NOT City LIKE 'San%'` + +**Signature** + +```apex +HavingFilter notStartsWith(String value) +``` + +**Example** + +```sql +SELECT SUM(AnnualRevenue) +FROM Lead +GROUP BY City +HAVING (NOT City LIKE 'San%') +``` +```apex +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notStartsWith('San')) + .toAggregated(); +``` + +### endsWith + +- `HAVING City LIKE '%San'` + +**Signature** + +```apex +HavingFilter endsWith(String value) +``` + +**Example** + +```sql +SELECT SUM(AnnualRevenue) +FROM Lead +GROUP BY City +HAVING City LIKE '%San' +``` +```apex +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).endsWith('San')) + .toAggregated(); +``` + +### notEndsWith + +- `HAVING NOT City LIKE '%San'` + +**Signature** + +```apex +HavingFilter notEndsWith(String value) +``` + +**Example** + +```sql +SELECT SUM(AnnualRevenue) +FROM Lead +GROUP BY City +HAVING (NOT City LIKE '%San') +``` +```apex +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notEndsWith('San')) + .toAggregated(); +``` + + +### isIn + +- `HAVING City IN ('San Francisco', 'Los Angeles')` + +**Signature** + +```apex +HavingFilter isIn(Iterable inList) +``` + +**Example** + +```sql +SELECT SUM(AnnualRevenue) +FROM Lead +GROUP BY City +HAVING City IN ('San Francisco', 'Los Angeles') +``` +```apex +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).isIn(new List{ 'San Francisco', 'Los Angeles' })) + .toAggregated(); +``` + +### notIn + +- `HAVING City NOT IN ('San Francisco', 'Los Angeles')` + +**Signature** + +```apex +HavingFilter notIn(Iterable inList) +``` + +**Example** + +```sql +SELECT SUM(AnnualRevenue) +FROM Lead +GROUP BY City +HAVING City NOT IN ('San Francisco', 'Los Angeles') +``` +```apex +SOQL.of(Lead.SObjectType) + .sum(Lead.AnnualRevenue) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.with(Lead.City).notIn(new List{ 'San Francisco', 'Los Angeles' })) + .toAggregated(); +``` diff --git a/website/docs/api/soql-join.md b/website/docs/api/soql-join.md index bb1e11f..ff1a44f 100644 --- a/website/docs/api/soql-join.md +++ b/website/docs/api/soql-join.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 50 --- # JoinQuery diff --git a/website/docs/api/soql-sub.md b/website/docs/api/soql-sub.md index 2b6c1ce..1ef13db 100644 --- a/website/docs/api/soql-sub.md +++ b/website/docs/api/soql-sub.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 20 --- # SubQuery diff --git a/website/docs/api/soql.md b/website/docs/api/soql.md index 5ce745b..68f6f9f 100644 --- a/website/docs/api/soql.md +++ b/website/docs/api/soql.md @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 10 --- # SOQL @@ -92,6 +92,7 @@ The following are methods for `SOQL`. - [`whereAre(FilterGroup filterGroup)`](#whereare) - [`whereAre(Filter filter)`](#whereare) +- [`whereAre(String dynamicCondition)`](#whereare-string) - [`conditionLogic(String order)`](#conditionlogic) - [`anyConditionMatching()`](#anyconditionmatching); @@ -104,6 +105,14 @@ The following are methods for `SOQL`. - [`groupByCube(SObjectField field)`](#groupbycube) - [`groupByCube(String relationshipName, SObjectField field)`](#groupbycube-related) +[**HAVING**](#having) + +- [`have(HavingFilterGroup filterGroup)`](#have) +- [`have(HavingFilter filter)`](#have) +- [`have(String dynamicCondition)`](#having-string) +- [`havingConditionLogic(String order)`](#havingconditionlogic) +- [`anyHavingConditionMatching()`](#anyhavingconditionmatching) + [**ORDER BY**](#order-by) - [`orderBy(SObjectField field)`](#order-by) @@ -1416,6 +1425,131 @@ SOQL.of(Lead.SObjectType) .toAggregated(); ``` +## HAVING + +### have + +[HAVING](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_having.htm) + +> `HAVING` is an optional clause that can be used in a SOQL query to filter results that aggregate functions return. + +For more details check [`SOQL.HavingFilterGroup`](soql-having-filter-group.md) and [`SOQL.HavingFilter`](soql-having-filter.md) + +**Signature** + +```apex +Queryable have(HavingFilterGroup havingFilterGroup) +Queryable have(HavingFilter havingFilter) +``` + +**Example** + +```sql +SELECT LeadSource, COUNT(Name) +FROM Lead +GROUP BY LeadSource, City +HAVING COUNT(Name) > 100 AND City LIKE 'San%' +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .have(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .toAggregated(); +``` + +### having string + +Execute having conditions passed as String. + +**Signature** + +```apex +Queryable have(String conditions) +``` + +**Example** + +```sql +SELECT LeadSource, COUNT(Name) +FROM Lead +GROUP BY LeadSource +HAVING COUNT(Name) > 100 AND COUNT(Name) < 200 +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .have('COUNT(Name) > 100 AND COUNT(Name) < 200') + .toAggregated(); +``` + +### havingConditionLogic + +Set conditions order for SOQL HAVING clause. When not specify all conditions will be with `AND`. + +**Signature** + +```apex +Queryable havingConditionLogic(String order) +``` + +**Example** + +```sql +SELECT LeadSource, COUNT(Name) +FROM Lead +GROUP BY LeadSource, City +HAVING COUNT(Name) > 100 OR City LIKE 'San%' +``` +```apex + SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .have(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .havingConditionLogic('1 OR 2') + .toAggregated(); +``` + +### anyHavingConditionMatching + +When the [havingConditionLogic](#havingConditionLogic) is not specified, all conditions are joined using the `AND` operator by default. + +To change the default condition logic, you can utilize the `anyHavingConditionMatching` method, which joins conditions using the `OR` operator. + +**Signature** + +```apex +Queryable anyHavingConditionMatching() +``` + +**Example** + +```sql +SELECT LeadSource, COUNT(Name) +FROM Lead +GROUP BY LeadSource, City +HAVING COUNT(Name) > 100 OR City LIKE 'San%' +``` +```apex +SOQL.of(Lead.SObjectType) + .with(Lead.LeadSource) + .count(Lead.Name) + .groupBy(Lead.LeadSource) + .groupBy(Lead.City) + .have(SOQL.HavingFilter.count(Lead.Name).greaterThan(100)) + .have(SOQL.HavingFilter.with(Lead.City).startsWith('San')) + .anyHavingConditionMatching() + .toAggregated(); +``` + ## ORDER BY [ORDER BY](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_orderby.htm) diff --git a/website/docs/docs/design.md b/website/docs/docs/design.md index 585c21a..48b7ce2 100644 --- a/website/docs/docs/design.md +++ b/website/docs/docs/design.md @@ -16,218 +16,315 @@ All crucial information is kept at the top of the class, so developers can use i ```apex -public static SubQuery SubQuery { - get { - return new QSubQuery(); + public interface Selector { + Queryable query(); } -} -public static FilterGroup FilterGroup { // A group to nest more filters - get { - return new QFilterGroup(); + public interface Queryable { + // SELECT + Queryable with(SObjectField field); + Queryable with(SObjectField field1, SObjectField field2); + Queryable with(SObjectField field1, SObjectField field2, SObjectField field3); + Queryable with(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4); + Queryable with(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5); + Queryable with(List fields); + Queryable with(Iterable fields); + Queryable with(String fields); + Queryable with(SObjectField field, String alias); + Queryable with(String relationshipName, SObjectField field); + Queryable with(String relationshipName, SObjectField field1, SObjectField field2); + Queryable with(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3); + Queryable with(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4); + Queryable with(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5); + Queryable with(String relationshipName, Iterable fields); + Queryable with(SubQuery subQuery); + Queryable withFieldSet(String fieldSetName); + // SELECT - AGGREGATE FUNCTIONS + Queryable count(); + Queryable count(SObjectField field); + Queryable count(SObjectField field, String alias); + Queryable count(String relationshipName, SObjectField field); + Queryable count(String relationshipName, SObjectField field, String alias); + Queryable avg(SObjectField field); + Queryable avg(SObjectField field, String alias); + Queryable avg(String relationshipName, SObjectField field); + Queryable avg(String relationshipName, SObjectField field, String alias); + Queryable countDistinct(SObjectField field); + Queryable countDistinct(SObjectField field, String alias); + Queryable countDistinct(String relationshipName, SObjectField field); + Queryable countDistinct(String relationshipName, SObjectField field, String alias); + Queryable min(SObjectField field); + Queryable min(SObjectField field, String alias); + Queryable min(String relationshipName, SObjectField field); + Queryable min(String relationshipName, SObjectField field, String alias); + Queryable max(SObjectField field); + Queryable max(SObjectField field, String alias); + Queryable max(String relationshipName, SObjectField field); + Queryable max(String relationshipName, SObjectField field, String alias); + Queryable sum(SObjectField field); + Queryable sum(SObjectField field, String alias); + Queryable sum(String relationshipName, SObjectField field); + Queryable sum(String relationshipName, SObjectField field, String alias); + // SELECT - GROUPING + Queryable grouping(SObjectField field, String alias); + // SELECT - toLabel + Queryable toLabel(SObjectField field); + Queryable toLabel(String field); + // SELECT - FORMAT + Queryable format(SObjectField field); + Queryable format(SObjectField field, String alias); + // USING SCOPE + Queryable delegatedScope(); + Queryable mineScope(); + Queryable mineAndMyGroupsScope(); + Queryable myTerritoryScope(); + Queryable myTeamTerritoryScope(); + Queryable teamScope(); + // WHERE + Queryable whereAre(FilterGroup filterGroup); + Queryable whereAre(Filter filter); + Queryable whereAre(String conditions); + Queryable conditionLogic(String order); + Queryable anyConditionMatching(); + // GROUP BY + Queryable groupBy(SObjectField field); + Queryable groupBy(String relationshipName, SObjectField field); + Queryable groupByRollup(SObjectField field); + Queryable groupByRollup(String relationshipName, SObjectField field); + Queryable groupByCube(SObjectField field); + Queryable groupByCube(String relationshipName, SObjectField field); + // HAVING + Queryable have(HavingFilterGroup havingFilterGroup); + Queryable have(HavingFilter havingFilter); + Queryable have(String havingConditions); + Queryable havingConditionLogic(String havingConditionsOrder); + Queryable anyHavingConditionMatching(); + // ORDER BY + Queryable orderBy(String field); + Queryable orderBy(String field, String direction); + Queryable orderBy(SObjectField field); + Queryable orderBy(String relationshipName, SObjectField field); + Queryable sortDesc(); + Queryable nullsLast(); + // LIMIT + Queryable setLimit(Integer amount); + // OFFSET + Queryable offset(Integer startingRow); + // FOR + Queryable forReference(); + Queryable forView(); + Queryable forUpdate(); + Queryable allRows(); + // FIELD-LEVEL SECURITY + Queryable userMode(); + Queryable systemMode(); + Queryable stripInaccessible(); + Queryable stripInaccessible(AccessType accessType); + // SHARING MODE + Queryable withSharing(); + Queryable withoutSharing(); + // MOCKING + Queryable mockId(String id); + // DEBUGGING + Queryable preview(); + // PREDEFINIED + Queryable byId(SObject record); + Queryable byId(Id recordId); + Queryable byIds(Iterable recordIds); + Queryable byIds(List records); + Queryable byRecordType(String recordTypeDeveloperName); + // RESULT + Boolean doExist(); + String toString(); + Object toValueOf(SObjectField fieldToExtract); + Set toValuesOf(SObjectField fieldToExtract); + Integer toInteger(); + SObject toObject(); + List toList(); + List toAggregated(); + Map toMap(); + Map toMap(SObjectField keyField); + Map toMap(SObjectField keyField, SObjectField valueField); + Map> toAggregatedMap(SObjectField keyField); + Map> toAggregatedMap(SObjectField keyField, SObjectField valueField); + Database.QueryLocator toQueryLocator(); } -} -public static Filter Filter { - get { - return new QFilter(); + public interface SubQuery { + SubQuery of(String ofObject); + // SELECT + SubQuery with(SObjectField field); + SubQuery with(SObjectField field1, SObjectField field2); + SubQuery with(SObjectField field1, SObjectField field2, SObjectField field3); + SubQuery with(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4); + SubQuery with(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5); + SubQuery with(Iterable fields); + SubQuery with(String relationshipName, Iterable fields); + SubQuery with(SubQuery subQuery); + // WHERE + SubQuery whereAre(FilterGroup filterGroup); + SubQuery whereAre(Filter filter); + //ORDER BY + SubQuery orderBy(SObjectField field); + SubQuery orderBy(String relationshipName, SObjectField field); + SubQuery sortDesc(); + SubQuery nullsLast(); + // LIMIT + SubQuery setLimit(Integer amount); + // OFFSET + SubQuery offset(Integer startingRow); + // FOR + SubQuery forReference(); + SubQuery forView(); } -} -public static InnerJoin InnerJoin { - get { - return new QJoinQuery(); + public interface FilterGroup { + // ADD CONDITION + FilterGroup add(FilterGroup filterGroup); + FilterGroup add(Filter filter); + FilterGroup add(String dynamicCondition); + // ORDER + FilterGroup anyConditionMatching(); + FilterGroup conditionLogic(String order); + // ADDITIONAL + FilterGroup ignoreWhen(Boolean logicExpression); + + Boolean hasValues(); } -} - -public interface Selector { - Queryable query(); -} - -public interface Queryable { - Queryable of(SObjectType ofObject); - Queryable of(String ofObject); // Dynamic SOQL - - Queryable with(SObjectField field); - Queryable with(SObjectField field1, SObjectField field2); - Queryable with(SObjectField field1, SObjectField field2, SObjectField field3); - Queryable with(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4); - Queryable with(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5); - Queryable with(List fields); // For more than 5 fields - Queryable with(String fields); // Dynamic SOQL - Queryable with(SObjectField field, String alias); // Only aggregate expressions use field aliasing - Queryable with(String relationshipName, SObjectField field); - Queryable with(String relationshipName, SObjectField field1, SObjectField field2); - Queryable with(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3); - Queryable with(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4); - Queryable with(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5); - Queryable with(String relationshipName, List fields); // For more than 5 fields - Queryable with(SubQuery subQuery); // SOQL.SubQuery - - Queryable count(); - Queryable count(SObjectField field); - Queryable count(SObjectField field, String alias); - - Queryable delegatedScope(); - Queryable mineScope(); - Queryable mineAndMyGroupsScope(); - Queryable myTerritoryScope(); - Queryable myTeamTerritoryScope(); - Queryable teamScope(); - - Queryable whereAre(FilterGroup filterGroup); // SOQL.FilterGroup - Queryable whereAre(Filter filter); // SOQL.Filter - Queryable whereAre(String conditions); // Conditions to evaluate - - Queryable groupBy(SObjectField field); - Queryable groupByRollup(SObjectField field); - Queryable groupByCube(SObjectField field); - - Queryable orderBy(String field); // ASC, NULLS FIRST by default - Queryable orderBy(String field, String direction); // dynamic order by, NULLS FIRST by default - Queryable orderBy(SObjectField field); // ASC, NULLS FIRST by default - Queryable orderBy(String relationshipName, SObjectField field); // ASC, NULLS FIRST by default - Queryable sortDesc(); - Queryable nullsLast(); - - Queryable setLimit(Integer amount); - - Queryable offset(Integer startingRow); - - Queryable forReference(); - Queryable forView(); - Queryable forUpdate(); - Queryable allRows(); - - Queryable systemMode(); // USER_MODE by default - - Queryable withSharing(); // Works only with .systemMode() - Queryable withoutSharing(); // Works only with .systemMode() - - Queryable mockId(String id); - - Queryable preview(); - - Queryable stripInaccessible(); - Queryable stripInaccessible(AccessType accessType); - - Queryable byId(SObject record); - Queryable byId(Id recordId); - Queryable byIds(Iterable recordIds); // List or Set - Queryable byIds(List records); - - String toString(); - Object toValueOf(SObjectField fieldToExtract); - Set toValuesOf(SObjectField fieldToExtract); - Integer toInteger(); // For COUNT query - SObject toObject(); - List toList(); - List toAggregated(); - Map toMap(); - Database.QueryLocator toQueryLocator(); -} -public interface SubQuery { // SOQL.SubQuery - SubQuery of(String ofObject); - - SubQuery with(SObjectField field); - SubQuery with(SObjectField field1, SObjectField field2); - SubQuery with(SObjectField field1, SObjectField field2, SObjectField field3); - SubQuery with(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4); - SubQuery with(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5); - SubQuery with(List fields); // For more than 5 fields - SubQuery with(String relationshipName, List fields); - SubQuery with(SubQuery subQuery); // SOQL.SubQuery - - SubQuery whereAre(FilterGroup filterGroup); // SOQL.FilterGroup - SubQuery whereAre(Filter filter); // SOQL.Filter - - SubQuery orderBy(SObjectField field); - SubQuery orderBy(String relationshipName, SObjectField field); - SubQuery sortDesc(); - SubQuery nullsLast(); - - SubQuery setLimit(Integer amount); + public interface Filter { + // FIELDS + Filter id(); + Filter recordType(); + Filter name(); + Filter with(SObjectField field); + Filter with(String field); + Filter with(String relationshipName, SObjectField field); + // COMPERATORS + Filter isNull(); + Filter isNotNull(); + Filter isTrue(); + Filter isFalse(); + Filter equal(Object value); + Filter notEqual(Object value); + Filter lessThan(Object value); + Filter greaterThan(Object value); + Filter lessOrEqual(Object value); + Filter greaterOrEqual(Object value); + Filter containsSome(Iterable values); + Filter contains(String value); + Filter notContains(String value); + Filter endsWith(String value); + Filter notEndsWith(String value); + Filter startsWith(String value); + Filter notStartsWith(String value); + Filter contains(String prefix, String value, String suffix); + Filter notContains(String prefix, String value, String suffix); + Filter isIn(Iterable iterable); + Filter isIn(InnerJoin joinQuery); + Filter notIn(Iterable iterable); + Filter notIn(InnerJoin joinQuery); + Filter includesAll(Iterable values); + Filter includesSome(Iterable values); + Filter excludesAll(Iterable values); + Filter excludesSome(Iterable values); + // ADDITIONAL + Filter asDateLiteral(); + Filter ignoreWhen(Boolean logicExpression); + + Boolean hasValue(); + } - SubQuery offset(Integer startingRow); + public interface InnerJoin { + InnerJoin of(SObjectType ofObject); + // SELECT + InnerJoin with(SObjectField field); + // WHERE + InnerJoin whereAre(FilterGroup filterGroup); + InnerJoin whereAre(Filter filter); + } - SubQuery forReference(); - SubQuery forView(); -} + public interface HavingFilterGroup { + // ADD CONDITION + HavingFilterGroup add(HavingFilterGroup filterGroup); + HavingFilterGroup add(HavingFilter filter); + HavingFilterGroup add(String dynamicHaving); + // ORDER + HavingFilterGroup anyConditionMatching(); + HavingFilterGroup conditionLogic(String order); + } -public interface FilterGroup { // SOQL.FilterGroup - FilterGroup add(FilterGroup filterGroup); // SOQL.FilterGroup - FilterGroup add(Filter filter); // SOQL.Filter - FilterGroup add(String dynamicCondition); // Pass condition as String + public interface HavingFilter { + // FIELDS + HavingFilter with(SObjectField field); + HavingFilter with(String field); + HavingFilter count(SObjectField field); + HavingFilter avg(SObjectField field); + HavingFilter countDistinct(SObjectField field); + HavingFilter min(SObjectField field); + HavingFilter max(SObjectField field); + HavingFilter sum(SObjectField field); + // COMPERATORS + HavingFilter isNull(); + HavingFilter isNotNull(); + HavingFilter isTrue(); + HavingFilter isFalse(); + HavingFilter equal(Object value); + HavingFilter notEqual(Object value); + HavingFilter lessThan(Object value); + HavingFilter greaterThan(Object value); + HavingFilter lessOrEqual(Object value); + HavingFilter greaterOrEqual(Object value); + HavingFilter contains(String value); + HavingFilter contains(String prefix, String value, String suffix); + HavingFilter notContains(String value); + HavingFilter notContains(String prefix, String value, String suffix); + HavingFilter startsWith(String value); + HavingFilter notStartsWith(String value); + HavingFilter endsWith(String value); + HavingFilter notEndsWith(String value); + HavingFilter isIn(Iterable iterable); + HavingFilter notIn(Iterable iterable); + } - FilterGroup anyConditionMatching(); // All group filters will be join by OR - FilterGroup conditionLogic(String order); + public static SubQuery SubQuery { + get { return new SoqlSubQuery(); } + } - Boolean hasValues(); -} + public static FilterGroup FilterGroup { + get { return new SoqlFilterGroup(); } + } -public interface Filter { // SOQL.Filter - Filter id(); - Filter recordType(); - Filter name(); - Filter with(SObjectField field); - Filter with(String field); - Filter with(String relationshipName, SObjectField field); - - Filter isNull(); // = NULL - Filter isNotNull(); // != NULL - Filter isTrue(); // = TRUE - Filter isFalse(); // = FALSE - Filter equal(Object value); // = :value - Filter notEqual(Object value); // != :value - Filter lessThan(Object value); // < :value - Filter greaterThan(Object value); // > :value - Filter lessOrEqual(Object value); // <= :value - Filter greaterOrEqual(Object value); // >= :value - Filter containsSome(List values); // LIKE :values - Filter contains(String value); // LIKE :'%' + value + '%' - Filter endsWith(String value); // LIKE :'%' + value - Filter startsWith(String value); // LIKE :value + '%' - Filter contains(String prefix, String value, String suffix); // custom LIKE - Filter isIn(Iterable iterable); // IN :inList or inSet - Filter isIn(List inList); // IN :inList - Filter isIn(InnerJoin joinQuery); // SOQL.InnerJoin - Filter notIn(Iterable iterable); // NOT IN :inList or inSet - Filter notIn(List inList); // NOT IN :inList - Filter notIn(InnerJoin joinQuery); // SOQL.InnerJoin - Filter includesAll(Iterable values); // join with ; - Filter includesSome(Iterable values); // join with , - Filter excludesAll(Iterable values); // join with , - Filter excludesSome(Iterable values); // join with ; - - Filter ignoreWhen(Boolean logicExpression); - - Boolean hasValue(); -} + public static Filter Filter { + get { return new SoqlFilter(); } + } -public interface InnerJoin { // SOQL.InnerJoin - InnerJoin of(SObjectType ofObject); + public static InnerJoin InnerJoin { + get { return new SoqlJoinQuery(); } + } - InnerJoin with(SObjectField field); + public static HavingFilterGroup HavingFilterGroup { + get { return new SoqlHavingFilterGroup(); } + } - InnerJoin whereAre(FilterGroup filterGroup); // SOQL.FilterGroup - InnerJoin whereAre(Filter filter); // SOQL.Filter -} + public static HavingFilter HavingFilter { + get { return new SoqlHavingFilter(); } + } -@TestVisible -private static void setMock(String mockId, SObject record) { - setMock(mockId, new List{ record }); -} + @TestVisible + private static void setMock(String mockId, SObject record) { + setMock(mockId, new List{ record }); + } -@TestVisible -private static void setMock(String mockId, List records) { - mock.setMock(mockId, records); -} + @TestVisible + private static void setMock(String mockId, List records) { + mock.setMock(mockId, records); + } -@TestVisible -private static void setCountMock(String mockId, Integer amount) { - mock.setCountMock(mockId, amount); -} + @TestVisible + private static void setCountMock(String mockId, Integer amount) { + mock.setCountMock(mockId, amount); + } ``` ## Functional Programming diff --git a/website/docs/docs/installation.md b/website/docs/docs/installation.md index ec926ad..3ab5970 100644 --- a/website/docs/docs/installation.md +++ b/website/docs/docs/installation.md @@ -7,7 +7,7 @@ sidebar_position: 15 **System Requirements** -- API: 58.0 +- API: 61.0 ## Deploy diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 3539699..bea67f8 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -6,7 +6,7 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula') /** @type {import('@docusaurus/types').Config} */ const config = { - title: 'BTC SOQL Lib', + title: 'SOQL Lib', tagline: 'Apex SOQL provides functional constructs for SOQL.', favicon: 'img/favicon.ico',