Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/2.2.2 [IN PROGRESS] #83

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 56 additions & 7 deletions force-app/main/default/classes/SOQL.cls
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public virtual inherited sharing class SOQL implements Queryable {
// GROUP BY
Queryable groupBy(SObjectField field);
Queryable groupByRollup(SObjectField field);
Queryable groupByCube(SObjectField field);
// ORDER BY
Queryable orderBy(String field);
Queryable orderBy(String field, String direction);
Expand Down Expand Up @@ -403,11 +404,19 @@ public virtual inherited sharing class SOQL implements Queryable {

public SOQL groupBy(SObjectField field) {
builder.groupBy.with(field);
builder.fields.withAggregatedField(field);
return this;
}

public SOQL groupByRollup(SObjectField field) {
builder.groupBy.rollup(field);
builder.fields.withAggregatedField(field);
return this;
}

public SOQL groupByCube(SObjectField field) {
builder.groupBy.cube(field);
builder.fields.withAggregatedField(field);
return this;
}

Expand Down Expand Up @@ -685,32 +694,41 @@ public virtual inherited sharing class SOQL implements Queryable {
private class QFields implements QueryClause {
private Set<String> fields = new Set<String>();
private Set<String> counts = new Set<String>();
private Set<String> aggregatedFields = new Set<String>();

public void count() {
// COUNT() must be the only element in the SELECT list.
clearAllFields();
count('COUNT()');
}

public void count(SObjectField field) {
withAggregatedField(field);
count('COUNT(' + field + ')');
}

public void count(SObjectField field, String alias) {
withAggregatedField(field);
count('COUNT(' + field + ') ' + alias);
}

public void count(String countSoql) {
// Clear all default fields to avoid "Field must be grouped or aggregated"
clearAllFields();
private void count(String countSoql) {
counts.add(countSoql);
}

public void with(SObjectField field, String alias) {
// Only aggregate expressions use field aliasing. Clear all default fields to avoid "Field must be grouped or aggregated"
clearAllFields();
withAggregatedField(field + ' ' + alias);
fields.add(field + ' ' + alias);
}

public void withAggregatedField(SObjectField field) {
withAggregatedField(field.getDescribe().getName());
}

public void withAggregatedField(String field) {
aggregatedFields.add(field);
}

public void with(String stringFields) {
// To avoid field duplicates in query
fields.addAll(stringFields.deleteWhitespace().split(','));
Expand Down Expand Up @@ -745,6 +763,8 @@ public virtual inherited sharing class SOQL implements Queryable {
}

public override String toString() {
removeNotAggregatedFieldsFromAggregateSoql();

if (fields.isEmpty() && counts.isEmpty()) {
return 'SELECT Id';
}
Expand All @@ -756,6 +776,18 @@ public virtual inherited sharing class SOQL implements Queryable {

return 'SELECT ' + String.join(selectStatement, ', ');
}

public void removeNotAggregatedFieldsFromAggregateSoql() {
if (aggregatedFields.isEmpty()) {
return;
}
// Clear not grouped or aggregated fields to avoid "Field must be grouped or aggregated" error
for (String field : fields) {
if (!aggregatedFields.contains(field)) {
fields.remove(field);
}
}
}
}

private class QSubQuery implements SubQuery {
Expand Down Expand Up @@ -1274,17 +1306,34 @@ public virtual inherited sharing class SOQL implements Queryable {

private class QGroupBy implements QueryClause {
private Set<String> groupByFields = new Set<String>();
private String groupByFunction = '';

public void with(SObjectField field) {
setGroupByFunction('{0}');
groupByFields.add(field.getDescribe().getName());
}

public void rollup(SObjectField field) {
groupByFields.add('ROLLUP(' + field + ')');
setGroupByFunction('ROLLUP({0})');
groupByFields.add(field.getDescribe().getName());
}

public void cube(SObjectField field) {
setGroupByFunction('CUBE({0})');
groupByFields.add(field.getDescribe().getName());
}

public void setGroupByFunction(String newGroupByFunction) {
if (String.isNotEmpty(groupByFunction) && groupByFunction != newGroupByFunction) {
QueryException e = new QueryException();
e.setMessage('You cant combine GROUP BY, GROUP BY ROLLUP and GROUP BY CUBE syntax in the same statement.');
throw e;
}
this.groupByFunction = newGroupByFunction;
}

public override String toString() {
return 'GROUP BY ' + String.join(groupByFields, ', ');
return 'GROUP BY ' + String.format(groupByFunction, new List<String> { String.join(groupByFields, ', ') });
pgajek2 marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
35 changes: 35 additions & 0 deletions force-app/main/default/classes/SOQL_Test.cls
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,41 @@ private class SOQL_Test {
Assert.areEqual('SELECT COUNT(Name) cnt, LeadSource FROM Lead GROUP BY ROLLUP(LeadSource)', soql);
}

@IsTest
static void groupByCube() {
// Test
String soql = SOQL.of(Account.SObjectType)
.with(Account.Type)
.groupByCube(Account.Type)
.toString();

// Verify
Assert.areEqual('SELECT Type FROM Account GROUP BY CUBE(Type)', soql);
}

@IsTest
static void differentGroupByFunctionsException() {
// Setup
Exception queryException = null;

// Test
try {
String soql = SOQL.of(Account.SObjectType)
.with(Account.Type)
.groupBy(Account.Type)
.groupByCube(Account.Type)
.toString();
} catch(Exception e) {
queryException = e;
}

// Verify
Assert.areEqual(
'You cant combine GROUP BY, GROUP BY ROLLUP and GROUP BY CUBE syntax in the same statement.',
queryException.getMessage()
);
}

@IsTest
static void orderByString() {
// Test
Expand Down
25 changes: 24 additions & 1 deletion website/docs/api/soql.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ The following are methods for `SOQL`.

- [`groupBy(SObjectField field)`](#group-by)
- [`groupByRollup(SObjectField field)`](#groupbyrollup)
- [`groupByCube(SObjectField field)`](#groupbycube)

[**ORDER BY**](#order-by)

Expand Down Expand Up @@ -726,13 +727,35 @@ FROM Lead
GROUP BY ROLLUP(LeadSource)
```
```apex
QS.of(Lead.SObjectType)
SOQL.of(Lead.SObjectType)
.with(Lead.LeadSource)
.count(Lead.Name, 'cnt')
.groupByRollup(Lead.LeadSource)
.toAggregated();
```

### groupByCube

**Signature**

```apex
SOQL groupByCube(SObjectField field)
```

**Example**

```sql
SELECT Type
FROM Account
GROUP BY ROLLUP(Type)
```
```apex
SOQL.of(Account.SObjectType)
.with(Account.Type)
.groupByCube(Account.Type)
.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)
Expand Down
1 change: 1 addition & 0 deletions website/docs/docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public interface Queryable {

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
Expand Down