Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
81 changes: 75 additions & 6 deletions nebula-query-and-search/main/classes/AggregateQuery.cls
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ global class AggregateQuery extends SOQL {
private SOQL.GroupingDimension groupingDimension;
private List<AggregateField> aggregateFields;
private List<String> havingConditions;
private String countQuery;

global AggregateQuery(Schema.SObjectType sobjectType) {
super(sobjectType, false);
Expand Down Expand Up @@ -79,19 +80,48 @@ global class AggregateQuery extends SOQL {
return this.havingAggregate(aggregateFunction, new SOQL.QueryField(field), operator, value);
}

global AggregateQuery havingAggregate(SOQL.Aggregate aggregateFunction, Schema.SObjectField field, SOQL.Operator operator, Object value, String bindWithKey) {
return this.havingAggregate(aggregateFunction, new SOQL.QueryField(field), operator, value, bindWithKey);
}

global AggregateQuery havingAggregate(SOQL.Aggregate aggregateFunction, SOQL.QueryField queryField, SOQL.Operator operator, Object value) {
this.havingConditions.add(aggregateFunction.name() + '(' + queryField + ') ' + SOQL.getOperatorValue(operator) + ' ' + value);
return this.havingAggregate(aggregateFunction, queryField, operator, value, null);
}

Comment on lines +88 to +90
Copy link
Owner

Choose a reason for hiding this comment

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

I think this new method overload signature makes sense, especially for trying to keep things backwards compatible with QueryArgument. But my small complaint is:

  • I want to keep the backwards-compatability you've done for using QueryArgument
  • I plan to always use the query with binds functionality (personal preference + my impression is that it's more performant)
  • I don't want to have to specify a bind variable name every time, I'd prefer the bind variable name be auto-generated

So I'm thinking there could be another method to enable automatically generating bind variable names. Something like this:

private Boolean generateBindVariables  = false;
private Integer generatedBindVariableCounter = 0;

global AggregateQuery withBinds() {
    this.generateBindVariables  = true;
}

And then in this havingAggregate() overload, auto-generate the bind var name when enabled:

Suggested change
return this.havingAggregate(aggregateFunction, queryField, operator, value, null);
}
String bindVariableName = this.generateBindVariables == false ? null : 'bindVar' + this.generatedBindVariableCounter++;
return this.havingAggregate(aggregateFunction, queryField, operator, value, bindVariableName);
}

Example usage would look something like this:

AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType)
      .withBinds()
      .groupByField(Schema.Account.Name)
      .addAggregate(SOQL.Aggregate.COUNT, Schema.Account.Id)
      .havingAggregate(SOQL.Aggregate.COUNT, Schema.Account.Id, SOQL.Operator.GREATER_THAN, someVariable);

I think the same idea would apply in the Query class too. Let me know what you think.

global AggregateQuery havingAggregate(SOQL.Aggregate aggregateFunction, SOQL.QueryField queryField, SOQL.Operator operator, Object value, String bindWithKey) {
this.havingConditions.add(
String.format(
'{0}({1}) {2} {3}',
new List<String> {
aggregateFunction.name(),
queryField.toString(),
SOQL.getOperatorValue(operator),
(String.isNotBlank(bindWithKey) ? ':' + bindWithKey : new QueryArgument(value).toString())
Copy link
Owner

Choose a reason for hiding this comment

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

I love how you've kept it backwards compatible with QueryArgument, this is great!

}
)
);
if (String.isNotBlank(bindWithKey)) {
this.bindsMap.put(bindWithKey, value);
}
return this.setHasChanged();
}

global AggregateQuery filterWhere(Schema.SObjectField field, SOQL.Operator operator, Object value) {
return this.filterWhere(new SOQL.QueryField(field), operator, value);
}

global AggregateQuery filterWhere(Schema.SObjectField field, SOQL.Operator operator, Object value, String bindWithKey) {
return this.filterWhere(new SOQL.QueryField(field), operator, value, bindWithKey);
}

