Skip to content

Commit

Permalink
release v2.0.2 [IN PROGRESS] (#54)
Browse files Browse the repository at this point in the history
* Feature/to values of (#52)

* toValuesOf

* refactoring

* refactoring

* refactoring

* documentation update

* field aliasing

* Feature/query exceptions (#53)

* query exception

* refactoring

* Catch only query exception

* code review

* refactoring

* soql error handling
  • Loading branch information
pgajek2 authored Jul 3, 2023
1 parent 07116cf commit 2391490
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 107 deletions.
100 changes: 66 additions & 34 deletions force-app/main/default/classes/SOQL.cls
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public inherited sharing class SOQL implements Queryable {
Queryable with(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5);
Queryable with(List<SObjectField> 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);
Expand Down Expand Up @@ -114,8 +115,9 @@ public inherited sharing class SOQL implements Queryable {
Queryable byIds(List<SObject> records);

String toString();
Object toField(SObjectField fieldToExtract);
Integer toInteger();
Object toValueOf(SObjectField fieldToExtract);
Set<String> toValuesOf(SObjectField fieldToExtract);
Integer toInteger(); // For COUNT query
SObject toObject();
List<SObject> toList();
List<AggregateResult> toAggregated();
Expand Down Expand Up @@ -277,6 +279,11 @@ public inherited sharing class SOQL implements Queryable {
return this;
}

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

public SOQL with(String relationshipName, SObjectField field) {
return with(relationshipName, new List<SObjectField>{ field });
}
Expand Down Expand Up @@ -480,10 +487,16 @@ public inherited sharing class SOQL implements Queryable {
return builder.toString();
}

public Object toField(SObjectField fieldToExtract) {
public Object toValueOf(SObjectField fieldToExtract) {
builder.fields.clearAllFields(); // other fields not needed
return with(fieldToExtract).toObject()?.get(fieldToExtract);
}

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

public Integer toInteger() {
return executor.toInteger(builder.toString(), binder.getBindingMap());
}
Expand Down Expand Up @@ -652,12 +665,19 @@ public inherited sharing class SOQL implements Queryable {
}

public void count(String countSoql) {
// clear all default fields to avoid "Field must be grouped or aggregated"
fields.clear();
// Clear all default fields to avoid "Field must be grouped or aggregated"
clearAllFields();
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();
fields.add(field + ' ' + alias);
}

public void with(String stringFields) {
// To avoid field duplicates in query
fields.addAll(stringFields.deleteWhitespace().split(','));
}

Expand All @@ -681,6 +701,10 @@ public inherited sharing class SOQL implements Queryable {
fields.add(relationshipPath + '.' + field);
}

public void clearAllFields() {
fields.clear();
}

public override String toString() {
if (fields.isEmpty() && counts.isEmpty()) {
return 'SELECT Id';
Expand Down Expand Up @@ -1119,25 +1143,25 @@ public inherited sharing class SOQL implements Queryable {
}

public Filter includesAll(Iterable<String> iterable) {
//Bind expressions can't be used with other clauses, such as INCLUDES.
// Bind expressions can't be used with other clauses, such as INCLUDES.
skipBinding = true;
return set('INCLUDES', '(\'' + String.join(iterable, ';') + '\')');
}

public Filter includesSome(Iterable<String> iterable) {
//Bind expressions can't be used with other clauses, such as INCLUDES.
// Bind expressions can't be used with other clauses, such as INCLUDES.
skipBinding = true;
return set('INCLUDES', '(\'' + String.join(iterable, '\', \'') + '\')');
}

public Filter excludesAll(Iterable<String> iterable) {
//Bind expressions can't be used with other clauses, such as EXCLUDES.
// Bind expressions can't be used with other clauses, such as EXCLUDES.
skipBinding = true;
return set('EXCLUDES', '(\'' + String.join(iterable, '\', \'') + '\')');
}

public Filter excludesSome(Iterable<String> iterable) {
//Bind expressions can't be used with other clauses, such as EXCLUDES.
// Bind expressions can't be used with other clauses, such as EXCLUDES.
skipBinding = true;
return set('EXCLUDES', '(\'' + String.join(iterable, ';') + '\')');
}
Expand Down Expand Up @@ -1341,27 +1365,27 @@ public inherited sharing class SOQL implements Queryable {
}

private class Mock {
private final Map<String, List<SObject>> mocks = new Map<String, List<SObject>>();
private final Map<String, List<SObject>> sObjectsMocks = new Map<String, List<SObject>>();
private final Map<String, Integer> countMocks = new Map<String, Integer>();

public void setMock(String mockId, List<SObject> records) {
mocks.put(mockId, records);
sObjectsMocks.put(mockId, records);
}

public void setCountMock(String mockId, Integer amount) {
countMocks.put(mockId, amount);
}

public Boolean hasMock(String mockId) {
return mocks.containsKey(mockId);
return sObjectsMocks.containsKey(mockId);
}

public Boolean hasCountMock(String mockId) {
return countMocks.containsKey(mockId);
}

public List<SObject> getMocks(String mockId) {
return mocks.get(mockId);
public List<SObject> getSObjectsMock(String mockId) {
return sObjectsMocks.get(mockId);
}

public Integer getCountMock(String mockId) {
Expand Down Expand Up @@ -1396,72 +1420,80 @@ public inherited sharing class SOQL implements Queryable {
}

public SObject toObject(String query, Map<String, Object> binding) {
try {
return toList(query, binding)[0];
} catch (ListException e) {
return null; // List index out of bounds: 0
List<SObject> records = toList(query, binding);

if (records.size() > 1) {
QueryException e = new QueryException();
e.setMessage('List has more than 1 row for assignment to SObject');
throw e;
}
}

public Integer toInteger(String query, Map<String, Object> binding) {
if (mock.hasCountMock(mockId)) {
return mock.getCountMock(mockId);
if (records.size() == 0) {
return null; // handle: List has no rows for assignment to SObject
}

return sharingExecutor.executeCount(query, binding, accessMode);
return records[0];
}

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

if (accessType == null) {
return sharingExecutor.execute(query, binding, accessMode);
return sharingExecutor.toSObjects(query, binding, accessMode);
}

return Security.stripInaccessible(
accessType,
sharingExecutor.execute(query, binding, accessMode)
sharingExecutor.toSObjects(query, binding, accessMode)
).getRecords();
}

public Integer toInteger(String query, Map<String, Object> binding) {
if (mock.hasCountMock(mockId)) {
return mock.getCountMock(mockId);
}

return sharingExecutor.toInteger(query, binding, accessMode);
}

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

private interface DatabaseQuery {
List<SObject> execute(String query, Map<String, Object> binding, AccessLevel accessLevel);
Integer executeCount(String query, Map<String, Object> binding, AccessLevel accessLevel);
List<SObject> toSObjects(String query, Map<String, Object> binding, AccessLevel accessLevel);
Integer toInteger(String query, Map<String, Object> binding, AccessLevel accessLevel);
}

private inherited sharing class InheritedSharing implements DatabaseQuery {
public List<SObject> execute(String query, Map<String, Object> binding, AccessLevel accessLevel) {
public List<SObject> toSObjects(String query, Map<String, Object> binding, AccessLevel accessLevel) {
return Database.queryWithBinds(query, binding, accessLevel);
}

public Integer executeCount(String query, Map<String, Object> binding, AccessLevel accessLevel) {
public Integer toInteger(String query, Map<String, Object> binding, AccessLevel accessLevel) {
return Database.countQueryWithBinds(query, binding, accessLevel);
}
}

private without sharing class WithoutSharing implements DatabaseQuery {
public List<SObject> execute(String query, Map<String, Object> binding, AccessLevel accessLevel) {
public List<SObject> toSObjects(String query, Map<String, Object> binding, AccessLevel accessLevel) {
return Database.queryWithBinds(query, binding, accessLevel);
}

public Integer executeCount(String query, Map<String, Object> binding, AccessLevel accessLevel) {
public Integer toInteger(String query, Map<String, Object> binding, AccessLevel accessLevel) {
return Database.countQueryWithBinds(query, binding, accessLevel);
}
}

private with sharing class WithSharing implements DatabaseQuery {
public List<SObject> execute(String query, Map<String, Object> binding, AccessLevel accessLevel) {
public List<SObject> toSObjects(String query, Map<String, Object> binding, AccessLevel accessLevel) {
return Database.queryWithBinds(query, binding, accessLevel);
}

public Integer executeCount(String query, Map<String, Object> binding, AccessLevel accessLevel) {
public Integer toInteger(String query, Map<String, Object> binding, AccessLevel accessLevel) {
return Database.countQueryWithBinds(query, binding, accessLevel);
}
}
Expand Down
Loading

1 comment on commit 2391490

@vercel
Copy link

@vercel vercel bot commented on 2391490 Jul 3, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.