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/v3.0.0 #86

Merged
merged 15 commits into from
Oct 10, 2023
221 changes: 185 additions & 36 deletions force-app/main/default/classes/SOQL.cls
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public virtual inherited sharing class SOQL implements Queryable {
Queryable count();
Queryable count(SObjectField field);
Queryable count(SObjectField field, String alias);
// GROUPING
Queryable grouping(SObjectField field, String alias);
// USING SCOPE
Queryable delegatedScope();
Queryable mineScope();
Expand All @@ -81,6 +83,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 @@ -123,6 +126,10 @@ public virtual inherited sharing class SOQL implements Queryable {
List<SObject> toList();
List<AggregateResult> toAggregated();
Map<Id, SObject> toMap();
Map<String, SObject> toMap(SObjectField keyField);
Map<String, String> toMap(SObjectField keyField, SObjectField valueField);
Map<String, List<SObject>> toAggregatedMap(SObjectField keyField);
Map<String, List<String>> toAggregatedMap(SObjectField keyField, SObjectField valueField);
Database.QueryLocator toQueryLocator();
}

Expand Down Expand Up @@ -257,8 +264,8 @@ public virtual inherited sharing class SOQL implements Queryable {

public SOQL(String ofObject) {
binder = new Binder();
executor = new Executor();
builder = new QueryBuilder(ofObject);
executor = new Executor(ofObject, builder, binder);
}

public SOQL with(SObjectField field) {
Expand Down Expand Up @@ -346,6 +353,11 @@ public virtual inherited sharing class SOQL implements Queryable {
return this;
}

public SOQL grouping(SObjectField field, String alias) {
builder.fields.grouping(field, alias);
return this;
}

public SOQL delegatedScope() {
builder.scope.delegated();
return this;
Expand Down Expand Up @@ -403,11 +415,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 @@ -526,34 +546,57 @@ public virtual inherited sharing class SOQL implements Queryable {

public Set<String> toValuesOf(SObjectField fieldToExtract) {
// https://salesforce.stackexchange.com/questions/393308/get-a-list-of-one-column-from-a-soql-result
builder.fields.clearAllFields(); // other fields not needed
return new Map<String, SObject>(with(fieldToExtract, 'Id').groupBy(fieldToExtract).toAggregated()).keySet();
}

public Integer toInteger() {
if (builder.fields.areCountsEmpty()) {
if (!builder.fields.hasCount()) {
count();
}
return executor.toInteger(builder.toString(), binder.getBindingMap());
return executor.toInteger();
}

public SObject toObject() {
return executor.toObject(builder.toString(), binder.getBindingMap());
return executor.toObject();
}

public List<SObject> toList() {
return executor.toList(builder.toString(), binder.getBindingMap());
return executor.toList();
}

public List<AggregateResult> toAggregated() {
return (List<AggregateResult>) toList();
}

public Map<Id, SObject> toMap() {
return new Map<Id, SObject>(toList());
return executor.toMap();
}

public Map<String, SObject> toMap(SObjectField keyField) {
with(keyField);
return executor.toMap(keyField);
}

public Map<String, String> toMap(SObjectField keyField, SObjectField valueField) {
builder.fields.clearAllFields(); // other fields not needed
with(keyField, valueField);
return executor.toMap(keyField, valueField);
}

public Map<String, List<SObject>> toAggregatedMap(SObjectField keyField) {
with(keyField);
return executor.toAggregatedMap(keyField);
}

public Map<String, List<String>> toAggregatedMap(SObjectField keyField, SObjectField valueField) {
builder.fields.clearAllFields(); // other fields not needed
with(keyField, valueField);
return executor.toAggregatedMap(keyField, valueField);
}

public Database.QueryLocator toQueryLocator() {
return executor.toQueryLocator(builder.toString(), binder.getBindingMap());
return executor.toQueryLocator();
}

public SOQL byId(SObject record) {
Expand Down Expand Up @@ -684,10 +727,10 @@ 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>();
private Boolean hasCount = false;

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

Expand All @@ -697,20 +740,33 @@ public virtual inherited sharing class SOQL implements Queryable {

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

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

public void grouping(SObjectField field, String alias) {
withAggregatedField('GROUPING(' + field + ') ' + alias);
fields.add('GROUPING(' + field + ') ' + alias);
}

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 @@ -740,21 +796,30 @@ public virtual inherited sharing class SOQL implements Queryable {
fields.clear();
}

public Boolean areCountsEmpty() {
return counts.isEmpty();
public Boolean hasCount() {
return hasCount;
}

public override String toString() {
if (fields.isEmpty() && counts.isEmpty()) {
removeNotAggregatedFieldsFromAggregateSoql();

if (fields.isEmpty()) {
return 'SELECT Id';
}

List<String> selectStatement = new List<String>();

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

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);
}
}
}
}

Expand Down Expand Up @@ -1237,10 +1302,10 @@ public virtual inherited sharing class SOQL implements Queryable {

public override String toString() {
if (skipBinding) {
return String.format(wrapper, new List<String> { field + ' ' + comperator + ' ' + value });
return String.format(wrapper, new List<String>{ field + ' ' + comperator + ' ' + value });
}

return String.format(wrapper, new List<String> { field + ' ' + comperator + ' :' + binder.bind(value) });
return String.format(wrapper, new List<String>{ field + ' ' + comperator + ' :' + binder.bind(value) });
}
}

Expand Down Expand Up @@ -1274,17 +1339,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 use GROUP BY, GROUP BY ROLLUP and GROUP BY CUBE in the same query.');
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, ', ') });
}
}

Expand Down Expand Up @@ -1445,6 +1527,15 @@ public virtual inherited sharing class SOQL implements Queryable {
private DatabaseQuery sharingExecutor = new InheritedSharing();
private AccessType accessType;
private String mockId;
private String ofObject;
private QueryBuilder builder;
private Binder binder;

public Executor(String ofObject, QueryBuilder builder, Binder binder) {
this.ofObject = ofObject;
this.builder = builder;
this.binder = binder;
}

public void withSharing() {
sharingExecutor = new WithSharing();
Expand All @@ -1466,8 +1557,8 @@ public virtual inherited sharing class SOQL implements Queryable {
mockId = id;
}

public SObject toObject(String query, Map<String, Object> binding) {
List<SObject> records = toList(query, binding);
public SObject toObject() {
List<SObject> records = toList();

if (records.size() > 1) {
QueryException e = new QueryException();
Expand All @@ -1482,31 +1573,89 @@ public virtual inherited sharing class SOQL implements Queryable {
return records[0];
}

public List<SObject> toList(String query, Map<String, Object> binding) {
public List<SObject> toList() {
if (mock.hasMock(mockId)) {
return mock.getSObjectsMock(mockId);
}

if (accessType == null) {
return sharingExecutor.toSObjects(query, binding, accessMode);
return sharingExecutor.toSObjects(builder.toString(), binder.getBindingMap(), accessMode);
}

return Security.stripInaccessible(
accessType,
sharingExecutor.toSObjects(query, binding, accessMode)
sharingExecutor.toSObjects(builder.toString(), binder.getBindingMap(), accessMode)
).getRecords();
}

public Integer toInteger(String query, Map<String, Object> binding) {
public Map<Id, SObject> toMap() {
Map<Id, SObject> recordPerId = (Map<Id, SObject>) Type.forName('Map<Id, ' + ofObject + ' >').newInstance();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recordToId?

recordPerId.putAll(toList());
return recordPerId;
}

public Map<String, SObject> toMap(SObjectField keyField) {
Map<String, SObject> recordPerCustomKey = (Map<String, SObject>) Type.forName('Map<String, ' + ofObject + ' >').newInstance();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recordToCustomKey?


for (SObject record : toList()) {
recordPerCustomKey.put(String.valueOf(record.get(keyField)), record);
}

return recordPerCustomKey;
}

public Map<String, String> toMap(SObjectField keyField, SObjectField valueField) {
Map<String, String> customValuePerCustomKey = new Map<String, String>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fieldToCustomKey?


for (SObject record : toList()) {
customValuePerCustomKey.put(String.valueOf(record.get(keyField)), String.valueOf(record.get(valueField)));
}

return customValuePerCustomKey;
}

public Map<String, List<SObject>> toAggregatedMap(SObjectField keyField) {
Map<String, List<SObject>> recordsPerCustomKey = (Map<String, List<SObject>>) Type.forName('Map<String, List<' + ofObject + ' >>').newInstance();

for (SObject record : toList()) {
String key = String.valueOf(record.get(keyField));

if (!recordsPerCustomKey.containsKey(key)) {
recordsPerCustomKey.put(key, new List<SObject>());
}

recordsPerCustomKey.get(key).add(record);
}

return recordsPerCustomKey;
}

public Map<String, List<String>> toAggregatedMap(SObjectField keyField, SObjectField valueField) {
Map<String, List<String>> customValuesPerCustomKey = new Map<String, List<String>>();

for (SObject record : toList()) {
String key = String.valueOf(record.get(keyField));

if (!customValuesPerCustomKey.containsKey(key)) {
customValuesPerCustomKey.put(key, new List<String>());
}

customValuesPerCustomKey.get(key).add(String.valueOf(record.get(valueField)));
}

return customValuesPerCustomKey;
}

public Integer toInteger() {
if (mock.hasCountMock(mockId)) {
return mock.getCountMock(mockId);
}

return sharingExecutor.toInteger(query, binding, accessMode);
return sharingExecutor.toInteger(builder.toString(), binder.getBindingMap(), accessMode);
}

public Database.QueryLocator toQueryLocator(String query, Map<String, Object> binding) {
return Database.getQueryLocatorWithBinds(query, binding, accessMode);
public Database.QueryLocator toQueryLocator() {
return Database.getQueryLocatorWithBinds(builder.toString(), binder.getBindingMap(), accessMode);
}
}

Expand Down
Loading