global AggregateQuery filterWhere(SOQL.QueryField queryField, SOQL.Operator operator, Object value) {
return this.filterWhere(new SOQL.QueryFilter(queryField, operator, value));
}

global AggregateQuery filterWhere(SOQL.QueryField queryField, SOQL.Operator operator, Object value, String bindWithKey) {
return this.filterWhere(new SOQL.QueryFilter(queryField, operator, value, bindWithKey));
}

global AggregateQuery filterWhere(SOQL.QueryFilter filter) {
return this.filterWhere(new List<SOQL.QueryFilter>{ filter });
}
Expand All @@ -106,6 +136,11 @@ global class AggregateQuery extends SOQL {
return this.setHasChanged();
}

global AggregateQuery withAccessLevel(System.AccessLevel accessLevel) {
super.doWithAccessLevel(accessLevel);
return this.setHasChanged();
}

global AggregateQuery orderByField(Schema.SObjectField field) {
return this.orderByField(field, null);
}
Expand Down Expand Up @@ -166,6 +201,26 @@ global class AggregateQuery extends SOQL {
return this.setHasChanged();
}

global AggregateQuery setBind(String key, Object value) {
super.doSetBind(key, value);
return this.setHasChanged();
}

global AggregateQuery setBinds(Map<String, Object> binds) {
super.doSetBinds(binds);
return this.setHasChanged();
}

global AggregateQuery removeBind(String key) {
super.doRemoveBind(key);
return this.setHasChanged();
}

global AggregateQuery clearBinds() {
super.doClearBinds();
return this.setHasChanged();
}

