diff --git a/nebula-query-and-search/main/classes/AggregateQuery.cls b/nebula-query-and-search/main/classes/AggregateQuery.cls index 47b98f9e..3793fb0b 100644 --- a/nebula-query-and-search/main/classes/AggregateQuery.cls +++ b/nebula-query-and-search/main/classes/AggregateQuery.cls @@ -15,13 +15,16 @@ global class AggregateQuery extends SOQL { private SOQL.GroupingDimension groupingDimension; private List aggregateFields; - private List havingConditions; + private List havingClauseFilters; + protected String havingClauseFilterLogic; + private String countQuery; global AggregateQuery(Schema.SObjectType sobjectType) { super(sobjectType, false); this.aggregateFields = new List(); - this.havingConditions = new List(); + this.havingClauseFilters = new List(); + this.havingClauseFilterLogic = ''; } global AggregateQuery groupByField(Schema.SObjectField field) { @@ -79,8 +82,29 @@ 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, this.generateNextBindVariableKey()); + } + + global AggregateQuery havingAggregate(SOQL.Aggregate aggregateFunction, SOQL.QueryField queryField, SOQL.Operator operator, Object value, String bindWithKey) { + this.havingClauseFilters.add( + new AggregateQueryFilter(aggregateFunction, queryField, operator, value, bindWithKey) + ); + this.havingClauseFilterLogic += (this.havingClauseFilters.size() == 1 ? '1' : ' AND ' + this.havingClauseFilters.size()); + return this.setHasChanged(); + } + + global AggregateQuery orHavingAggregate(List filters) { + this.havingClauseFilterLogic = this.doOrFilter(filters, this.havingClauseFilters, this.havingClauseFilterLogic); + return this.setHasChanged(); + } + + global AggregateQuery setHavingFilterLogic(String filterLogic) { + this.havingClauseFilterLogic = filterLogic; return this.setHasChanged(); } @@ -88,10 +112,18 @@ global class AggregateQuery extends SOQL { 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{ filter }); } @@ -106,6 +138,16 @@ global class AggregateQuery extends SOQL { return this.setHasChanged(); } + global AggregateQuery setWhereFilterLogic(String filterLogic) { + super.doSetWhereFilterLogic(filterLogic); + 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); } @@ -166,6 +208,31 @@ 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 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(); + } + + global AggregateQuery generateBindVariableKeys() { + super.doGenerateBindVariableKeys(); + return this; + } + // TODO decide if this should be global public AggregateQuery cacheResults() { super.doCacheResults(); @@ -197,7 +264,7 @@ global class AggregateQuery extends SOQL { super.doGetUsingScopeString() + super.doGetWhereClauseString() + this.getGroupByString() + - this.getHavingString() + + this.getHavingClauseString() + super.doGetOrderByString() + super.doGetLimitCountString() + super.doGetOffetString(); @@ -206,21 +273,35 @@ 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 + super.doGetUsingScopeString() + super.doGetWhereClauseString() + this.getGroupByString() + - this.getHavingString() + + this.getHavingClauseString() + 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() { @@ -232,7 +313,7 @@ global class AggregateQuery extends SOQL { } private AggregateQuery setHasChanged() { - this.hasChanged = true; + this.doSetHasChanged(); return this; } @@ -262,8 +343,8 @@ global class AggregateQuery extends SOQL { return String.isEmpty(queryFieldString) ? '' : groupByTextString + queryFieldString + groupingDimensionClosingString; } - private String getHavingString() { - return this.havingConditions.isEmpty() ? '' : ' HAVING ' + String.join(this.havingConditions, ', '); + private String getHavingClauseString() { + return this.doGetFilterableClauseString('HAVING', this.havingClauseFilters, this.havingClauseFilterLogic); } private class AggregateField { @@ -287,4 +368,29 @@ global class AggregateQuery extends SOQL { return aggregateFunction.name() + '(' + fieldApiName + ') ' + fieldAlias; } } + + global class AggregateQueryFilter extends SOQL.QueryFilter { + private SOQL.Aggregate aggregateFunction; + + public AggregateQueryFilter(SOQL.Aggregate aggregateFunction, SOQL.QueryField queryField, SOQL.Operator operator, Object value, String bindKey) + { + super(queryField, operator, value, bindKey); + this.aggregateFunction = aggregateFunction; + } + + public override String toString() + { + return String.format( + '{0}({1}) {2} {3}', + new List { + this.aggregateFunction.name(), + this.queryField.toString(), + SOQL.getOperatorValue(this.operator), + (String.isNotBlank(this.bindKey) ? ':' + this.bindKey : new QueryArgument(this.value).toString()) + } + ); + } + + } + } diff --git a/nebula-query-and-search/main/classes/AggregateQuery.cls-meta.xml b/nebula-query-and-search/main/classes/AggregateQuery.cls-meta.xml index 133fce1e..c01f6433 100644 --- a/nebula-query-and-search/main/classes/AggregateQuery.cls-meta.xml +++ b/nebula-query-and-search/main/classes/AggregateQuery.cls-meta.xml @@ -1,5 +1,5 @@ - 58.0 + 61.0 Active diff --git a/nebula-query-and-search/main/classes/Query.cls b/nebula-query-and-search/main/classes/Query.cls index 2025858f..97e4d09c 100644 --- a/nebula-query-and-search/main/classes/Query.cls +++ b/nebula-query-and-search/main/classes/Query.cls @@ -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{ filter }); } @@ -217,6 +225,11 @@ global class Query extends SOQL { return this.setHasChanged(); } + global Query setWhereFilterLogic(String filterLogic) { + super.doSetWhereFilterLogic(filterLogic); + return this.setHasChanged(); + } + //global Query filterWhereInSubquery(Schema.SObjectType childSObjectType, Schema.SObjectField lookupFieldOnChildSObject) { //this.whereFilters.add('Id IN (SELECT ' + lookupFieldOnChildSObject + ' FROM ' + childSObjectType + ')'); //return this.setHasChanged(); @@ -239,6 +252,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)); } @@ -289,6 +307,31 @@ 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 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(); + } + + global Query generateBindVariableKeys() { + super.doGenerateBindVariableKeys(); + return this; + } + // TODO decide if this should be global public Query cacheResults() { super.doCacheResults(); @@ -412,7 +455,7 @@ global class Query extends SOQL { } private Query setHasChanged() { - this.hasChanged = true; + this.doSetHasChanged(); return this; } diff --git a/nebula-query-and-search/main/classes/Query.cls-meta.xml b/nebula-query-and-search/main/classes/Query.cls-meta.xml index 133fce1e..c01f6433 100644 --- a/nebula-query-and-search/main/classes/Query.cls-meta.xml +++ b/nebula-query-and-search/main/classes/Query.cls-meta.xml @@ -1,5 +1,5 @@ - 58.0 + 61.0 Active diff --git a/nebula-query-and-search/main/classes/RecordSearch.cls b/nebula-query-and-search/main/classes/RecordSearch.cls index f3fbdb4a..143d0d04 100644 --- a/nebula-query-and-search/main/classes/RecordSearch.cls +++ b/nebula-query-and-search/main/classes/RecordSearch.cls @@ -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(); diff --git a/nebula-query-and-search/main/classes/RecordSearch.cls-meta.xml b/nebula-query-and-search/main/classes/RecordSearch.cls-meta.xml index 133fce1e..c01f6433 100644 --- a/nebula-query-and-search/main/classes/RecordSearch.cls-meta.xml +++ b/nebula-query-and-search/main/classes/RecordSearch.cls-meta.xml @@ -1,5 +1,5 @@ - 58.0 + 61.0 Active diff --git a/nebula-query-and-search/main/classes/SOQL.cls b/nebula-query-and-search/main/classes/SOQL.cls index a2b0f802..c444a18c 100644 --- a/nebula-query-and-search/main/classes/SOQL.cls +++ b/nebula-query-and-search/main/classes/SOQL.cls @@ -180,14 +180,18 @@ global abstract class SOQL implements Comparable { protected Map includedQueryFieldsAndCategory; protected Set excludedQueryFields; protected Scope scope; - protected List whereFilters; + protected List whereClauseFilters; + protected String whereClauseFilterLogic; + protected System.AccessLevel accessLevel; protected List orderByFieldApiNames; protected Integer limitCount; protected Integer offset; protected Boolean hasChanged; protected Boolean sortQueryFields; - + protected Map bindsMap; protected Boolean cacheResults; + private Boolean generateBindVariableKeys; + private Integer generatedBindVariableKeyCounter; protected SOQL(Schema.SObjectType sobjectType, Boolean sortQueryFields) { this.sobjectType = sobjectType; @@ -196,10 +200,15 @@ global abstract class SOQL implements Comparable { this.sobjectDescribe = this.sobjectType.getDescribe(Schema.SObjectDescribeOptions.DEFERRED); this.includedQueryFieldsAndCategory = new Map(); this.excludedQueryFields = new Set(); - this.whereFilters = new List(); + this.whereClauseFilters = new List(); + this.whereClauseFilterLogic = ''; this.orderByFieldApiNames = new List(); + this.accessLevel = System.AccessLevel.SYSTEM_MODE; + this.bindsMap = new Map(); this.cacheResults = false; this.hasChanged = false; + this.generateBindVariableKeys = false; + this.generatedBindVariableKeyCounter = 0; } global Schema.SObjectType getSObjectType() { @@ -246,29 +255,44 @@ global abstract class SOQL implements Comparable { } protected void doFilterWhere(List filters) { - if (filters == null || filters.isEmpty()) { + if (filters?.isEmpty() != false) { return; } for (SOQL.QueryFilter filter : filters) { - this.whereFilters.add(filter.toString()); + this.whereClauseFilters.add(filter); + this.whereClauseFilterLogic += (this.whereClauseFilters.size() == 1 ? '1' : ' AND ' + this.whereClauseFilters.size()); } - this.doSetHasChanged(); } - protected void doOrFilterWhere(List filters) { - if (filters == null || filters.isEmpty()) { - return; + protected String doOrFilter(List newFilters, List clauseFilters, String filterLogic) { + if (newFilters?.isEmpty() != false) { + return filterLogic; } - filters.sort(); + newFilters.sort(); - List orFilterPieces = new List(); - for (SOQL.QueryFilter filter : filters) { - orFilterPieces.add(filter.toString()); + filterLogic += (clauseFilters.isEmpty() ? '' : ' AND ') + '('; + for (Integer i = 0; i < newFilters.size(); i++) { + SOQL.QueryFilter filter = newFilters[i]; + clauseFilters.add(filter); + filterLogic += (i == 0 ? '' : ' OR ') + clauseFilters.size(); } - this.whereFilters.add('(' + String.join(orFilterPieces, ' OR ') + ')'); - this.doSetHasChanged(); + filterLogic += ')'; + return filterLogic; + } + + protected void doSetWhereFilterLogic(String filterLogic) + { + this.whereClauseFilterLogic = filterLogic; + } + + protected void doOrFilterWhere(List filters) { + this.whereClauseFilterLogic = this.doOrFilter(filters, this.whereClauseFilters, this.whereClauseFilterLogic); + } + + protected void doWithAccessLevel(System.AccessLevel accessLevel) { + this.accessLevel = accessLevel; } protected void doOrderBy(SOQL.QueryField queryField, SOQL.SortOrder sortOrder, Boolean sortNullsFirst) { @@ -296,6 +320,41 @@ 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 binds) { + this.bindsMap.putAll(binds); + } + + protected void doRemoveBind(String key) { + this.bindsMap.remove(key); + for (QueryFilter filter : this.whereClauseFilters) + { + if (filter.bindKey == key) + { + filter.bindKey = null; + } + } + } + + protected void doClearBinds() { + this.bindsMap.clear(); + for (QueryFilter filter : this.whereClauseFilters) + { + filter.bindKey = null; + } + } + + protected void doGenerateBindVariableKeys() { + this.generateBindVariableKeys = true; + } + + protected String generateNextBindVariableKey() { + return this.generateBindVariableKeys != true ? null : 'bindVar' + this.generatedBindVariableKeyCounter++; + } + protected SObject doGetFirstResult() { List results = this.doGetResults(); return results == null || results.isEmpty() ? null : results[0]; @@ -305,7 +364,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() + ); } } @@ -356,9 +419,58 @@ global abstract class SOQL implements Comparable { return this.scope == null ? '' : ' USING SCOPE ' + this.scope.name(); } + protected String doGetFilterableClauseString(String clause, LIST filters, String filterLogic) { + List filterStrings = new List(); + for (Soql.QueryFilter filter : filters) + { + filter.setBindKey(filter.getBindKey() ?? this.generateNextBindVariableKey()); + if (String.isNotBlank(filter.getBindKey())) { + this.bindsMap.put(filter.getBindKey(), filter.getValue()); + } + filterStrings.add(filter.toString()); + } + if (String.isBlank(filterLogic)) + { + return ''; + } + + List filterLogicParts = filterLogic.split('\\s+'); + List filterLogicPartsReplaced = new List(); + for (String filterLogicPart : filterLogicParts) + { + Matcher m = Pattern.compile('\\d+').matcher(filterLogicPart); + Boolean indexFound = m.find(); + if (indexFound) + { + Integer filterLogicIndex = Integer.valueOf(m.group()); + try + { + filterLogicPartsReplaced.add( + filterLogicPart.replace( + m.group(), + filterStrings[filterLogicIndex - 1] + ) + ); + } + catch (ListException e) + { + throw new QueryException('No query ' + clause + ' filter defined for index "' + filterLogicIndex + '" specified in filter conditions "' + filterLogic + '"'); + } + } + else + { + filterLogicPartsReplaced.add(filterLogicPart); + } + } + return ' ' + clause + ' ' + String.join(filterLogicPartsReplaced, ' '); + } + protected String doGetWhereClauseString() { - this.whereFilters.sort(); - return this.whereFilters.isEmpty() ? '' : ' WHERE ' + String.join(this.whereFilters, ' AND '); + return this.doGetFilterableClauseString('WHERE', this.whereClauseFilters, this.whereClauseFilterLogic); + } + + protected System.AccessLevel doGetAccessLevel() { + return this.accessLevel ?? System.AccessLevel.SYSTEM_MODE; } protected String doGetOrderByString() { @@ -373,7 +485,11 @@ global abstract class SOQL implements Comparable { return this.offset == null ? '' : ' OFFSET ' + this.offset; } - private void doSetHasChanged() { + protected Map doGetBindsMap() { + return this.bindsMap ?? new Map(); + } + + protected void doSetHasChanged() { this.hasChanged = true; } @@ -383,7 +499,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 @@ -536,34 +659,47 @@ global abstract class SOQL implements Comparable { } } - global class QueryFilter implements Comparable { - private SOQL.QueryField queryField; - private SOQL.Operator operator; - private Object value; - private String formattedValue; - private String filterString; + global virtual class QueryFilter implements Comparable { + protected SOQL.QueryField queryField; + protected SOQL.Operator operator; + protected Object value; + protected String bindKey; + protected Schema.SObjectType childSObjectType; + protected Query childQuery; + protected Boolean inOrNotIn; + protected Schema.SObjectField lookupFieldOnChildSObject; 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.filterString = queryField + ' ' + SOQL.getOperatorValue(operator) + ' ' + formattedValue; + this.bindKey = bindKey; } global QueryFilter(Schema.SObjectType childSObjectType, Boolean inOrNotIn, Schema.SObjectField lookupFieldOnChildSObject) { this.operator = inOrNotIn ? SOQL.Operator.IS_IN : SOQL.Operator.IS_NOT_IN; - this.filterString = 'Id ' + SOQL.getOperatorValue(this.operator) + ' (SELECT ' + lookupFieldOnChildSObject + ' FROM ' + childSObjectType + ')'; + this.childSObjectType = childSObjectType; + this.inOrNotIn = inOrNotIn; + this.lookupFieldOnChildSObject = lookupFieldOnChildSObject; } global QueryFilter(Query childQuery, Boolean inOrNotIn, Schema.SObjectField lookupFieldOnChildSObject) { this.operator = inOrNotIn ? SOQL.Operator.IS_IN : SOQL.Operator.IS_NOT_IN; - this.filterString = 'Id ' + SOQL.getOperatorValue(this.operator) + ' ' + childQuery.getSubquery(lookupFieldOnChildSObject); + this.childQuery = childQuery; + this.inOrNotIn = inOrNotIn; + this.lookupFieldOnChildSObject = lookupFieldOnChildSObject; } global Integer compareTo(Object compareTo) { @@ -590,14 +726,33 @@ global abstract class SOQL implements Comparable { return this.value; } + global String getBindKey() { + return this.bindKey; + } + + global void setBindKey(String bindKey) { + if (this.queryField != null) + { + this.bindKey = bindKey; + } + } + // TODO decide if this should be global public Object getFormattedValue() { - return this.formattedValue; + return (String.isNotBlank(this.bindKey) ? ':' + this.bindKey : new QueryArgument(this.value).toString()); } // TODO decide if this should be global - public override String toString() { - return this.filterString; + public virtual override String toString() { + return ( + this.queryField != null ? + this.queryField + ' ' + SOQL.getOperatorValue(this.operator) + ' ' + this.getFormattedValue() : + ( + this.childSObjectType != null ? + 'Id ' + SOQL.getOperatorValue(this.operator) + ' (SELECT ' + this.lookupFieldOnChildSObject + ' FROM ' + this.childSObjectType + ')' : + 'Id ' + SOQL.getOperatorValue(this.operator) + ' ' + this.childQuery.getSubquery(this.lookupFieldOnChildSObject) + ) + ); } } @@ -605,7 +760,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) { diff --git a/nebula-query-and-search/main/classes/SOQL.cls-meta.xml b/nebula-query-and-search/main/classes/SOQL.cls-meta.xml index 133fce1e..c01f6433 100644 --- a/nebula-query-and-search/main/classes/SOQL.cls-meta.xml +++ b/nebula-query-and-search/main/classes/SOQL.cls-meta.xml @@ -1,5 +1,5 @@ - 58.0 + 61.0 Active diff --git a/nebula-query-and-search/main/classes/SOSL.cls b/nebula-query-and-search/main/classes/SOSL.cls index 918c5b1f..32ad7489 100644 --- a/nebula-query-and-search/main/classes/SOSL.cls +++ b/nebula-query-and-search/main/classes/SOSL.cls @@ -42,6 +42,7 @@ global abstract class SOSL { protected SOSL.ArticleReporting articleReporting; protected List withClauses; protected List withDataCategoryClauses; + protected System.AccessLevel accessLevel; protected SOSL.SearchGroup searchGroup; protected SOSL(String searchTerm, Query sobjectQuery) { @@ -57,6 +58,7 @@ global abstract class SOSL { this.searchGroup = SOSL.SearchGroup.ALL_FIELDS; this.withClauses = new List(); this.withDataCategoryClauses = new List(); + this.accessLevel = System.AccessLevel.SYSTEM_MODE; } public Set getSObjectTypes() { @@ -88,7 +90,7 @@ global abstract class SOSL { if (this.cacheResults) { return this.getCachedResults(); } else { - return System.Search.query(this.getSearch()); + return System.Search.query(this.getSearch(), this.doGetAccessLevel()); } } @@ -119,6 +121,14 @@ global abstract class SOSL { return this.withClauses.isEmpty() ? '' : ' WITH ' + String.join(this.withClauses, ' WITH '); } + protected void doWithAccessLevel(System.AccessLevel accessLevel) { + this.accessLevel = accessLevel; + } + + protected System.AccessLevel doGetAccessLevel() { + return this.accessLevel ?? System.AccessLevel.SYSTEM_MODE; + } + protected String doGetUpdateArticleReportingString() { return this.articleReporting == null ? '' : ' UPDATE ' + this.articleReporting.name(); } @@ -129,7 +139,10 @@ global abstract class SOSL { Boolean isCached = cachedSearchResultsByHashCode.containsKey(hashCode); if (!isCached) { - cachedSearchResultsByHashCode.put(hashCode, Search.query(searchQuery)); + cachedSearchResultsByHashCode.put( + hashCode, + System.Search.query(searchQuery, this.doGetAccessLevel()) + ); } // Always return a deep clone so the original cached version is never modified diff --git a/nebula-query-and-search/main/classes/SOSL.cls-meta.xml b/nebula-query-and-search/main/classes/SOSL.cls-meta.xml index 133fce1e..c01f6433 100644 --- a/nebula-query-and-search/main/classes/SOSL.cls-meta.xml +++ b/nebula-query-and-search/main/classes/SOSL.cls-meta.xml @@ -1,5 +1,5 @@ - 58.0 + 61.0 Active diff --git a/nebula-query-and-search/tests/classes/AggregateQuery_Tests.cls b/nebula-query-and-search/tests/classes/AggregateQuery_Tests.cls index 8e4a2a86..f318bc76 100644 --- a/nebula-query-and-search/tests/classes/AggregateQuery_Tests.cls +++ b/nebula-query-and-search/tests/classes/AggregateQuery_Tests.cls @@ -8,6 +8,35 @@ ) @IsTest(IsParallel=true) private class AggregateQuery_Tests { + + @IsTest + static void it_should_construct_a_count_query_without_binds() + { + // SETUP + String expectedQueryString = 'SELECT COUNT() FROM Opportunity WHERE AccountId != null'; + + // TEST + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Opportunity.SObjectType) + .filterWhere(new SOQL.QueryFilter(Schema.Opportunity.AccountId, SOQL.Operator.NOT_EQUAL_TO, null)); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getCountQuery()); + } + + @IsTest + static void it_should_construct_a_count_query_with_binds() + { + // SETUP + String expectedQueryString = 'SELECT COUNT() FROM Opportunity WHERE AccountId != :accountIdFilter'; + + // TEST + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Opportunity.SObjectType) + .filterWhere(new SOQL.QueryFilter(Schema.Opportunity.AccountId, SOQL.Operator.NOT_EQUAL_TO, null, 'accountIdFilter')); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getCountQuery()); + } + @IsTest static void it_should_be_usable_after_construction() { String expectedQueryString = 'SELECT COUNT(Id) COUNT__Id FROM Opportunity'; @@ -34,9 +63,56 @@ private class AggregateQuery_Tests { System.Assert.areEqual(expectedResults, returnedResults); } + @IsTest + static void it_should_return_count_result_when_filtering_with_binds() + { + // SETUP + String expectedQueryString = 'SELECT COUNT() FROM Opportunity WHERE AccountId != :accountIdFilter'; + Integer expectedResult = Database.countQueryWithBinds( + expectedQueryString, + new Map { + 'accountIdFilter' => null + }, + System.AccessLevel.SYSTEM_MODE + ); + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Opportunity.SObjectType) + .filterWhere(new SOQL.QueryFilter(Schema.Opportunity.AccountId, SOQL.Operator.NOT_EQUAL_TO, null, 'accountIdFilter')); + + // TEST + Integer returnedResult = aggregateQuery.getResultCount(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getCountQuery()); + System.Assert.areEqual(expectedResult, returnedResult); + } + + @IsTest + static void it_should_return_results_when_filtering_with_binds() + { + // SETUP + String expectedQueryString = 'SELECT Type FROM Opportunity WHERE AccountId != :accountIdFilter GROUP BY Type'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'accountIdFilter' => null + }, + System.AccessLevel.SYSTEM_MODE + ); + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Opportunity.SObjectType) + .groupByField(Schema.Opportunity.Type) + .filterWhere(new SOQL.QueryFilter(Schema.Opportunity.AccountId, SOQL.Operator.NOT_EQUAL_TO, null, 'accountIdFilter')); + + // TEST + List returnedResults = aggregateQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + } + @IsTest static void it_should_return_results_when_filtering_with_an_or_statement() { - String expectedQueryString = 'SELECT Type, COUNT(Id) COUNT__Id FROM Account WHERE (AnnualRevenue = null OR Type = null) AND ParentId != null GROUP BY Type'; + String expectedQueryString = 'SELECT Type, COUNT(Id) COUNT__Id FROM Account WHERE ParentId != null AND (AnnualRevenue = null OR Type = null) GROUP BY Type'; AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType) .groupByField(Schema.Account.Type) @@ -59,13 +135,13 @@ private class AggregateQuery_Tests { static void it_should_cache_results() { AggregateQuery aggregateQuery = new AggregateQuery(Schema.Opportunity.SObjectType); aggregateQuery.cacheResults(); - System.assertEquals(0, System.Limits.getQueries()); + System.Assert.areEqual(0, System.Limits.getQueries()); for (Integer i = 0; i < 3; i++) { aggregateQuery.getResults(); } - System.assertEquals(1, System.Limits.getQueries()); + System.Assert.areEqual(1, System.Limits.getQueries()); } @IsTest @@ -115,6 +191,31 @@ private class AggregateQuery_Tests { System.Assert.areEqual(expectedResults, returnedResults); } + @IsTest + static void it_should_group_by_having_aggregate_with_binds() + { + // SETUP + String expectedQueryString = 'SELECT Name, COUNT(Id) COUNT__Id FROM Account GROUP BY Name HAVING COUNT(Id) > :minCount'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'minCount' => 2 + }, + System.AccessLevel.SYSTEM_MODE + ); + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType) + .groupByField(Schema.Account.Name) + .addAggregate(SOQL.Aggregate.COUNT, Schema.Account.Id) + .havingAggregate(SOQL.Aggregate.COUNT, Schema.Account.Id, SOQL.Operator.GREATER_THAN, 2, 'minCount'); + + // TEST + List returnedResults = aggregateQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + } + @IsTest static void it_should_group_by_a_date_function() { String expectedQueryString = 'SELECT CALENDAR_MONTH(CloseDate), COUNT(Id) COUNT__Id FROM Opportunity GROUP BY CALENDAR_MONTH(CloseDate)'; @@ -129,6 +230,52 @@ private class AggregateQuery_Tests { System.Assert.areEqual(expectedResults, returnedResults); } + @IsTest + static void it_should_run_with_system_mode() { + String expectedQueryString = 'SELECT COUNT(Id) COUNT__Id FROM Opportunity'; + + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Opportunity.SObjectType) + .addAggregate(SOQL.Aggregate.COUNT, Opportunity.Id) + .withAccessLevel(System.AccessLevel.SYSTEM_MODE); + + System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); + List expectedResults = Database.query(expectedQueryString); + List returnedResults; + Exception caughtException; + System.runAs(minimumAccessUser()) { + try { + returnedResults = aggregateQuery.getResults(); + } catch (Exception e) { + caughtException = e; + } + } + System.Assert.isNull(caughtException, 'Query should not throw exception when run in System Mode'); + System.Assert.areEqual(expectedResults, returnedResults); + } + + @IsTest + static void it_should_run_with_user_mode() { + String expectedQueryString = 'SELECT COUNT(Id) COUNT__Id FROM Opportunity'; + + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Opportunity.SObjectType) + .addAggregate(SOQL.Aggregate.COUNT, Opportunity.Id) + .withAccessLevel(System.AccessLevel.USER_MODE); + + System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); + List expectedResults = Database.query(expectedQueryString); + List returnedResults; + Exception caughtException; + System.runAs(minimumAccessUser()) { + try { + returnedResults = aggregateQuery.getResults(); + } catch (Exception e) { + caughtException = e; + } + } + System.Assert.isInstanceOfType(caughtException, System.QueryException.class, 'Query should throw exception when run in User Mode'); + System.Assert.isTrue(caughtException.getMessage().contains('sObject type \'Opportunity\' is not supported'), 'Query should throw exception when run in User Mode'); + } + @IsTest static void it_should_build_a_ridiculous_query_string() { String expectedQueryString = @@ -136,7 +283,7 @@ private class AggregateQuery_Tests { ' COUNT_DISTINCT(AccountId) COUNT_DISTINCT__AccountId, COUNT_DISTINCT(OwnerId) COUNT_DISTINCT__OwnerId, COUNT_DISTINCT(Type) COUNT_DISTINCT__Type,' + ' MAX(CreatedDate) MAX__CreatedDate, MIN(CreatedDate) MIN__CreatedDate, SUM(Amount) SUM__Amount' + ' FROM Opportunity' + - ' WHERE AccountId != null' + + ' WHERE AccountId != null AND CreatedDate >= :createdDateFilter' + ' GROUP BY Account.Type, StageName' + ' ORDER BY Account.Type ASC NULLS FIRST, StageName ASC NULLS FIRST, SUM(Amount) ASC NULLS FIRST,' + ' MIN(CloseDate) DESC NULLS FIRST, MAX(Account.LastActivityDate) ASC NULLS FIRST' + @@ -160,12 +307,238 @@ private class AggregateQuery_Tests { .orderByAggregate(SOQL.Aggregate.MIN, Schema.Opportunity.CloseDate, SOQL.SortOrder.DESCENDING) .orderByAggregate(SOQL.Aggregate.MAX, new SOQL.QueryField(new List{ Schema.Opportunity.AccountId, Schema.Account.LastActivityDate })) .filterWhere(Schema.Opportunity.AccountId, SOQL.Operator.NOT_EQUAL_TO, null) + .filterWhere(Schema.Opportunity.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today(), 'createdDateFilter') .limitTo(100) .offsetBy(0); System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); - List expectedResults = Database.query(expectedQueryString); + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'createdDateFilter' => Date.today() + }, + System.AccessLevel.SYSTEM_MODE + ); + List returnedResults = aggregateQuery.getResults(); + System.Assert.areEqual(expectedResults, returnedResults); + } + + @IsTest + static void it_will_set_a_bind_variable() + { + // SETUP + String expectedQueryString = 'SELECT MIN(CreatedDate) MIN__CreatedDate FROM Account WHERE CreatedDate >= :dateFilter'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'dateFilter' => Date.today().addDays(-1) + }, + System.AccessLevel.SYSTEM_MODE + ); + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType) + .addAggregate(SOQL.Aggregate.MIN, Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1), 'dateFilter')); + + // TEST + aggregateQuery.setBind('dateFilter', Date.today().addDays(-1)); + List returnedResults = aggregateQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse((Date)returnedResults[0].get('MIN__CreatedDate') < Date.today().addDays(-1)); + } + } + + @IsTest + static void it_will_set_multiple_bind_variables() + { + // SETUP + String expectedQueryString = 'SELECT MAX(CreatedDate) MAX__CreatedDate, MIN(CreatedDate) MIN__CreatedDate FROM Account WHERE CreatedDate >= :minDateFilter AND CreatedDate < :maxDateFilter'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'minDateFilter' => Date.today().addDays(-7), + 'maxDateFilter' => Date.today() + }, + System.AccessLevel.SYSTEM_MODE + ); + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType) + .addAggregate(SOQL.Aggregate.MIN, Schema.Account.CreatedDate) + .addAggregate(SOQL.Aggregate.MAX, Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1), 'minDateFilter')) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.LESS_THAN, Date.today().addDays(-1), 'maxDateFilter')); + + // TEST + aggregateQuery.setBind('minDateFilter', Date.today().addDays(-7)); + aggregateQuery.setBind('maxDateFilter', Date.today()); List returnedResults = aggregateQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse((Date)returnedResults[0].get('MIN__CreatedDate') < Date.today().addDays(-7)); + System.Assert.isFalse((Date)returnedResults[0].get('MAX__CreatedDate') >= Date.today()); + } } + + @IsTest + static void it_will_remove_a_bind_variable() + { + // SETUP + String expectedQueryString = 'SELECT MIN(CreatedDate) MIN__CreatedDate FROM Account WHERE CreatedDate >= 2000-01-01T05:00:00Z'; + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType) + .addAggregate(SOQL.Aggregate.MIN, Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Datetime.newInstance(2000, 1, 1), 'dateFilter')); + + // TEST + aggregateQuery.removeBind('dateFilter'); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); + } + + @IsTest + static void it_will_clear_all_bind_variables() + { + // SETUP + String expectedQueryString = 'SELECT MAX(CreatedDate) MAX__CreatedDate, MIN(CreatedDate) MIN__CreatedDate FROM Account WHERE CreatedDate >= 2000-01-01T05:00:00Z AND CreatedDate < 2001-01-01T05:00:00Z'; + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType) + .addAggregate(SOQL.Aggregate.MIN, Schema.Account.CreatedDate) + .addAggregate(SOQL.Aggregate.MAX, Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Datetime.newInstance(2000, 1, 1), 'minDateFilter')) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.LESS_THAN, Datetime.newInstance(2001, 1, 1), 'maxDateFilter')); + + // TEST + aggregateQuery.clearBinds(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); + } + + static User minimumAccessUser() { + return new User( + Alias = 'newUser', + Email = 'newuser@testorg.com', + EmailEncodingKey = 'UTF-8', + LastName = 'Testing', + LanguageLocaleKey = 'en_US', + LocaleSidKey = 'en_US', + ProfileId = [SELECT Id FROM Profile WHERE Name = 'Minimum Access - Salesforce'].Id, + TimeZoneSidKey = 'GMT', + UserName = 'newuser@testorg.com' + ); + } + + @IsTest + static void it_will_generate_a_bind_variable_for_a_having_filter() + { + // SETUP + String expectedQueryString = 'SELECT ParentId, MIN(CreatedDate) MIN__CreatedDate FROM Account GROUP BY ParentId HAVING MIN(CreatedDate) >= :bindVar0'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'bindVar0' => Date.today().addDays(-1) + }, + System.AccessLevel.SYSTEM_MODE + ); + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType) + .generateBindVariableKeys() + .addAggregate(SOQL.Aggregate.MIN, Schema.Account.CreatedDate) + .groupByField(Account.ParentId) + .havingAggregate(SOQL.Aggregate.MIN, Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1)); + + // TEST + List returnedResults = aggregateQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse((Date)returnedResults[0].get('MIN__CreatedDate') < Date.today().addDays(-1)); + } + } + + @IsTest + static void it_will_generate_a_bind_variable_for_a_where_filter() + { + // SETUP + String expectedQueryString = 'SELECT MIN(CreatedDate) MIN__CreatedDate FROM Account WHERE CreatedDate >= :bindVar0'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'bindVar0' => Date.today().addDays(-1) + }, + System.AccessLevel.SYSTEM_MODE + ); + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType) + .generateBindVariableKeys() + .addAggregate(SOQL.Aggregate.MIN, Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1))); + + // TEST + List returnedResults = aggregateQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, aggregateQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse((Date)returnedResults[0].get('MIN__CreatedDate') < Date.today().addDays(-1)); + } + } + + @IsTest + static void it_will_construct_a_where_clause_based_on_custom_logic() + { + // SETUP + String expectedQueryString = 'SELECT MIN(CreatedDate) MIN__CreatedDate FROM Account WHERE (CreatedDate = :bindVar1 OR LastModifiedDate = :bindVar0) AND (Name LIKE :bindVar3 OR Parent.Name LIKE :bindVar2)'; + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType) + .generateBindVariableKeys() + .addAggregate(SOQL.Aggregate.MIN, Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.LastModifiedDate, SOQL.Operator.EQUALS, Date.today())) + .orFilterWhere( + new List { + new SOQL.QueryFilter(new SOQL.QueryField(Schema.Account.getSObjectType(), 'Parent.Name'), SOQL.Operator.IS_LIKE, 'Test%'), + new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.EQUALS, Date.today().addDays(-1)) + } + ) + .filterWhere(new SOQL.QueryFilter(Schema.Account.Name, SOQL.Operator.IS_LIKE, 'Smith%')) + .setWhereFilterLogic('(2 OR 1) AND (4 OR 3)'); + + // TEST + String actualQueryString = aggregateQuery.getQuery(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, actualQueryString); + } + + @IsTest + static void it_will_construct_a_having_clause_based_on_custom_logic() + { + // SETUP + String expectedQueryString = 'SELECT COUNT(Id) COUNT__Id, MIN(CreatedDate) MIN__CreatedDate, MIN(LastModifiedDate) MIN__LastModifiedDate FROM Account HAVING (MIN(CreatedDate) = :bindVar2 OR MIN(LastModifiedDate) = :bindVar1) AND (COUNT(Id) = :bindVar3 OR COUNT(Id) = :bindVar0)'; + AggregateQuery aggregateQuery = new AggregateQuery(Schema.Account.SObjectType) + .generateBindVariableKeys() + .addAggregate(SOQL.Aggregate.MIN, Schema.Account.CreatedDate) + .addAggregate(SOQL.Aggregate.MIN, Schema.Account.LastModifiedDate) + .addAggregate(SOQL.Aggregate.COUNT, Schema.Account.Id) + .havingAggregate(SOQL.Aggregate.COUNT, Schema.Account.Id, SOQL.Operator.EQUALS, 10) + .havingAggregate(SOQL.Aggregate.MIN, Schema.Account.LastModifiedDate, SOQL.Operator.EQUALS, Datetime.newInstance(2000, 1, 1)) + .havingAggregate(SOQL.Aggregate.MIN, Schema.Account.CreatedDate, SOQL.Operator.EQUALS, Datetime.newInstance(2021, 12, 31)) + .havingAggregate(SOQL.Aggregate.COUNT, Schema.Account.Id, SOQL.Operator.EQUALS, 100) + .setHavingFilterLogic('(3 OR 2) AND (4 OR 1)'); + + // TEST + String actualQueryString = aggregateQuery.getQuery(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, actualQueryString); + } + } diff --git a/nebula-query-and-search/tests/classes/AggregateQuery_Tests.cls-meta.xml b/nebula-query-and-search/tests/classes/AggregateQuery_Tests.cls-meta.xml index 133fce1e..c01f6433 100644 --- a/nebula-query-and-search/tests/classes/AggregateQuery_Tests.cls-meta.xml +++ b/nebula-query-and-search/tests/classes/AggregateQuery_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 58.0 + 61.0 Active diff --git a/nebula-query-and-search/tests/classes/Query_Tests.cls b/nebula-query-and-search/tests/classes/Query_Tests.cls index 051ac675..fec1f9d1 100644 --- a/nebula-query-and-search/tests/classes/Query_Tests.cls +++ b/nebula-query-and-search/tests/classes/Query_Tests.cls @@ -8,13 +8,42 @@ ) @IsTest(IsParallel=true) private class Query_Tests { + + @IsTest + static void it_should_construct_a_query_without_binds() + { + // SETUP + String expectedQueryString = 'SELECT Id, Name FROM Account WHERE CreatedDate >= THIS_MONTH'; + + // TEST + Query accountQuery = new Query(Schema.Account.SObjectType) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, new SOQL.DateLiteral(SOQL.FixedDateLiteral.THIS_MONTH))); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + } + + @IsTest + static void it_should_construct_a_query_with_binds() + { + // SETUP + String expectedQueryString = 'SELECT Id, Name FROM Account WHERE CreatedDate >= :dateFilter'; + + // TEST + Query accountQuery = new Query(Schema.Account.SObjectType) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1), 'dateFilter')); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + } + @IsTest static void it_should_return_results_for_a_simple_query() { String expectedQueryString = 'SELECT Id, Name FROM Account'; Query simpleAccountQuery = new Query(Schema.Account.SObjectType); - System.assertEquals(expectedQueryString, simpleAccountQuery.getQuery()); + System.Assert.areEqual(expectedQueryString, simpleAccountQuery.getQuery()); List accounts = simpleAccountQuery.getResults(); } @@ -36,15 +65,15 @@ private class Query_Tests { String expectedQueryString = 'SELECT Alias, Email, Id, IsActive, Profile.Name, ProfileId' + ' FROM User USING SCOPE MINE' + - ' WHERE CreatedDate <= LAST_WEEK' + - ' AND Email != null' + - ' AND IsActive = true' + - ' AND LastLoginDate >= LAST_N_DAYS:3' + - ' AND LastModifiedDate <= ' + - now.format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'', 'Greenwich Mean Time') + + ' WHERE IsActive = true' + ' AND Profile.Id != \'' + System.UserInfo.getProfileId() + '\'' + + ' AND LastModifiedDate <= ' + + now.format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'', 'Greenwich Mean Time') + + ' AND LastLoginDate >= LAST_N_DAYS:3' + + ' AND CreatedDate <= LAST_WEEK' + + ' AND Email != null' + ' ORDER BY Profile.CreatedBy.LastModifiedDate ASC NULLS FIRST, Name ASC NULLS FIRST, Email ASC NULLS FIRST' + ' LIMIT 100 OFFSET 1 FOR VIEW'; List fieldsToQuery = new List{ Schema.User.IsActive, Schema.User.Alias }; @@ -71,7 +100,7 @@ private class Query_Tests { .offsetBy(1) .forView(); - System.assertEquals(expectedQueryString, userQuery.getQuery()); + System.Assert.areEqual(expectedQueryString, userQuery.getQuery()); List expectedResults = Database.query(expectedQueryString); List returnedResults = userQuery.getResults(); System.Assert.areEqual(expectedResults, returnedResults); @@ -89,7 +118,7 @@ private class Query_Tests { Query userQuery = new Query(Schema.User.SObjectType).addField(queryField).limitTo(1); - System.assertEquals(expectedQueryString, userQuery.getQuery()); + System.Assert.areEqual(expectedQueryString, userQuery.getQuery()); List expectedResults = Database.query(expectedQueryString); List returnedResults = userQuery.getResults(); System.Assert.areEqual(expectedResults, returnedResults); @@ -101,7 +130,7 @@ private class Query_Tests { Query accountQuery = new Query(Schema.Account.SObjectType).addField(new SOQL.QueryField(Schema.Account.OwnerId)); - System.assertEquals(expectedQueryString, accountQuery.getQuery()); + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); List accounts = accountQuery.getResults(); } @@ -147,7 +176,7 @@ private class Query_Tests { System.Test.stopTest(); - System.assertEquals(expectedQuery, taskQuery.getQuery()); + System.Assert.areEqual(expectedQuery, taskQuery.getQuery()); } @IsTest @@ -156,7 +185,7 @@ private class Query_Tests { Query leadQuery = new Query(Schema.Lead.SObjectType).addField(new SOQL.QueryField(Schema.Lead.OwnerId)); - System.assertEquals(expectedQueryString, leadQuery.getQuery()); + System.Assert.areEqual(expectedQueryString, leadQuery.getQuery()); List expectedResults = Database.query(expectedQueryString); List returnedResults = leadQuery.getResults(); System.Assert.areEqual(expectedResults, returnedResults); @@ -172,7 +201,7 @@ private class Query_Tests { .includeRelatedRecords(Schema.Contact.AccountId, contactQuery) .addField(new SOQL.QueryField(Schema.Account.Type)); - System.assertEquals(expectedQueryString, accountQuery.getQuery()); + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); List accounts = accountQuery.getResults(); } @@ -193,13 +222,82 @@ private class Query_Tests { List accounts = accountQuery.getResults(); } + @IsTest + static void it_should_return_results_when_filtering_with_binds() + { + // SETUP + String expectedQueryString = 'SELECT Id, Name FROM Account WHERE CreatedDate >= :createdDateFilter'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'createdDateFilter' => Date.today() + }, + System.AccessLevel.SYSTEM_MODE + ); + Query accountQuery = new Query(Schema.Account.SObjectType) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today(), 'createdDateFilter')); + + // TEST + List returnedResults = accountQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + } + + @IsTest + static void it_should_run_with_system_mode() { + String expectedQueryString = 'SELECT Id, Name FROM Account LIMIT 1'; + + Query accountQuery = new Query(Schema.Account.SObjectType) + .limitTo(1) + .withAccessLevel(System.AccessLevel.SYSTEM_MODE); + + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + List expectedResults = Database.query(expectedQueryString); + List returnedResults; + Exception caughtException; + System.runAs(minimumAccessUser()) { + try { + returnedResults = accountQuery.getResults(); + } catch (Exception e) { + caughtException = e; + } + } + System.Assert.isNull(caughtException, 'Query should not throw exception when run in System Mode'); + System.Assert.areEqual(expectedResults, returnedResults); + } + + @IsTest + static void it_should_run_with_user_mode() { + String expectedQueryString = 'SELECT Id, Name FROM Account LIMIT 1'; + + Query accountQuery = new Query(Schema.Account.SObjectType) + .limitTo(1) + .withAccessLevel(System.AccessLevel.USER_MODE); + + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + List expectedResults = Database.query(expectedQueryString); + List returnedResults; + Exception caughtException; + System.runAs(minimumAccessUser()) { + try { + returnedResults = accountQuery.getResults(); + } catch (Exception e) { + caughtException = e; + } + } + System.Assert.isInstanceOfType(caughtException, System.QueryException.class, 'Query should throw exception when run in User Mode'); + System.Assert.isTrue(caughtException.getMessage().contains('sObject type \'Account\' is not supported'), 'Query should throw exception when run in User Mode'); + } + @IsTest static void it_includes_order_by_statement_for_single_field() { String expectedQueryString = 'SELECT Id, Name FROM Lead ORDER BY CreatedDate ASC NULLS FIRST'; Query leadQuery = new Query(Schema.Lead.SObjectType).orderByField(Schema.Lead.CreatedDate); - System.assertEquals(expectedQueryString, leadQuery.getQuery()); + System.Assert.areEqual(expectedQueryString, leadQuery.getQuery()); List expectedResults = Database.query(expectedQueryString); List returnedResults = leadQuery.getResults(); System.Assert.areEqual(expectedResults, returnedResults); @@ -211,7 +309,7 @@ private class Query_Tests { Query leadQuery = new Query(Schema.Lead.SObjectType).forReference(); - System.assertEquals(expectedQueryString, leadQuery.getQuery()); + System.Assert.areEqual(expectedQueryString, leadQuery.getQuery()); List expectedResults = Database.query(expectedQueryString); List returnedResults = leadQuery.getResults(); System.Assert.areEqual(expectedResults, returnedResults); @@ -223,7 +321,7 @@ private class Query_Tests { Query leadQuery = new Query(Schema.Lead.SObjectType).forUpdate(); - System.assertEquals(expectedQueryString, leadQuery.getQuery()); + System.Assert.areEqual(expectedQueryString, leadQuery.getQuery()); List expectedResults = Database.query(expectedQueryString); List returnedResults = leadQuery.getResults(); System.Assert.areEqual(expectedResults, returnedResults); @@ -235,7 +333,7 @@ private class Query_Tests { Query leadQuery = new Query(Schema.Lead.SObjectType).forView(); - System.assertEquals(expectedQueryString, leadQuery.getQuery()); + System.Assert.areEqual(expectedQueryString, leadQuery.getQuery()); List expectedResults = Database.query(expectedQueryString); List returnedResults = leadQuery.getResults(); System.Assert.areEqual(expectedResults, returnedResults); @@ -261,11 +359,11 @@ private class Query_Tests { Query userQuery = new Query(Schema.User.SObjectType).limitTo(1); // First, verify that caching is not enabled by default - System.assertEquals(0, System.Limits.getQueries()); + System.Assert.areEqual(0, System.Limits.getQueries()); for (Integer i = 0; i < loops; i++) { userQuery.getResults(); } - System.assertEquals(loops, System.Limits.getQueries()); + System.Assert.areEqual(loops, System.Limits.getQueries()); System.Test.startTest(); @@ -273,8 +371,302 @@ private class Query_Tests { for (Integer i = 0; i < loops; i++) { userQuery.getResults(); } - System.assertEquals(1, System.Limits.getQueries()); + System.Assert.areEqual(1, System.Limits.getQueries()); System.Test.stopTest(); } + + @IsTest + static void it_will_set_a_bind_key() + { + // SETUP + String expectedQueryString = 'SELECT CreatedDate, Id, Name FROM Account WHERE CreatedDate >= :dateFilter ORDER BY CreatedDate ASC NULLS FIRST LIMIT 1'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'dateFilter' => Date.today().addDays(-1) + }, + System.AccessLevel.SYSTEM_MODE + ); + Query accountQuery = new Query(Schema.Account.SObjectType) + .addField(Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1), 'dateFilter')) + .orderByField(Schema.Account.CreatedDate, SOQL.SortOrder.ASCENDING) + .limitTo(1); + + // TEST + accountQuery.setBind('dateFilter', Date.today().addDays(-1)); + List returnedResults = accountQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse(returnedResults[0].CreatedDate < Date.today().addDays(-1)); + } + } + + @IsTest + static void it_will_set_multiple_bind_keys() + { + // SETUP + String expectedQueryString = 'SELECT CreatedDate, Id, Name FROM Account WHERE CreatedDate >= :minDateFilter AND CreatedDate < :maxDateFilter ORDER BY CreatedDate ASC NULLS FIRST LIMIT 1'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'minDateFilter' => Date.today().addDays(-7), + 'maxDateFilter' => Date.today() + }, + System.AccessLevel.SYSTEM_MODE + ); + Query accountQuery = new Query(Schema.Account.SObjectType) + .addField(Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1), 'minDateFilter')) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.LESS_THAN, Date.today().addDays(-1), 'maxDateFilter')) + .orderByField(Schema.Account.CreatedDate, SOQL.SortOrder.ASCENDING) + .limitTo(1); + + // TEST + accountQuery.setBind('minDateFilter', Date.today().addDays(-7)); + accountQuery.setBind('maxDateFilter', Date.today()); + List returnedResults = accountQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse(returnedResults[0].CreatedDate < Date.today().addDays(-7)); + System.Assert.isFalse(returnedResults[0].CreatedDate >= Date.today()); + } + } + + @IsTest + static void it_will_remove_a_bind_key() + { + // SETUP + String expectedQueryString = 'SELECT CreatedDate, Id, Name FROM Account WHERE CreatedDate >= 2000-01-01T05:00:00Z'; + Query accountQuery = new Query(Schema.Account.SObjectType) + .addField(Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Datetime.newInstance(2000, 1, 1), 'dateFilter')); + + // TEST + accountQuery.removeBind('dateFilter'); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + } + + @IsTest + static void it_will_clear_all_bind_keys() + { + // SETUP + String expectedQueryString = 'SELECT CreatedDate, Id, Name FROM Account WHERE CreatedDate >= 2000-01-01T05:00:00Z AND CreatedDate < 2001-01-01T05:00:00Z'; + Query accountQuery = new Query(Schema.Account.SObjectType) + .addField(Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Datetime.newInstance(2000, 1, 1), 'minDateFilter')) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.LESS_THAN, Datetime.newInstance(2001, 1, 1), 'maxDateFilter')); + + // TEST + accountQuery.clearBinds(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + } + + static User minimumAccessUser() { + return new User( + Alias = 'newUser', + Email = 'newuser@testorg.com', + EmailEncodingKey = 'UTF-8', + LastName = 'Testing', + LanguageLocaleKey = 'en_US', + LocaleSidKey = 'en_US', + ProfileId = [SELECT Id FROM Profile WHERE Name = 'Minimum Access - Salesforce'].Id, + TimeZoneSidKey = 'GMT', + UserName = 'newuser@testorg.com' + ); + } + + @IsTest + static void it_will_generate_a_bind_key() + { + // SETUP + String expectedQueryString = 'SELECT CreatedDate, Id, Name FROM Account WHERE CreatedDate >= :bindVar0 ORDER BY CreatedDate ASC NULLS FIRST LIMIT 1'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'bindVar0' => Date.today().addDays(-1) + }, + System.AccessLevel.SYSTEM_MODE + ); + Query accountQuery = new Query(Schema.Account.SObjectType) + .generateBindVariableKeys() + .addField(Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1))) + .orderByField(Schema.Account.CreatedDate, SOQL.SortOrder.ASCENDING) + .limitTo(1); + + // TEST + List returnedResults = accountQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse(returnedResults[0].CreatedDate < Date.today().addDays(-1)); + } + } + + @IsTest + static void it_will_generate_a_bind_key_when_instructed_before_filter_is_added() + { + // SETUP + String expectedQueryString = 'SELECT CreatedDate, Id, Name FROM Account WHERE CreatedDate >= :bindVar0 ORDER BY CreatedDate ASC NULLS FIRST LIMIT 1'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'bindVar0' => Date.today().addDays(-1) + }, + System.AccessLevel.SYSTEM_MODE + ); + Query accountQuery = new Query(Schema.Account.SObjectType) + .generateBindVariableKeys() + .addField(Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1))) + .orderByField(Schema.Account.CreatedDate, SOQL.SortOrder.ASCENDING) + .limitTo(1); + + // TEST + List returnedResults = accountQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse(returnedResults[0].CreatedDate < Date.today().addDays(-1)); + } + } + + @IsTest + static void it_will_generate_a_bind_key_when_instructed_after_filter_is_added() + { + // SETUP + String expectedQueryString = 'SELECT CreatedDate, Id, Name FROM Account WHERE CreatedDate >= :bindVar0 ORDER BY CreatedDate ASC NULLS FIRST LIMIT 1'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'bindVar0' => Date.today().addDays(-1) + }, + System.AccessLevel.SYSTEM_MODE + ); + Query accountQuery = new Query(Schema.Account.SObjectType) + .addField(Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1))) + .orderByField(Schema.Account.CreatedDate, SOQL.SortOrder.ASCENDING) + .limitTo(1) + .generateBindVariableKeys(); + + // TEST + List returnedResults = accountQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse(returnedResults[0].CreatedDate < Date.today().addDays(-1)); + } + } + + @IsTest + static void it_will_not_generate_a_bind_key_if_already_specified() + { + // SETUP + String expectedQueryString = 'SELECT CreatedDate, Id, Name FROM Account WHERE CreatedDate >= :dateFilter ORDER BY CreatedDate ASC NULLS FIRST LIMIT 1'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'dateFilter' => Date.today().addDays(-1) + }, + System.AccessLevel.SYSTEM_MODE + ); + Query accountQuery = new Query(Schema.Account.SObjectType) + .addField(Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1), 'dateFilter')) + .orderByField(Schema.Account.CreatedDate, SOQL.SortOrder.ASCENDING) + .limitTo(1) + .generateBindVariableKeys(); + + // TEST + List returnedResults = accountQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse(returnedResults[0].CreatedDate < Date.today().addDays(-1)); + } + } + + @IsTest + static void it_will_generate_a_bind_key_if_removed() + { + // SETUP + String expectedQueryString = 'SELECT CreatedDate, Id, Name FROM Account WHERE CreatedDate >= :bindVar0 ORDER BY CreatedDate ASC NULLS FIRST LIMIT 1'; + List expectedResults = Database.queryWithBinds( + expectedQueryString, + new Map { + 'bindVar0' => Date.today().addDays(-1) + }, + System.AccessLevel.SYSTEM_MODE + ); + Query accountQuery = new Query(Schema.Account.SObjectType) + .addField(Schema.Account.CreatedDate) + .filterWhere(new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.GREATER_THAN_OR_EQUAL_TO, Date.today().addMonths(-1), 'dateFilter')) + .orderByField(Schema.Account.CreatedDate, SOQL.SortOrder.ASCENDING) + .limitTo(1) + .removeBind('dateFilter') + .generateBindVariableKeys(); + + // TEST + List returnedResults = accountQuery.getResults(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, accountQuery.getQuery()); + System.Assert.areEqual(expectedResults, returnedResults); + if (!returnedResults.isEmpty()) + { + System.Assert.isFalse(returnedResults[0].CreatedDate < Date.today().addDays(-1)); + } + } + + @IsTest + static void it_will_construct_a_where_clause_based_on_custom_logic() + { + // SETUP + String expectedQueryString = 'SELECT Id, Name FROM Account WHERE (CreatedDate = :bindVar1 OR LastModifiedDate = :bindVar0) AND (Name LIKE :bindVar3 OR Parent.Name LIKE :bindVar2)'; + Query accountQuery = new Query(Schema.Account.SObjectType) + .generateBindVariableKeys() + .filterWhere(new SOQL.QueryFilter(Schema.Account.LastModifiedDate, SOQL.Operator.EQUALS, Date.today())) + .orFilterWhere( + new List { + new SOQL.QueryFilter(new SOQL.QueryField(Schema.Account.getSObjectType(), 'Parent.Name'), SOQL.Operator.IS_LIKE, 'Test%'), + new SOQL.QueryFilter(Schema.Account.CreatedDate, SOQL.Operator.EQUALS, Date.today().addDays(-1)) + } + ) + .filterWhere(new SOQL.QueryFilter(Schema.Account.Name, SOQL.Operator.IS_LIKE, 'Smith%')) + .setWhereFilterLogic('(2 OR 1) AND (4 OR 3)'); + + // TEST + String actualQueryString = accountQuery.getQuery(); + + // VERIFY + System.Assert.areEqual(expectedQueryString, actualQueryString); + } + } diff --git a/nebula-query-and-search/tests/classes/Query_Tests.cls-meta.xml b/nebula-query-and-search/tests/classes/Query_Tests.cls-meta.xml index 133fce1e..c01f6433 100644 --- a/nebula-query-and-search/tests/classes/Query_Tests.cls-meta.xml +++ b/nebula-query-and-search/tests/classes/Query_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 58.0 + 61.0 Active diff --git a/nebula-query-and-search/tests/classes/RecordSearch_Tests.cls b/nebula-query-and-search/tests/classes/RecordSearch_Tests.cls index 53805d8a..66a7a428 100644 --- a/nebula-query-and-search/tests/classes/RecordSearch_Tests.cls +++ b/nebula-query-and-search/tests/classes/RecordSearch_Tests.cls @@ -127,6 +127,56 @@ private class RecordSearch_Tests { List userSearchResults = userSearch.getFirstResults(); } + @IsTest + static void it_should_run_with_system_mode() { + String expectedSearchQueryString = 'FIND \'' + System.UserInfo.getUserEmail() + '\' IN ALL FIELDS RETURNING Contact(Id, Name)'; + + Query contactQuery = new Query(Schema.Contact.SObjectType); + RecordSearch contactSearch = new RecordSearch(System.UserInfo.getUserEmail(), contactQuery) + .withAccessLevel(System.AccessLevel.SYSTEM_MODE); + + System.assertEquals(expectedSearchQueryString, contactSearch.getSearch()); + List contactSearchResults = contactSearch.getFirstResults(); + + List> expectedResults = Search.query(expectedSearchQueryString); + List> returnedResults; + Exception caughtException; + System.runAs(minimumAccessUser()) { + try { + returnedResults = contactSearch.getResults(); + } catch (Exception e) { + caughtException = e; + } + } + System.Assert.isNull(caughtException, 'Search should not throw exception when run in System Mode'); + System.Assert.areEqual(expectedResults, returnedResults); + } + + @IsTest + static void it_should_run_with_user_mode() { + String expectedSearchQueryString = 'FIND \'' + System.UserInfo.getUserEmail() + '\' IN ALL FIELDS RETURNING Contact(Id, Name)'; + + Query contactQuery = new Query(Schema.Contact.SObjectType); + RecordSearch contactSearch = new RecordSearch(System.UserInfo.getUserEmail(), contactQuery) + .withAccessLevel(System.AccessLevel.USER_MODE); + + System.assertEquals(expectedSearchQueryString, contactSearch.getSearch()); + List contactSearchResults = contactSearch.getFirstResults(); + + List> expectedResults = Search.query(expectedSearchQueryString); + List> returnedResults; + Exception caughtException; + System.runAs(minimumAccessUser()) { + try { + returnedResults = contactSearch.getResults(); + } catch (Exception e) { + caughtException = e; + } + } + System.Assert.isInstanceOfType(caughtException, System.QueryException.class, 'Search should throw exception when run in User Mode'); + System.Assert.isTrue(caughtException.getMessage().contains('sObject type \'contact\' is not supported'), 'Search should throw exception when run in User Mode'); + } + @IsTest static void it_should_cache_search_results_when_enabled() { Integer loops = 4; @@ -150,4 +200,18 @@ private class RecordSearch_Tests { System.Test.stopTest(); } + + static User minimumAccessUser() { + return new User( + Alias = 'newUser', + Email = 'newuser@testorg.com', + EmailEncodingKey = 'UTF-8', + LastName = 'Testing', + LanguageLocaleKey = 'en_US', + LocaleSidKey = 'en_US', + ProfileId = [SELECT Id FROM Profile WHERE Name = 'Minimum Access - Salesforce'].Id, + TimeZoneSidKey = 'GMT', + UserName = 'newuser@testorg.com' + ); + } } diff --git a/nebula-query-and-search/tests/classes/RecordSearch_Tests.cls-meta.xml b/nebula-query-and-search/tests/classes/RecordSearch_Tests.cls-meta.xml index 133fce1e..c01f6433 100644 --- a/nebula-query-and-search/tests/classes/RecordSearch_Tests.cls-meta.xml +++ b/nebula-query-and-search/tests/classes/RecordSearch_Tests.cls-meta.xml @@ -1,5 +1,5 @@ - 58.0 + 61.0 Active diff --git a/sfdx-project.json b/sfdx-project.json index ea0452fd..6644f90c 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -2,7 +2,7 @@ "name": "Nebula Query & Search", "namespace": "", "sfdcLoginUrl": "https://login.salesforce.com", - "sourceApiVersion": "58.0", + "sourceApiVersion": "61.0", "plugins": { "sfdx-plugin-prettier": { "enabled": true