diff --git a/force-app/main/default/classes/SOQL.cls b/force-app/main/default/classes/SOQL.cls index c5abffbf..30b42b5d 100644 --- a/force-app/main/default/classes/SOQL.cls +++ b/force-app/main/default/classes/SOQL.cls @@ -17,25 +17,25 @@ public virtual inherited sharing class SOQL implements Queryable { public static SubQuery SubQuery { get { - return new QSubQuery(); + return new SoqlSubQuery(); } } public static FilterGroup FilterGroup { get { - return new QFilterGroup(); + return new SoqlFilterGroup(); } } public static Filter Filter { get { - return new QFilter(); + return new SoqlFilter(); } } public static InnerJoin InnerJoin { get { - return new QJoinQuery(); + return new SoqlJoinQuery(); } } @@ -61,11 +61,21 @@ public virtual inherited sharing class SOQL implements Queryable { Queryable with(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5); Queryable with(String relationshipName, List fields); Queryable with(SubQuery subQuery); - // COUNT + // SELECT - AGGREGATE FUNCTIONS Queryable count(); Queryable count(SObjectField field); Queryable count(SObjectField field, String alias); - // GROUPING + Queryable avg(SObjectField field); + Queryable avg(SObjectField field, String alias); + Queryable countDistinct(SObjectField field); + Queryable countDistinct(SObjectField field, String alias); + Queryable min(SObjectField field); + Queryable min(SObjectField field, String alias); + Queryable max(SObjectField field); + Queryable max(SObjectField field, String alias); + Queryable sum(SObjectField field); + Queryable sum(SObjectField field, String alias); + // SELECT - GROUPING Queryable grouping(SObjectField field, String alias); // USING SCOPE Queryable delegatedScope(); @@ -169,6 +179,8 @@ public virtual inherited sharing class SOQL implements Queryable { // ORDER FilterGroup anyConditionMatching(); FilterGroup conditionLogic(String order); + // ADDITIONAL + FilterGroup ignoreWhen(Boolean logicExpression); Boolean hasValues(); } @@ -344,8 +356,7 @@ public virtual inherited sharing class SOQL implements Queryable { } public SOQL count(SObjectField field) { - builder.fields.count(field); - return this; + return count(field, ''); } public SOQL count(SObjectField field, String alias) { @@ -353,6 +364,51 @@ public virtual inherited sharing class SOQL implements Queryable { return this; } + public SOQL avg(SObjectField field) { + return avg(field, ''); + } + + public SOQL avg(SObjectField field, String alias) { + builder.fields.avg(field, alias); + return this; + } + + public SOQL countDistinct(SObjectField field) { + return countDistinct(field, ''); + } + + public SOQL countDistinct(SObjectField field, String alias) { + builder.fields.countDistinct(field, alias); + return this; + } + + public SOQL min(SObjectField field) { + return min(field, ''); + } + + public SOQL min(SObjectField field, String alias) { + builder.fields.min(field, alias); + return this; + } + + public SOQL max(SObjectField field) { + return max(field, ''); + } + + public SOQL max(SObjectField field, String alias) { + builder.fields.max(field, alias); + return this; + } + + public SOQL sum(SObjectField field) { + return sum(field, ''); + } + + public SOQL sum(SObjectField field, String alias) { + builder.fields.sum(field, alias); + return this; + } + public SOQL grouping(SObjectField field, String alias) { builder.fields.grouping(field, alias); return this; @@ -415,19 +471,19 @@ public virtual inherited sharing class SOQL implements Queryable { public SOQL groupBy(SObjectField field) { builder.groupBy.with(field); - builder.fields.withAggregatedField(field); + builder.fields.withGroupedField(field); return this; } public SOQL groupByRollup(SObjectField field) { builder.groupBy.rollup(field); - builder.fields.withAggregatedField(field); + builder.fields.withGroupedField(field); return this; } public SOQL groupByCube(SObjectField field) { builder.groupBy.cube(field); - builder.fields.withAggregatedField(field); + builder.fields.withGroupedField(field); return this; } @@ -553,9 +609,7 @@ public virtual inherited sharing class SOQL implements Queryable { } public Integer toInteger() { - if (!builder.fields.hasCount()) { - count(); - } + builder.fields.addCountWhenNotPresented(); return executor.toInteger(); } @@ -625,91 +679,91 @@ public virtual inherited sharing class SOQL implements Queryable { private List clauses = new QueryClause[10]; public QueryBuilder(String ofObject) { - clauses.set(0, new QFields()); - clauses.set(2, new QFrom(ofObject)); + clauses.set(0, new SoqlFields()); + clauses.set(2, new SoqlFrom(ofObject)); } - public QFields fields { + public SoqlFields fields { get { - return (QFields) clauses[0]; + return (SoqlFields) clauses[0]; } } - public QSubQueries subQueries { + public SoqlSubQueries subQueries { get { if (clauses[1] == null) { - clauses.set(1, new QSubQueries()); + clauses.set(1, new SoqlSubQueries()); } - return (QSubQueries) clauses[1]; + return (SoqlSubQueries) clauses[1]; } } - public QScope scope { + public SoqlScope scope { get { if (clauses[3] == null) { - clauses.set(3, new QScope()); + clauses.set(3, new SoqlScope()); } - return (QScope) clauses[3]; + return (SoqlScope) clauses[3]; } } - public QMainFilterGroup conditions { + public MainFilterGroup conditions { get { if (clauses[4] == null) { - clauses.set(4, new QMainFilterGroup()); + clauses.set(4, new MainFilterGroup()); } - return (QMainFilterGroup) clauses[4]; + return (MainFilterGroup) clauses[4]; } } - public QGroupBy groupBy { + public SoqlGroupBy groupBy { get { if (clauses[5] == null) { - clauses.set(5, new QGroupBy()); + clauses.set(5, new SoqlGroupBy()); } - return (QGroupBy) clauses[5]; + return (SoqlGroupBy) clauses[5]; } } - public QOrderBy latestOrderBy { + public SoqlOrderBy latestOrderBy { get { - return orderBys.recentOrderBy(); + return orderBys.latestOrderBy(); } } - public QOrderBys orderBys { + public SoqlOrderBys orderBys { get { if (clauses[6] == null) { - clauses.set(6, new QOrderBys()); + clauses.set(6, new SoqlOrderBys()); } - return (QOrderBys) clauses[6]; + return (SoqlOrderBys) clauses[6]; } } - public QLimit soqlLimit { + public SoqlLimit soqlLimit { get { if (clauses[7] == null) { - clauses.set(7, new QLimit()); + clauses.set(7, new SoqlLimit()); } - return (QLimit) clauses[7]; + return (SoqlLimit) clauses[7]; } } - public QOffset soqlOffset { + public SoqlOffset soqlOffset { get { if (clauses[8] == null) { - clauses.set(8, new QOffset()); + clauses.set(8, new SoqlOffset()); } - return (QOffset) clauses[8]; + return (SoqlOffset) clauses[8]; } } - public QFor soqlFor { + public SoqlFor soqlFor { get { if (clauses[9] == null) { - clauses.set(9, new QFor()); + clauses.set(9, new SoqlFor()); } - return (QFor) clauses[9]; + return (SoqlFor) clauses[9]; } } @@ -727,49 +781,75 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QFields implements QueryClause { + private class SoqlFields implements QueryClause { private Set fields = new Set(); - private Set aggregatedFields = new Set(); + private Set aggregateFunctions = new Set(); + private Set groupedFields = new Set(); public void count() { - count('COUNT()'); + clearAllFields(); // COUNT() must be the only element in the SELECT list. + withAggregateFunction('COUNT()', ''); } - public void count(SObjectField field) { - count('COUNT(' + field + ')'); + public void count(SObjectField field, String alias) { + withAggregateFunction('COUNT(' + field + ')', alias); } - public void count(SObjectField field, String alias) { - count('COUNT(' + field + ') ' + alias); - fields.add('COUNT(' + field + ') ' + alias); + private void avg(SObjectField field, String alias) { + withAggregateFunction('AVG(' + field + ')', alias); + } + + private void countDistinct(SObjectField field, String alias) { + withAggregateFunction('COUNT_DISTINCT(' + field + ')', alias); + } + + private void min(SObjectField field, String alias) { + withAggregateFunction('MIN(' + field + ')', alias); + } + + private void max(SObjectField field, String alias) { + withAggregateFunction('MAX(' + field + ')', alias); } - private void count(String count) { - withAggregatedField(count); - fields.add(count); + private void sum(SObjectField field, String alias) { + withAggregateFunction('SUM(' + field + ')', alias); } public void grouping(SObjectField field, String alias) { - withAggregatedField('GROUPING(' + field + ') ' + alias); - fields.add('GROUPING(' + field + ') ' + alias); + withAggregateFunction('GROUPING(' + field + ')', alias); } public void with(SObjectField field, String alias) { - withAggregatedField(field + ' ' + alias); - fields.add(field + ' ' + alias); + withAggregateFunction(field.getDescribe().getName(), alias); } - public void withAggregatedField(SObjectField field) { - withAggregatedField(field.getDescribe().getName()); + private void withAggregateFunction(String aggregateFunction, String alias) { + if (String.isNotBlank(alias)) { + aggregateFunction += ' ' + alias; + } + + aggregateFunctions.add(aggregateFunction); } - public void withAggregatedField(String field) { - aggregatedFields.add(field); + public void withGroupedField(SObjectField field) { + groupedFields.add(field.getDescribe().getName()); + } + + public void with(String commaSeparatedFields) { + // Added to Set to avoid field duplicates in query + for (String splittedField : commaSeparatedFields.split(',')) { + String field = splittedField.trim(); + if (isAggregateFunction(field)) { + aggregateFunctions.add(field); + } else { + fields.add(field); + } + } } - public void with(String stringFields) { - // To avoid field duplicates in query - fields.addAll(stringFields.deleteWhitespace().split(',')); + private Boolean isAggregateFunction(String field) { + // AVG(), COUNT(), MIN(), MAX(), SUM() or Field aliasing + return field.contains('(') && field.contains(')') || field.contains(' '); } public void with(List fields) { @@ -794,36 +874,45 @@ public virtual inherited sharing class SOQL implements Queryable { public void clearAllFields() { fields.clear(); + aggregateFunctions.clear(); } - public Boolean hasCount() { - return !aggregatedFields.isEmpty(); + public void addCountWhenNotPresented() { + if (aggregateFunctions.isEmpty()) { + count(); + } } public override String toString() { - removeNotAggregatedFieldsFromAggregateSoql(); - - if (fields.isEmpty()) { + if (fields.isEmpty() && aggregateFunctions.isEmpty()) { return 'SELECT Id'; } + if (!groupedFields.isEmpty() || !aggregateFunctions.isEmpty()) { + List selectFields = new List(); + + removeNotGroupedFields(); + + selectFields.addAll(fields); + selectFields.addAll(aggregateFunctions); + + return 'SELECT ' + String.join(selectFields, ', '); + } + return 'SELECT ' + String.join(fields, ', '); } - public void removeNotAggregatedFieldsFromAggregateSoql() { - if (aggregatedFields.isEmpty()) { - return; - } - // Clear not grouped or aggregated fields to avoid "Field must be grouped or aggregated" error + public void removeNotGroupedFields() { + // To avoid "Field must be grouped or aggregated" error for (String field : fields) { - if (!aggregatedFields.contains(field)) { + if (!groupedFields.contains(field)) { fields.remove(field); } } } } - private class QSubQuery implements SubQuery { + private class SoqlSubQuery implements SubQuery { private QueryBuilder builder; public SubQuery of(String ofObject) { @@ -922,7 +1011,7 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QSubQueries implements QueryClause { + private class SoqlSubQueries implements QueryClause { private List subQueries = new List(); public void add(SubQuery subQuery) { @@ -940,10 +1029,10 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QFrom implements QueryClause { + private class SoqlFrom implements QueryClause { private String objectApiName; - public QFrom(String objectType) { + public SoqlFrom(String objectType) { objectApiName = objectType; } @@ -952,7 +1041,7 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QScope implements QueryClause { + private class SoqlScope implements QueryClause { private String scope = 'EVERYTHING'; public void delegated() { @@ -988,7 +1077,7 @@ public virtual inherited sharing class SOQL implements Queryable { Boolean isEmpty(); } - private virtual class QFilterGroup implements FilterGroup { + private virtual class SoqlFilterGroup implements FilterGroup { private List queryConditions = new List(); private String order; private String connector = 'AND'; @@ -1023,6 +1112,13 @@ public virtual inherited sharing class SOQL implements Queryable { return this; } + public FilterGroup ignoreWhen(Boolean logicExpression) { + if (logicExpression) { + queryConditions = new List(); + } + return this; + } + public Boolean hasValues() { return !queryConditions.isEmpty(); } @@ -1059,7 +1155,7 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QMainFilterGroup extends QFilterGroup implements QueryClause { + private class MainFilterGroup extends SoqlFilterGroup implements QueryClause { public override String toString() { if (!hasValues()) { return ''; @@ -1117,7 +1213,7 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QFilter implements Filter { + private class SoqlFilter implements Filter { private String field; private String comperator; private Object value; @@ -1226,7 +1322,7 @@ public virtual inherited sharing class SOQL implements Queryable { } private String formattedString(String value) { - return value == null ? value : String.escapeSingleQuotes(value.trim()); + return value == null ? value : value.trim(); } public Filter isIn(Iterable iterable) { @@ -1309,7 +1405,7 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QJoinQuery implements InnerJoin { + private class SoqlJoinQuery implements InnerJoin { private QueryBuilder builder; public InnerJoin of(SObjectType ofObject) { @@ -1337,7 +1433,7 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QGroupBy implements QueryClause { + private class SoqlGroupBy implements QueryClause { private Set groupByFields = new Set(); private String groupByFunction = ''; @@ -1370,22 +1466,22 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QOrderBys implements QueryClause { - public List orderBys = new List(); + private class SoqlOrderBys implements QueryClause { + public List orderBys = new List(); - public QOrderBy newOrderBy() { - orderBys.add(new QOrderBy()); - return recentOrderBy(); + public SoqlOrderBy newOrderBy() { + orderBys.add(new SoqlOrderBy()); + return latestOrderBy(); } - public QOrderBy recentOrderBy() { + public SoqlOrderBy latestOrderBy() { return orderBys.get(orderBys.size() - 1); } public override String toString() { List orderFields = new List(); - for (QOrderBy orderBy : orderBys) { + for (SoqlOrderBy orderBy : orderBys) { orderFields.add(orderBy.toString()); } @@ -1393,7 +1489,7 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QOrderBy implements QueryClause { + private class SoqlOrderBy implements QueryClause { private String orderField; private String sortingOrder = 'ASC'; private String nullsOrder = 'FIRST'; @@ -1406,7 +1502,7 @@ public virtual inherited sharing class SOQL implements Queryable { with(relationshipName + '.' + field); } - public QOrderBy with(String field) { + public SoqlOrderBy with(String field) { orderField = field; return this; } @@ -1428,7 +1524,7 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QLimit implements QueryClause { + private class SoqlLimit implements QueryClause { private Integer soqlLimit; public void max(Integer soqlLimit) { @@ -1440,7 +1536,7 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QOffset implements QueryClause { + private class SoqlOffset implements QueryClause { private Integer soqlOffset; public void offset(Integer fromRow) { @@ -1452,7 +1548,7 @@ public virtual inherited sharing class SOQL implements Queryable { } } - private class QFor implements QueryClause { + private class SoqlFor implements QueryClause { private String forStatement; public void forReference() { diff --git a/force-app/main/default/classes/SOQL_Test.cls b/force-app/main/default/classes/SOQL_Test.cls index fea1095e..760185b2 100644 --- a/force-app/main/default/classes/SOQL_Test.cls +++ b/force-app/main/default/classes/SOQL_Test.cls @@ -41,6 +41,18 @@ private class SOQL_Test { Assert.areEqual('SELECT COUNT() FROM Account', soql); } + @IsTest + static void countWithDefaultFields() { + // Test + String soql = SOQL.of(Account.SObjectType) + .with(Account.Id, Account.Name) + .count() + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT() FROM Account', soql); + } + @IsTest static void countField() { // Test @@ -53,6 +65,19 @@ private class SOQL_Test { Assert.areEqual('SELECT COUNT(Id), COUNT(CampaignId) FROM Opportunity', soql); } + @IsTest + static void countFieldWithDefaultFields() { + // Test + String soql = SOQL.of(Opportunity.SObjectType) + .with(Opportunity.LeadSource) + .count(Opportunity.Id) + .count(Opportunity.CampaignId) + .toString(); + + // Verify + Assert.areEqual('SELECT COUNT(Id), COUNT(CampaignId) FROM Opportunity', soql); + } + @IsTest static void countWithAlias() { // Test @@ -62,6 +87,122 @@ private class SOQL_Test { Assert.areEqual('SELECT COUNT(Name) names FROM Account', soql); } + @IsTest + static void avg() { + // Test + String soql = SOQL.of(Opportunity.SObjectType) + .with(Opportunity.CampaignId) + .avg(Opportunity.Amount) + .groupBy(Opportunity.CampaignId) + .toString(); + + // Verify + Assert.areEqual('SELECT CampaignId, AVG(Amount) FROM Opportunity GROUP BY CampaignId', soql); + } + + @IsTest + static void avgWithAlias() { + // Test + String soql = SOQL.of(Opportunity.SObjectType) + .with(Opportunity.CampaignId) + .avg(Opportunity.Amount, 'amount') + .groupBy(Opportunity.CampaignId) + .toString(); + + // Verify + Assert.areEqual('SELECT CampaignId, AVG(Amount) amount FROM Opportunity GROUP BY CampaignId', soql); + } + + @IsTest + static void countDistinct() { + // Test + String soql = SOQL.of(Lead.SObjectType).countDistinct(Lead.Company).toString(); + + // Verify + Assert.areEqual('SELECT COUNT_DISTINCT(Company) FROM Lead', soql); + } + + @IsTest + static void countDistinctWithAlias() { + // Test + String soql = SOQL.of(Lead.SObjectType).countDistinct(Lead.Company, 'company').toString(); + + // Verify + Assert.areEqual('SELECT COUNT_DISTINCT(Company) company FROM Lead', soql); + } + + @IsTest + static void min() { + // Test + String soql = SOQL.of(Contact.SObjectType) + .with(Contact.FirstName, Contact.LastName) + .min(Contact.CreatedDate) + .groupBy(Contact.FirstName) + .groupBy(Contact.LastName) + .toString(); + + // Verify + Assert.areEqual('SELECT FirstName, LastName, MIN(CreatedDate) FROM Contact GROUP BY FirstName, LastName', soql); + } + + @IsTest + static void minWithAlias() { + // Test + String soql = SOQL.of(Contact.SObjectType) + .with(Contact.FirstName, Contact.LastName) + .min(Contact.CreatedDate, 'createdDate') + .groupBy(Contact.FirstName) + .groupBy(Contact.LastName) + .toString(); + + // Verify + Assert.areEqual('SELECT FirstName, LastName, MIN(CreatedDate) createdDate FROM Contact GROUP BY FirstName, LastName', soql); + } + + @IsTest + static void max() { + // Test + String soql = SOQL.of(Campaign.SObjectType) + .with(Campaign.Name) + .max(Campaign.BudgetedCost) + .groupBy(Campaign.Name) + .toString(); + + // Verify + Assert.areEqual('SELECT Name, MAX(BudgetedCost) FROM Campaign GROUP BY Name', soql); + } + + @IsTest + static void maxWithAlias() { + // Test + String soql = SOQL.of(Campaign.SObjectType) + .with(Campaign.Name) + .max(Campaign.BudgetedCost, 'budgetedCost') + .groupBy(Campaign.Name) + .toString(); + + // Verify + Assert.areEqual('SELECT Name, MAX(BudgetedCost) budgetedCost FROM Campaign GROUP BY Name', soql); + } + + @IsTest + static void sum() { + // Test + String soql = SOQL.of(Opportunity.SObjectType).sum(Opportunity.Amount).toString(); + + // Verify + Assert.areEqual('SELECT SUM(Amount) FROM Opportunity', soql); + } + + @IsTest + static void sumWithAlias() { + // Test + String soql = SOQL.of(Opportunity.SObjectType).sum(Opportunity.Amount, 'amount').toString(); + + // Verify + Assert.areEqual('SELECT SUM(Amount) amount FROM Opportunity', soql); + } + @IsTest static void grouping() { // Test @@ -179,6 +320,18 @@ private class SOQL_Test { Assert.areEqual('SELECT Id, Name, BillingCity FROM Account', soql); } + @IsTest + static void withStringAggregationAndGroupingFields() { + // Test + String soql = SOQL.of(Opportunity.SObjectType) + .with('CampaignId campaign, AVG(Amount) amount') + .groupBy(Opportunity.CampaignId) + .toString(); + + // Verify + Assert.areEqual('SELECT CampaignId campaign, AVG(Amount) amount FROM Opportunity GROUP BY CampaignId', soql); + } + @IsTest static void withFieldAlias() { // Test @@ -1272,7 +1425,7 @@ private class SOQL_Test { } @IsTest - static void ignoreWhen() { + static void ignoreWhenFilter() { // Setup String accountName = ''; @@ -1288,6 +1441,31 @@ private class SOQL_Test { Assert.areEqual('SELECT Id FROM Account WHERE (BillingCity = :v1)', soql); } + @IsTest + static void ignoreWhenFilterGroup() { + // Setup + Boolean isPartnerUser = false; + + // Test + String soql = SOQL.of(Account.SObjectType) + .whereAre(SOQL.FilterGroup + .add(SOQL.FilterGroup + .add(SOQL.Filter.with(Account.BillingCity).equal('Krakow')) + .add(SOQL.Filter.with(Account.BillingCity).equal('Warsaw')) + .anyConditionMatching() + .ignoreWhen(!isPartnerUser) + ) + .add(SOQL.FilterGroup + .add(SOQL.Filter.with(Account.Industry).equal('IT')) + .add(SOQL.Filter.name().contains('MyAcccount')) + ) + ) + .toString(); + + // Verify + Assert.areEqual('SELECT Id FROM Account WHERE ((Industry = :v1 AND Name LIKE :v2))', soql); + } + @IsTest static void groupBy() { // Test @@ -1310,7 +1488,7 @@ private class SOQL_Test { .toString(); // Verify - Assert.areEqual('SELECT COUNT(Name) cnt, LeadSource FROM Lead GROUP BY ROLLUP(LeadSource)', soql); + Assert.areEqual('SELECT LeadSource, COUNT(Name) cnt FROM Lead GROUP BY ROLLUP(LeadSource)', soql); } @IsTest @@ -1778,7 +1956,7 @@ private class SOQL_Test { @IsTest static void toValuesOfWhenNoValues() { // Setup - insertAccounts(); + insertAccounts(); // Industry is empty // Test Set accountNames = SOQL.of(Account.SObjectType).toValuesOf(Account.Industry); @@ -1878,7 +2056,7 @@ private class SOQL_Test { } @IsTest - static void toIntegerWithoutCount() { + static void toIntegerWithoutSpecifiedCount() { // Setup List accounts = insertAccounts(); diff --git a/force-app/main/default/classes/example/SOQL_Account.cls b/force-app/main/default/classes/example/SOQL_Account.cls index 783e30e8..75323515 100644 --- a/force-app/main/default/classes/example/SOQL_Account.cls +++ b/force-app/main/default/classes/example/SOQL_Account.cls @@ -1,4 +1,6 @@ public inherited sharing class SOQL_Account extends SOQL implements SOQL.Selector { + public final String MOCK_ID = 'SOQL_Account'; + public static SOQL_Account query() { return new SOQL_Account(); } @@ -6,9 +8,10 @@ public inherited sharing class SOQL_Account extends SOQL implements SOQL.Selecto private SOQL_Account() { super(Account.SObjectType); // default settings - with(Account.Id, Account.Name, Account.Type) - .systemMode() - .withoutSharing(); + with(Account.Id, Account.Name, Account.Type); + systemMode(); + withoutSharing(); + mockId(MOCK_ID); } public SOQL_Account byRecordType(String rt) { diff --git a/force-app/main/default/classes/example/SOQL_Contact.cls b/force-app/main/default/classes/example/SOQL_Contact.cls index 1586dc02..f4e753dd 100644 --- a/force-app/main/default/classes/example/SOQL_Contact.cls +++ b/force-app/main/default/classes/example/SOQL_Contact.cls @@ -1,4 +1,6 @@ public inherited sharing class SOQL_Contact extends SOQL implements SOQL.Selector { + public final String MOCK_ID = 'SOQL_Contact'; + public static SOQL_Contact query() { return new SOQL_Contact(); } @@ -6,9 +8,10 @@ public inherited sharing class SOQL_Contact extends SOQL implements SOQL.Selecto private SOQL_Contact() { super(Contact.SObjectType); // default settings - with(Contact.Id, Contact.Name, Contact.AccountId) - .systemMode() - .withoutSharing(); + with(Contact.Id, Contact.Name, Contact.AccountId); + systemMode(); + withoutSharing(); + mockId(MOCK_ID); } public SOQL_Contact byRecordType(String rt) { diff --git a/force-app/main/default/classes/example/SOQL_Opportunity.cls b/force-app/main/default/classes/example/SOQL_Opportunity.cls index 39d1395c..234440ef 100644 --- a/force-app/main/default/classes/example/SOQL_Opportunity.cls +++ b/force-app/main/default/classes/example/SOQL_Opportunity.cls @@ -1,4 +1,6 @@ public inherited sharing class SOQL_Opportunity extends SOQL implements SOQL.Selector { + public final String MOCK_ID = 'SOQL_Opportunity'; + public static SOQL_Opportunity query() { return new SOQL_Opportunity(); } @@ -7,6 +9,7 @@ public inherited sharing class SOQL_Opportunity extends SOQL implements SOQL.Sel super(Opportunity.SObjectType); // default settings with(Opportunity.Id, Opportunity.AccountId); + mockId(MOCK_ID); } public SOQL_Opportunity byAccountId(Id accountId) { diff --git a/website/docs/api/soql-filters-group.md b/website/docs/api/soql-filters-group.md index 493f93b6..b5652ecf 100644 --- a/website/docs/api/soql-filters-group.md +++ b/website/docs/api/soql-filters-group.md @@ -21,6 +21,10 @@ The following are methods for `FilterGroup`. - [`anyConditionMatching()`](#anyconditionmatching) - [`conditionLogic(String order)`](#conditionlogic) +[**ADDITIONAL**](#additional) + +- [`ignoreWhen(Boolean logicExpression)`](#ignorewhen) + ## ADD CONDITION ### add @@ -135,3 +139,42 @@ SOQL.of(Account.SObjectType) .anyConditionMatching() ).toList(); ``` + +## ADDITIONAL + +### ignoreWhen + +All group's conditions will be removed when logic expression will evaluate to true. + +**Signature** + +```apex +FilterGroup ignoreWhen(Boolean logicExpression); +``` + +**Example** + +```sql +SELECT Id +FROM Account +WHERE Industry = 'IT' AND Name LIKE '%MyAccount%' +``` + +```apex +Boolean isPartnerUser = false; + +SOQL.of(Account.SObjectType) + .whereAre(SOQL.FilterGroup + .add(SOQL.FilterGroup + .add(SOQL.Filter.with(Account.BillingCity).equal('Krakow')) + .add(SOQL.Filter.with(Account.BillingCity).equal('Warsaw')) + .anyConditionMatching() + .ignoreWhen(!isPartnerUser) + ) + .add(SOQL.FilterGroup + .add(SOQL.Filter.with(Account.Industry).equal('IT')) + .add(SOQL.Filter.name().contains('MyAcccount')) + ) + ) + .toList(); +``` diff --git a/website/docs/api/soql.md b/website/docs/api/soql.md index b0cc4a81..98b4450d 100644 --- a/website/docs/api/soql.md +++ b/website/docs/api/soql.md @@ -32,11 +32,21 @@ The following are methods for `SOQL`. - [`with(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5)`](#with-related-field1---field5) - [`with(String relationshipName, List fields)`](#with-related-fields) -[**COUNT**](#count) +[**AGGREGATION FUNCTIONS**](#aggregate-functions) - [`count()`](#count) - [`count(SObjectField field)`](#count-field) - [`count(SObjectField field, String alias)`](#count-with-alias) +- [`avg(SObjectField field)`](#avg) +- [`avg(SObjectField field, String alias)`](#avg-with-alias) +- [`countDistinct(SObjectField field)`](#count_distinct) +- [`countDistinct(SObjectField field, String alias)`](#count_distinct-with-alias) +- [`min(SObjectField field)`](#min) +- [`min(SObjectField field, String alias)`](#min-with-alias) +- [`max(SObjectField field)`](#max) +- [`max(SObjectField field, String alias)`](#max-with-alias) +- [`sum(SObjectField field)`](#sum) +- [`sum(SObjectField field, String alias)`](#sum-with-alias) [**GROUPING**](#grouping) @@ -369,7 +379,9 @@ SOQL.of(Account.SObjectType) ).toList(); ``` -## COUNT-QUERY +## [AGGREGATE FUNCTIONS](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_agg_functions.htm) + +**Note!** To avoid the `Field must be grouped or aggregated` error, any default fields that are neither in Aggregation Functions nor included in the [GROUP BY](#group-by) clause will be automatically removed. ### count @@ -430,10 +442,6 @@ FROM Opportunity count(SObjectField field, String alias) ``` -**Note!** To avoid the `Field must be grouped or aggregated` error, any default fields will be automatically removed. - -You can still specify additional fields, but they should be placed after the COUNT() function in the SELECT statement. - **Example** ```sql @@ -445,6 +453,210 @@ SOQL.of(Account.SObjectType) .toAggregated(); ``` +### avg + +**Signature** + +```apex +Queryable avg(SObjectField field) +``` + +**Example** + +```sql +SELECT CampaignId, AVG(Amount) FROM Opportunity GROUP BY CampaignId +``` +```apex +SOQL.of(Opportunity.SObjectType) + .with(Opportunity.CampaignId) + .avg(Opportunity.Amount) + .groupBy(Opportunity.CampaignId) + .toAggregate(); +``` + +### avg with alias + +**Signature** + +```apex +Queryable avg(SObjectField field, String alias) +``` + +**Example** + +```sql +SELECT CampaignId, AVG(Amount) amount FROM Opportunity GROUP BY CampaignId +``` +```apex +SOQL.of(Opportunity.SObjectType) + .with(Opportunity.CampaignId) + .avg(Opportunity.Amount, 'amount') + .groupBy(Opportunity.CampaignId) + .toAggregate(); +``` + +### count_distinct + +**Signature** + +```apex +Queryable countDistinct(SObjectField field +``` + +**Example** + +```sql +SELECT COUNT_DISTINCT(Company) FROM Lead +``` +```apex +SOQL.of(Lead.SObjectType).countDistinct(Lead.Company).toAggregate(); +``` + +### count_distinct with alias + +**Signature** + +```apex +Queryable countDistinct(SObjectField field, String alias) +``` + +**Example** + +```sql +SELECT COUNT_DISTINCT(Company) company FROM Lead +``` +```apex +SOQL.of(Lead.SObjectType).countDistinct(Lead.Company, 'company').toAggregate(); +``` + +### min + +**Signature** + +```apex +Queryable min(SObjectField field) +``` + +**Example** + +```sql +SELECT FirstName, LastName, MIN(CreatedDate) +FROM Contact +GROUP BY FirstName, LastName +``` +```apex +SOQL.of(Contact.SObjectType) + .with(Contact.FirstName, Contact.LastName) + .min(Contact.CreatedDate) + .groupBy(Contact.FirstName) + .groupBy(Contact.LastName) + .toAggregate(); +``` + +### min with alias + +**Signature** + +```apex +Queryable min(SObjectField field, String alias) +``` + +**Example** + +```sql +SELECT FirstName, LastName, MIN(CreatedDate) createDate +FROM Contact +GROUP BY FirstName, LastName +``` +```apex +SOQL.of(Contact.SObjectType) + .with(Contact.FirstName, Contact.LastName) + .min(Contact.CreatedDate, 'createDate') + .groupBy(Contact.FirstName) + .groupBy(Contact.LastName) + .toAggregate(); +``` + +### max + +**Signature** + +```apex +Queryable max(SObjectField field) +``` + +**Example** + +```sql +SELECT Name, MAX(BudgetedCost) +FROM Campaign +GROUP BY Name +``` +```apex + SOQL.of(Campaign.SObjectType) + .with(Campaign.Name) + .max(Campaign.BudgetedCost) + .groupBy(Campaign.Name) + .toAggregate(); +``` + +### max with alias + +**Signature** + +```apex +Queryable max(SObjectField field, String alias) +``` + +**Example** + +```sql +SELECT Name, MAX(BudgetedCost) budgetedCost +FROM Campaign +GROUP BY Name +``` +```apex + SOQL.of(Campaign.SObjectType) + .with(Campaign.Name) + .max(Campaign.BudgetedCost, 'budgetedCost') + .groupBy(Campaign.Name) + .toAggregate(); +``` + +### sum + +**Signature** + +```apex +Queryable sum(SObjectField field) +``` + +**Example** + +```sql +SELECT SUM(Amount) FROM Opportunity +``` +```apex +SOQL.of(Opportunity.SObjectType).sum(Opportunity.Amount).toAggregate(); +``` + +### sum with alias + +**Signature** + +```apex +Queryable sum(SObjectField field, String alias) +``` + +**Example** + +```sql +SELECT SUM(Amount) amount FROM Opportunity +``` +```apex +SOQL.of(Opportunity.SObjectType).sum(Opportunity.Amount, 'amount').toAggregate(); +``` + ## GROUPING ### grouping