// TODO decide if this should be global
public AggregateQuery cacheResults() {
super.doCacheResults();
Expand Down Expand Up @@ -206,10 +261,12 @@ global class AggregateQuery extends SOQL {
return this.query;
}

// TODO consider renaming to getCountResult()
@SuppressWarnings('PMD.ApexSOQLInjection')
global Integer getResultCount() {
String countQuery =
global String getCountQuery() {
if (this.countQuery != null && !this.hasChanged) {
return this.countQuery;
}

this.countQuery =
'SELECT COUNT()' +
' FROM ' +
this.sobjectType +
Expand All @@ -220,7 +277,19 @@ global class AggregateQuery extends SOQL {
super.doGetOrderByString() +
super.doGetLimitCountString() +
super.doGetOffetString();
return Database.countQuery(countQuery);

System.debug(System.LoggingLevel.FINEST, this.countQuery);
return this.countQuery;
}

// TODO consider renaming to getCountResult()
@SuppressWarnings('PMD.ApexSOQLInjection')
global Integer getResultCount() {
return Database.countQueryWithBinds(
this.getCountQuery(),
this.doGetBindsMap(),
this.doGetAccessLevel()
);
}

global AggregateResult getFirstResult() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>58.0</apiVersion>
<apiVersion>61.0</apiVersion>
<status>Active</status>
</ApexClass>
33 changes: 33 additions & 0 deletions nebula-query-and-search/main/classes/Query.cls
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,18 @@ global class Query extends SOQL {
return this.filterWhere(new SOQL.QueryField(field), operator, value);
}

global Query filterWhere(Schema.SObjectField field, SOQL.Operator operator, Object value, String bindWithKey) {
return this.filterWhere(new SOQL.QueryField(field), operator, value, bindWithKey);
}

global Query filterWhere(SOQL.QueryField queryField, SOQL.Operator operator, Object value) {
return this.filterWhere(new SOQL.QueryFilter(queryField, operator, value));
}

global Query filterWhere(SOQL.QueryField queryField, SOQL.Operator operator, Object value, String bindWithKey) {
return this.filterWhere(new SOQL.QueryFilter(queryField, operator, value, bindWithKey));
}

global Query filterWhere(SOQL.QueryFilter filter) {
return this.filterWhere(new List<SOQL.QueryFilter>{ filter });
}
Expand Down Expand Up @@ -239,6 +247,11 @@ global class Query extends SOQL {
//return this.setHasChanged();
//}

global Query withAccessLevel(System.AccessLevel accessLevel) {
super.doWithAccessLevel(accessLevel);
return this.setHasChanged();
}

global Query orderByField(Schema.SObjectField field) {
return this.orderByField(new SOQL.QueryField(field));
}
Expand Down Expand Up @@ -289,6 +302,26 @@ global class Query extends SOQL {
return this.setHasChanged();
}

global Query setBind(String key, Object value) {
super.doSetBind(key, value);
return this.setHasChanged();
}

global Query setBinds(Map<String, Object> binds) {
super.doSetBinds(binds);
return this.setHasChanged();
}

global Query removeBind(String key) {
super.doRemoveBind(key);
return this.setHasChanged();
}

global Query clearBinds() {
super.doClearBinds();
return this.setHasChanged();
}

// TODO decide if this should be global
public Query cacheResults() {
super.doCacheResults();
Expand Down
2 changes: 1 addition & 1 deletion nebula-query-and-search/main/classes/Query.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>58.0</apiVersion>
<apiVersion>61.0</apiVersion>
<status>Active</status>
</ApexClass>
5 changes: 5 additions & 0 deletions nebula-query-and-search/main/classes/RecordSearch.cls
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ global class RecordSearch extends SOSL {
return this.setHasChanged();
}

global RecordSearch withAccessLevel(System.AccessLevel accessLevel) {
super.doWithAccessLevel(accessLevel);
return this.setHasChanged();
}

global RecordSearch updateArticleReporting(SOSL.ArticleReporting articleReporting) {
this.articleReporting = articleReporting;
return this.setHasChanged();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>58.0</apiVersion>
<apiVersion>61.0</apiVersion>
<status>Active</status>
</ApexClass>
67 changes: 61 additions & 6 deletions nebula-query-and-search/main/classes/SOQL.cls
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,13 @@ global abstract class SOQL implements Comparable {
protected Set<SOQL.QueryField> excludedQueryFields;
protected Scope scope;
protected List<String> whereFilters;
protected System.AccessLevel accessLevel;
protected List<String> orderByFieldApiNames;
protected Integer limitCount;
protected Integer offset;
protected Boolean hasChanged;
protected Boolean sortQueryFields;

protected Map<String, Object> bindsMap;
protected Boolean cacheResults;

protected SOQL(Schema.SObjectType sobjectType, Boolean sortQueryFields) {
Expand All @@ -198,6 +199,8 @@ global abstract class SOQL implements Comparable {
this.excludedQueryFields = new Set<SOQL.QueryField>();
this.whereFilters = new List<String>();
this.orderByFieldApiNames = new List<String>();
this.accessLevel = System.AccessLevel.SYSTEM_MODE;
this.bindsMap = new Map<String, Object>();
this.cacheResults = false;
this.hasChanged = false;
}
Expand Down Expand Up @@ -246,12 +249,15 @@ global abstract class SOQL implements Comparable {
}

protected void doFilterWhere(List<SOQL.QueryFilter> filters) {
if (filters == null || filters.isEmpty()) {
if (filters?.isEmpty() != false) {
return;
}

for (SOQL.QueryFilter filter : filters) {
this.whereFilters.add(filter.toString());
if (String.isNotBlank(filter.bindKey)) {
this.bindsMap.put(filter.bindKey, filter.value);
}
}
this.doSetHasChanged();
}
Expand All @@ -271,6 +277,10 @@ global abstract class SOQL implements Comparable {
this.doSetHasChanged();
}

protected void doWithAccessLevel(System.AccessLevel accessLevel) {
this.accessLevel = accessLevel;
}

protected void doOrderBy(SOQL.QueryField queryField, SOQL.SortOrder sortOrder, Boolean sortNullsFirst) {
this.doOrderBy(queryField.toString(), sortOrder, sortNullsFirst);
}
Expand All @@ -296,6 +306,22 @@ global abstract class SOQL implements Comparable {
this.offset = offset;
}

protected void doSetBind(String key, Object value) {
this.bindsMap.put(key, value);
}

protected void doSetBinds(Map<String, Object> binds) {
this.bindsMap.putAll(binds);
}

protected void doRemoveBind(String key) {
this.bindsMap.remove(key);
}

protected void doClearBinds() {
this.bindsMap.clear();
}

protected SObject doGetFirstResult() {
List<SObject> results = this.doGetResults();
return results == null || results.isEmpty() ? null : results[0];
Expand All @@ -305,7 +331,11 @@ global abstract class SOQL implements Comparable {
if (this.cacheResults) {
return this.getCachedResults();
} else {
return Database.query(this.getQuery());
return Database.queryWithBinds(
this.getQuery(),
this.doGetBindsMap(),
this.doGetAccessLevel()
);
}
}

Expand Down Expand Up @@ -361,6 +391,10 @@ global abstract class SOQL implements Comparable {
return this.whereFilters.isEmpty() ? '' : ' WHERE ' + String.join(this.whereFilters, ' AND ');
}

protected System.AccessLevel doGetAccessLevel() {
return this.accessLevel ?? System.AccessLevel.SYSTEM_MODE;
}

protected String doGetOrderByString() {
return this.orderByFieldApiNames.isEmpty() ? '' : ' ORDER BY ' + String.join(this.orderByFieldApiNames, ', ');
}
Expand All @@ -373,6 +407,10 @@ global abstract class SOQL implements Comparable {
return this.offset == null ? '' : ' OFFSET ' + this.offset;
}

protected Map<String, Object> doGetBindsMap() {
return this.bindsMap ?? new Map<String, Object>();
}

private void doSetHasChanged() {
this.hasChanged = true;
}
Expand All @@ -383,7 +421,14 @@ global abstract class SOQL implements Comparable {

Boolean isCached = cachedResultsByHashCode.containsKey(hashCode);
if (!isCached) {
cachedResultsByHashCode.put(hashCode, Database.query(query));
cachedResultsByHashCode.put(
hashCode,
Database.queryWithBinds(
this.getQuery(),
this.doGetBindsMap(),
this.doGetAccessLevel()
)
);
}

// Always return a deep clone so the original cached version is never modified
Expand Down Expand Up @@ -542,16 +587,26 @@ global abstract class SOQL implements Comparable {
private Object value;
private String formattedValue;
private String filterString;
private String bindKey;

global QueryFilter(Schema.SObjectField field, SOQL.Operator operator, Object value) {
this(new QueryField(field), operator, value);
}

global QueryFilter(Schema.SObjectField field, SOQL.Operator operator, Object value, String bindKey) {
this(new QueryField(field), operator, value, bindKey);
}

global QueryFilter(QueryField queryField, SOQL.Operator operator, Object value) {
this(queryField, operator, value, null);
}

global QueryFilter(QueryField queryField, SOQL.Operator operator, Object value, String bindKey) {
this.queryField = queryField;
this.operator = operator;
this.value = value;
this.formattedValue = new QueryArgument(value).toString();
this.formattedValue = (String.isNotBlank(bindKey) ? ':' + bindKey : new QueryArgument(value).toString());
this.bindKey = bindKey;

this.filterString = queryField + ' ' + SOQL.getOperatorValue(operator) + ' ' + formattedValue;
}
Expand Down Expand Up @@ -605,7 +660,7 @@ global abstract class SOQL implements Comparable {
public class SOQLException extends Exception {
}

private class QueryArgument {
public class QueryArgument {
private String value;

public QueryArgument(Object valueToFormat) {
Expand Down
2 changes: 1 addition & 1 deletion nebula-query-and-search/main/classes/SOQL.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>58.0</apiVersion>
<apiVersion>61.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading