From 68306b3fcc911260a35dee17d22b01326edd61dd Mon Sep 17 00:00:00 2001 From: Robert Baillie Date: Tue, 15 Feb 2022 10:40:37 +0000 Subject: [PATCH] Removed possibility of null offset in search window Ensure that query factory does not add OFFSET 0 into SOQL clauses --- .../classes/common/fflib_QueryFactory.cls | 184 +++++++++--------- .../default/classes/search/SearchWindow.cls | 2 +- .../classes/search/tests/SearchWindowTest.cls | 15 ++ 3 files changed, 108 insertions(+), 93 deletions(-) diff --git a/framework/default/fflib/default/classes/common/fflib_QueryFactory.cls b/framework/default/fflib/default/classes/common/fflib_QueryFactory.cls index 0ac00d30090..e82f2cda6ef 100644 --- a/framework/default/fflib/default/classes/common/fflib_QueryFactory.cls +++ b/framework/default/fflib/default/classes/common/fflib_QueryFactory.cls @@ -2,22 +2,22 @@ * Copyright (c), FinancialForce.com, inc * All rights reserved. * - * Redistribution and use in source and binary forms, with or without modification, + * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * - * - Redistributions of source code must retain the above copyright notice, + * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. - * - Neither the name of the FinancialForce.com, inc nor the names of its contributors - * may be used to endorse or promote products derived from this software without + * - Neither the name of the FinancialForce.com, inc nor the names of its contributors + * may be used to endorse or promote products derived from this software without * specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL - * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) @@ -29,18 +29,18 @@ * This class is not meant to be used as a replacement for all SOQL queries, and due to the relatively high overhead in both CPU and describe calls * should be used in places where highly dynamic queries, such as those that include field sets or are mutated heavily * in multiple locations are a good fit for use with fflib_QueryFactory. - * + * * To use call construct a new instance for each query you intend to make. * To add additional fields to the query make use of the selectField(s) methods. * * Currently the WHERE clause of the query is manipulated as a single string, and is decidedly less OO-styled than other methods. * This is expected to be expanded upon in the future. - * + * * To include one or more ORDER BY clause(s), use one of the addOrdering methods. If not specified, the "NULLS FIRST" keywords * will be included by default. Constructing Ordering instances manually is discouraged. - * - * Subselect Queries are supported with the subselectQuery methods. - * More than one sub-query can be added to a single query, but sub-queries can only be 1 level deep. + * + * Subselect Queries are supported with the subselectQuery methods. + * More than one sub-query can be added to a single query, but sub-queries can only be 1 level deep. * An exception will thrown from the subselectQuery method when there is an attempt to add a subquery to a sub-query * or to add a subquery to a query with an invalid relationship. * @@ -61,22 +61,22 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr **/ public Schema.SObjectType table {get; private set;} @TestVisible - private Set fields; + private Set fields; private String conditionExpression; private Integer limitCount; private Integer offsetCount; private List order; /** * Integrate checking for READ Field Level Security within the selectField(s) methods - * This can optionally be enforced (or not) by calling the setEnforceFLS method prior to calling + * This can optionally be enforced (or not) by calling the setEnforceFLS method prior to calling * one of the selectField or selectFieldset methods. **/ private Boolean enforceFLS; - + private Boolean sortSelectFields = true; - + /** - * The relationship and subselectQueryMap variables are used to support subselect queries. Subselects can be added to + * The relationship and subselectQueryMap variables are used to support subselect queries. Subselects can be added to * a query, as long as it isn't a subselect query itself. You may have many subselects inside * a query, but they may only be 1 level deep (no subselect inside a subselect) * to add a subselect, call the subselectQuery method, passing in the ChildRelationship. @@ -89,8 +89,8 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr Schema.SObjectField token = fflib_SObjectDescribe.getDescribe(table).getField(fieldName.toLowerCase()); if(token == null) throw new InvalidFieldException(fieldName,this.table); - if (enforceFLS) - fflib_SecurityUtils.checkFieldIsReadable(this.table, token); + if (enforceFLS) + fflib_SecurityUtils.checkFieldIsReadable(this.table, token); return token.getDescribe().getName(); } @@ -102,7 +102,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr String field = i.next(); Schema.SObjectField token = fflib_SObjectDescribe.getDescribe(lastSObjectType).getField(field.toLowerCase()); DescribeFieldResult tokenDescribe = token != null ? token.getDescribe() : null; - + if (token != null && enforceFLS) { fflib_SecurityUtils.checkFieldIsReadable(lastSObjectType, token); } @@ -126,7 +126,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr @TestVisible private static String getFieldTokenPath(Schema.SObjectField field){ if(field == null){ - throw new InvalidFieldException('Invalid field: null'); + throw new InvalidFieldException('Invalid field: null'); } return field.getDescribe().getName(); } @@ -134,7 +134,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * fflib_QueryFactory instances will be considered equal if they produce the same SOQL query. * A faster comparison will first be attempted to check if they apply to the same table, and contain the same number of fields selected. - * This method will never return true if the provided object is not an instance of fflib_QueryFactory. + * This method will never return true if the provided object is not an instance of fflib_QueryFactory. * @param obj the object to check equality of. **/ public Boolean equals(Object obj){ @@ -157,18 +157,18 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * Construct a new fflib_QueryFactory instance with no options other than the FROM clause and the relationship. - * This should be used when constructing a subquery query for addition to a parent query. + * This should be used when constructing a subquery query for addition to a parent query. * Objects created with this constructor cannot be added to another object using the subselectQuery method. * You *must* call selectField(s) before {@link #toSOQL} will return a valid, runnable query. * @param relationship the ChildRelationship to be used in the FROM Clause of the resultant Query (when set overrides value of table). This sets the value of {@link #relationship} and {@link #table}. **/ private fflib_QueryFactory(Schema.ChildRelationship relationship){ - this(relationship.getChildSObject()); + this(relationship.getChildSObject()); this.relationship = relationship; } /** - * This method checks to see if the User has Read Access on {@link #table}. + * This method checks to see if the User has Read Access on {@link #table}. * Asserts true if User has access. **/ public fflib_QueryFactory assertIsAccessible(){ @@ -197,16 +197,16 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr this.sortSelectFields = doSort; return this; } - + /** * Selects a single field from the SObject specified in {@link #table}. * Selecting fields is idempotent, if this field is already selected calling this method will have no additional impact. * @param fieldName the API name of the field to add to the query's SELECT clause. **/ - public fflib_QueryFactory selectField(String fieldName){ + public fflib_QueryFactory selectField(String fieldName){ fields.add( getFieldPath(fieldName) ); return this; - } + } /** * Selects a field, avoiding the possible ambiguity of String API names. * @see #selectField(String) @@ -216,7 +216,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr public fflib_QueryFactory selectField(Schema.SObjectField field){ if(field == null) throw new InvalidFieldException(null,this.table); - if (enforceFLS) + if (enforceFLS) fflib_SecurityUtils.checkFieldIsReadable(table, field); fields.add( getFieldTokenPath(field) ); return this; @@ -228,7 +228,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr public fflib_QueryFactory selectFields(Set fieldNames){ for(String fieldName:fieldNames){ fields.add( getFieldPath(fieldName) ); - } + } return this; } /** @@ -248,9 +248,9 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr public fflib_QueryFactory selectFields(Set fields){ for(Schema.SObjectField token:fields){ if(token == null) - throw new InvalidFieldException(); - if (enforceFLS) - fflib_SecurityUtils.checkFieldIsReadable(table, token); + throw new InvalidFieldException(); + if (enforceFLS) + fflib_SecurityUtils.checkFieldIsReadable(table, token); this.fields.add( getFieldTokenPath(token) ); } return this; @@ -258,14 +258,14 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * Selects multiple fields. This acts the same as calling {@link #selectField(Schema.SObjectField)} multiple times. * @param fields the set of {@link Schema.SObjectField}s to select. - * @exception InvalidFieldException if the fields are null {@code fields}. + * @exception InvalidFieldException if the fields are null {@code fields}. **/ public fflib_QueryFactory selectFields(List fields){ for(Schema.SObjectField token:fields){ if(token == null) throw new InvalidFieldException(); - if (enforceFLS) - fflib_SecurityUtils.checkFieldIsReadable(table, token); + if (enforceFLS) + fflib_SecurityUtils.checkFieldIsReadable(table, token); this.fields.add( getFieldTokenPath(token) ); } return this; @@ -278,11 +278,11 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr } /** * This is equivalent to iterating the fields in the field set and calling {@link #selectField(String)} on each. - * @param fieldSet Select all fields included in the field set. + * @param fieldSet Select all fields included in the field set. * @param allowCrossObject if false this method will throw an exception if any fields in the field set reference fields on a related record. - * @exception InvalidFieldSetException if the fieldset is invalid for table {@code fields}. + * @exception InvalidFieldSetException if the fieldset is invalid for table {@code fields}. **/ - public fflib_QueryFactory selectFieldSet(Schema.FieldSet fieldSet, Boolean allowCrossObject){ + public fflib_QueryFactory selectFieldSet(Schema.FieldSet fieldSet, Boolean allowCrossObject){ if(fieldSet.getSObjectType() != table) throw new InvalidFieldSetException('Field set "'+fieldSet.getName()+'" is not for SObject type "'+table+'"'); for(Schema.FieldSetMember field: fieldSet.getFields()){ @@ -356,7 +356,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * @returns the selected fields **/ - public Set getSelectedFields() { + public Set getSelectedFields() { return this.fields; } @@ -364,10 +364,10 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned. * If not, a new one will be created and returned. * @deprecated Replaced by {@link #subselectQuery(String relationshipName)} and {@link #subselectQuery(ChildRelationship relationship)} - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param related The related object type **/ - public fflib_QueryFactory subselectQuery(SObjectType related){ + public fflib_QueryFactory subselectQuery(SObjectType related){ System.debug(LoggingLevel.WARN, 'fflib_QueryFactory.subselectQuery(Schema.SObjectType) is deprecated and will be removed in a future release. Use fflib_QueryFactory.subselectQuery(String) or fflib_QueryFactory.subselectQuery(ChildRelationship) instead.'); return setSubselectQuery(getChildRelationship(related), false); } @@ -376,29 +376,29 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned. * If not, a new one will be created and returned. * @deprecated Replaced by {@link #subselectQuery(String relationshipName, Boolean assertIsAccessible)} and {@link #subselectQuery(ChildRelationship relationship, Boolean assertIsAccessible)} - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param related The related object type * @param assertIsAccessible indicates whether to check if the user has access to the subquery object **/ public fflib_QueryFactory subselectQuery(SObjectType related, Boolean assertIsAccessible){ - System.debug(LoggingLevel.WARN, 'fflib_QueryFactory.subselectQuery(Schema.SObjectType, Boolean) is deprecated and will be removed in a future release. Use fflib_QueryFactory.subselectQuery(String, Boolean) or fflib_QueryFactory.subselectQuery(ChildRelationship, Boolean) instead.'); + System.debug(LoggingLevel.WARN, 'fflib_QueryFactory.subselectQuery(Schema.SObjectType, Boolean) is deprecated and will be removed in a future release. Use fflib_QueryFactory.subselectQuery(String, Boolean) or fflib_QueryFactory.subselectQuery(ChildRelationship, Boolean) instead.'); return setSubselectQuery(getChildRelationship(related), assertIsAccessible); } /** * Add a subquery query to this query. If a subquery for this relationshipName already exists, it will be returned. * If not, a new one will be created and returned. - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationshipName The relationshipName to be added as a subquery **/ - public fflib_QueryFactory subselectQuery(String relationshipName){ + public fflib_QueryFactory subselectQuery(String relationshipName){ return subselectQuery(relationshipName, false); } /** * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned. * If not, a new one will be created and returned. - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationshipName The relationshipName to be added as a subquery * @param assertIsAccessible indicates whether to check if the user has access to the subquery object **/ @@ -407,23 +407,23 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr if (relationship != null) { return setSubselectQuery(relationship, assertIsAccessible); } - throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery with relationshipName = '+relationshipName +'. Relationship does not exist for ' + table.getDescribe().getName()); + throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery with relationshipName = '+relationshipName +'. Relationship does not exist for ' + table.getDescribe().getName()); } /** * Add a subquery query to this query. If a subquery for this relationshipName already exists, it will be returned. * If not, a new one will be created and returned. - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationship The ChildRelationship to be added as a subquery **/ - public fflib_QueryFactory subselectQuery(Schema.ChildRelationship relationship){ + public fflib_QueryFactory subselectQuery(Schema.ChildRelationship relationship){ return subselectQuery(relationship, false); } /** * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned. * If not, a new one will be created and returned. - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationship The ChildRelationship to be added as a subquery * @param assertIsAccessible indicates whether to check if the user has access to the subquery object **/ @@ -434,22 +434,22 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned. * If not, a new one will be created and returned. - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationship The ChildRelationship to be added as a subquery **/ private fflib_QueryFactory setSubselectQuery(Schema.ChildRelationship relationship, Boolean assertIsAccessible){ if (this.relationship != null){ throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery. You may not add a subselect query to a subselect query.'); - } + } if (this.subselectQueryMap == null){ this.subselectQueryMap = new Map(); } if (this.subselectQueryMap.containsKey(relationship)){ return subselectQueryMap.get(relationship); } - + fflib_QueryFactory subSelectQuery = new fflib_QueryFactory(relationship); - + //The child queryFactory should be configured in the same way as the parent by default - can override after if required subSelectQuery.setSortSelectFields(sortSelectFields); @@ -466,7 +466,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr public List getSubselectQueries(){ if (subselectQueryMap != null) { return subselectQueryMap.values(); - } + } return null; } @@ -476,11 +476,11 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr **/ private Schema.ChildRelationship getChildRelationship(SObjectType objType){ for (Schema.ChildRelationship childRow : table.getDescribe().getChildRelationships()){ - //occasionally on some standard objects (Like Contact child of Contact) do not have a relationship name. + //occasionally on some standard objects (Like Contact child of Contact) do not have a relationship name. //if there is no relationship name, we cannot query on it, so throw an exception. - if (childRow.getChildSObject() == objType && childRow.getRelationshipName() != null){ + if (childRow.getChildSObject() == objType && childRow.getRelationshipName() != null){ return childRow; - } + } } throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery. Invalid relationship for table '+table + ' and objtype='+objType); } @@ -491,38 +491,38 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr **/ private Schema.ChildRelationship getChildRelationship(String relationshipName){ for (Schema.ChildRelationship childRow : table.getDescribe().getChildRelationships()){ - if (childRow.getRelationshipName() == relationshipName){ + if (childRow.getRelationshipName() == relationshipName){ return childRow; - } + } } return null; } /** - * Add a field to be sorted on. This may be a direct field or a field + * Add a field to be sorted on. This may be a direct field or a field * related through an object lookup or master-detail relationship. * Use the set to store unique field names, since we only want to sort * by the same field one time. The sort expressions are stored in a list * so that they are applied to the SOQL in the same order that they - * were added in. + * were added in. * @param fieldName The string value of the field to be sorted on * @param direction the direction to be sorted on (ASCENDING or DESCENDING) * @param nullsLast whether to sort null values last (NULLS LAST keyword included). - **/ + **/ public fflib_QueryFactory addOrdering(String fieldName, SortOrder direction, Boolean nullsLast){ order.add( new Ordering(getFieldPath(fieldName), direction, nullsLast) - ); + ); return this; } /** - * Add a field to be sorted on. This may be a direct field or a field + * Add a field to be sorted on. This may be a direct field or a field * related through an object lookup or master-detail relationship. * Use the set to store unique field names, since we only want to sort * by the same field one time. The sort expressions are stored in a list * so that they are applied to the SOQL in the same order that they - * were added in. + * were added in. * @param field The SObjectField to sort. This can only be a direct reference. * @param direction the direction to be sorted on (ASCENDING or DESCENDING) * @param nullsLast whether to sort null values last (NULLS LAST keyword included). @@ -530,37 +530,37 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr public fflib_QueryFactory addOrdering(SObjectField field, SortOrder direction, Boolean nullsLast){ order.add( new Ordering(getFieldTokenPath(field), direction, nullsLast) - ); + ); return this; } /** - * Add a field to be sorted on. This may be a direct field or a field + * Add a field to be sorted on. This may be a direct field or a field * related through an object lookup or master-detail relationship. * Use the set to store unique field names, since we only want to sort * by the same field one time. The sort expressions are stored in a list * so that they are applied to the SOQL in the same order that they - * were added in. - * The "NULLS FIRST" keywords will be included by default. If "NULLS LAST" + * were added in. + * The "NULLS FIRST" keywords will be included by default. If "NULLS LAST" * is required, use one of the overloaded addOrdering methods which include this parameter. * @param fieldName The string value of the field to be sorted on * @param direction the direction to be sorted on (ASCENDING or DESCENDING) - **/ + **/ public fflib_QueryFactory addOrdering(String fieldName, SortOrder direction){ order.add( new Ordering(getFieldPath(fieldName), direction) - ); + ); return this; } /** - * Add a field to be sorted on. This may be a direct field or a field + * Add a field to be sorted on. This may be a direct field or a field * related through an object lookup or master-detail relationship. * Use the set to store unique field names, since we only want to sort * by the same field one time. The sort expressions are stored in a list * so that they are applied to the SOQL in the same order that they - * were added in. - * The "NULLS FIRST" keywords will be included by default. If "NULLS LAST" + * were added in. + * The "NULLS FIRST" keywords will be included by default. If "NULLS LAST" * is required, use one of the overloaded addOrdering methods which include this parameter. * @param field The SObjectField to sort. This can only be a direct reference. * @param direction the direction to be sorted on (ASCENDING or DESCENDING) @@ -568,7 +568,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr public fflib_QueryFactory addOrdering(SObjectField field, SortOrder direction){ order.add( new Ordering(getFieldTokenPath(field), direction) - ); + ); return this; } @@ -646,18 +646,18 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr result += 'Id'; }else { List fieldsToQuery = new List(fields); - + if(sortSelectFields){ - fieldsToQuery.sort(); - } - + fieldsToQuery.sort(); + } + result += String.join(fieldsToQuery,', '); } - + if(subselectQueryMap != null && !subselectQueryMap.isEmpty()){ for (fflib_QueryFactory childRow : subselectQueryMap.values()){ result += ', (' + childRow.toSOQL() + ') '; - } + } } result += ' FROM ' + (relationship != null ? relationship.getRelationshipName() : table.getDescribe().getName()); if( String.isNotBlank( conditionExpression ) ) @@ -669,11 +669,11 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr result += o.toSOQL() +', '; result = result.substring(0,result.length()-2); } - + if(limitCount != null) result += ' LIMIT '+limitCount; - if(offsetCount != null) + if(offsetCount != null && offsetCount > 0) result += ' OFFSET '+offsetCount; return result; @@ -683,7 +683,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * Create a "deep" clone of this object that can be safely mutated without affecting the cloned instance * @return a deep clone of this fflib_QueryFactory **/ - public fflib_QueryFactory deepClone(){ + public fflib_QueryFactory deepClone(){ fflib_QueryFactory clone = new fflib_QueryFactory(this.table) .setLimit(this.limitCount) @@ -706,7 +706,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr return clone; } - + public class Ordering{ private SortOrder direction; private boolean nullsLast; @@ -750,7 +750,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr } - + public class InvalidFieldException extends Exception{ private String fieldName; private Schema.SObjectType objectType; @@ -762,5 +762,5 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr } public class InvalidFieldSetException extends Exception{} public class NonReferenceFieldException extends Exception{} - public class InvalidSubqueryRelationshipException extends Exception{} + public class InvalidSubqueryRelationshipException extends Exception{} } diff --git a/framework/default/ortoo-core/default/classes/search/SearchWindow.cls b/framework/default/ortoo-core/default/classes/search/SearchWindow.cls index f6266f34f5f..1d8078fb532 100644 --- a/framework/default/ortoo-core/default/classes/search/SearchWindow.cls +++ b/framework/default/ortoo-core/default/classes/search/SearchWindow.cls @@ -25,7 +25,7 @@ public inherited sharing class SearchWindow Integer length = deriveInteger( properties, 'length' ); Contract.requires( length > 0 || length == null, 'configure called with a negative or zero length' ); - this.offset = offset > 0 ? offset : null; + this.offset = offset; this.length = length; return this; diff --git a/framework/default/ortoo-core/default/classes/search/tests/SearchWindowTest.cls b/framework/default/ortoo-core/default/classes/search/tests/SearchWindowTest.cls index e5e0a44bebd..050c9abcf75 100644 --- a/framework/default/ortoo-core/default/classes/search/tests/SearchWindowTest.cls +++ b/framework/default/ortoo-core/default/classes/search/tests/SearchWindowTest.cls @@ -17,6 +17,21 @@ public inherited sharing class SearchWindowTest System.assertEquals( 20, searchWindow.length, 'configure, when called with offset and length properties, will store the length as a member variable' ); } + @isTest + private static void configure_whenCalledWithZeroOffset_storesItAsMemberVariable() // NOPMD: Test method name format + { + Map properties = new Map{ + 'offset' => 0, + 'length' => 20 + }; + + Test.startTest(); + SearchWindow searchWindow = new SearchWindow().configure( properties ); + Test.stopTest(); + + System.assertEquals( 0, searchWindow.offset, 'configure, when called with zero offset, will store the offset as a member variable' ); + } + @isTest private static void configure_whenPassedANullProperties_throwsAnException() // NOPMD: Test method name format {