@@ -61,7 +61,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
6161 **/
6262 public Schema.SObjectType table {get ; private set ;}
6363 @testVisible
64- private List <QueryField > fields ;
64+ private Set <QueryField > fields ;
6565 private String conditionExpression ;
6666 private Integer limitCount ;
6767 private Integer offset ;
@@ -71,10 +71,9 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
7171 /* This can optionally be enforced (or not) by calling the setEnforceFLS method prior to calling
7272 /* one of the selectField or selectFieldset methods.
7373 **/
74- @TestVisible
75- private Boolean enforceFLS = false ;
76- @TestVisible
77- private Boolean lightweight = false ;
74+ private Boolean enforceFLS ;
75+
76+ private Boolean sortSelectFields = true ;
7877
7978 /**
8079 * The relationship and subselectQueryMap variables are used to support subselect queries. Subselects can be added to
@@ -86,12 +85,6 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
8685 private Map <Schema .ChildRelationship , fflib_QueryFactory > subselectQueryMap ;
8786
8887 private QueryField getFieldToken (String fieldName ){
89-
90- // FLS will not be enforced, so we are going to take a lot of shortcuts in the name of performance
91- if (this .lightweight ) {
92- return new LightweightQueryField (fieldName );
93- }
94-
9588 QueryField result ;
9689 if (! fieldName .contains (' .' )){ // single field
9790 Schema .SObjectField token = fflib_SObjectDescribe .getDescribe (table ).getField (fieldName .toLowerCase ());
@@ -137,31 +130,19 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
137130 return false ;
138131 return ((fflib_QueryFactory )obj ).toSOQL () == this .toSOQL ();
139132 }
140-
133+
141134 /**
142135 * Construct a new fflib_QueryFactory instance with no options other than the FROM caluse.
143136 * You *must* call selectField(s) before {@link #toSOQL} will return a valid, runnable query.
144137 * @param table the SObject to be used in the FROM clause of the resultant query. This sets the value of {@link #table}.
145138 **/
146139 public fflib_QueryFactory (Schema.SObjectType table ){
147- this (table , false );
148- }
149-
150- /**
151- * Construct a new fflib_QueryFactory instance, allowing you to use LightweightQueryFields
152- * to build the query. This offers significant performance improvement in query build time
153- * at the expense of FLS enforcement, and up-front field validation.
154- * @param table the SObject to be used in the FROM clause of the resultant query. This sets the value of {@link #table}.
155- * @param lightweight a Boolean that specifies whether the LightweightQueryField is to be used when building the query.
156- **/
157- public fflib_QueryFactory (Schema.SObjectType table , Boolean lightweight ) {
158140 this .table = table ;
159- this .fields = new List <QueryField >();
160- this .order = new List <Ordering >();
161- this .lightweight = lightweight ;
162- this .enforceFLS = false ;
141+ fields = new Set <QueryField >();
142+ order = new List <Ordering >();
143+ enforceFLS = false ;
163144 }
164-
145+
165146 /**
166147 * Construct a new fflib_QueryFactory instance with no options other than the FROM clause and the relationship.
167148 * This should be used when constructing a subquery query for addition to a parent query.
@@ -170,22 +151,10 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
170151 * @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}.
171152 **/
172153 private fflib_QueryFactory (Schema.ChildRelationship relationship ){
173- this (relationship , false );
174- }
175-
176- /**
177- * Construct a new fflib_QueryFactory instance with no options other than the FROM clause and the relationship.
178- * This should be used when constructing a subquery query for addition to a parent query.
179- * Objects created with this constructor cannot be added to another object using the subselectQuery method.
180- * You *must* call selectField(s) before {@link #toSOQL} will return a valid, runnable query.
181- * @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}.
182- * @param lightweight a Boolean that specifies whether the LightweightQueryField is to be used when building the query.
183- **/
184- private fflib_QueryFactory (Schema.ChildRelationship relationship , Boolean lightweight ){
185- this (relationship .getChildSObject (), lightweight );
154+ this (relationship .getChildSObject ());
186155 this .relationship = relationship ;
187156 }
188-
157+
189158 /**
190159 * This method checks to see if the User has Read Access on {@link #table}.
191160 * Asserts true if User has access.
@@ -202,9 +171,6 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
202171 * @param enforce whether to enforce field level security (read)
203172 **/
204173 public fflib_QueryFactory setEnforceFLS (Boolean enforce ){
205- if (this .lightweight && enforce ) {
206- throw new InvalidOperationException (' Calling setEnforceFLS(true) on a "lightweight" QueryFactory instance is not allowed.' );
207- }
208174 this .enforceFLS = enforce ;
209175 return this ;
210176 }
@@ -213,10 +179,10 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
213179 * Sets a flag to indicate that this query should have ordered
214180 * query fields in the select statement (this at a small cost to performance).
215181 * If you are processing large query sets, you should switch this off.
216- * @deprecated Fields are ALWAYS sorted within the generated SOQL, so this method now does nothing.
217182 * @param whether or not select fields should be sorted in the soql statement.
218183 **/
219184 public fflib_QueryFactory setSortSelectFields (Boolean doSort ){
185+ this .sortSelectFields = doSort ;
220186 return this ;
221187 }
222188
@@ -226,7 +192,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
226192 * @param fieldName the API name of the field to add to the query's SELECT clause.
227193 **/
228194 public fflib_QueryFactory selectField (String fieldName ){
229- this . fields .add ( getFieldToken (fieldName ) );
195+ fields .add ( getFieldToken (fieldName ) );
230196 return this ;
231197 }
232198 /**
@@ -240,41 +206,31 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
240206 throw new InvalidFieldException (null ,this .table );
241207 if (enforceFLS )
242208 fflib_SecurityUtils .checkFieldIsReadable (table , field );
243- this . fields .add (getQueryFieldFromToken (field ));
209+ fields .add ( new QueryField (field ) );
244210 return this ;
245211 }
246-
247- /**
248- * Returns the appropriate QueryField implementation, based on the "lightweight" flag
249- * @param field the {@link Schema.SObjectField} for the QueryField
250- * @returns either a QueryField, or LightweightQueryField object for the specified SObjectField
251- **/
252- private QueryField getQueryFieldFromToken (Schema.SObjectField field ) {
253- QueryField qf ;
254- if (this .lightweight )
255- qf = new LightweightQueryField (field );
256- else
257- qf = new QueryField (field );
258- return qf ;
259- }
260-
261212 /**
262213 * Selects multiple fields. This acts the same as calling {@link #selectField(String)} multiple times.
263214 * @param fieldNames the Set of field API names to select.
264215 **/
265216 public fflib_QueryFactory selectFields (Set <String > fieldNames ){
217+ List <String > fieldList = new List <String >();
218+ Set <QueryField > toAdd = new Set <QueryField >();
266219 for (String fieldName : fieldNames ){
267- this .fields .add ( getFieldToken (fieldName ) );
268- }
220+ toAdd .add ( getFieldToken (fieldName ) );
221+ }
222+ fields .addAll (toAdd );
269223 return this ;
270224 }
271225 /**
272226 * Selects multiple fields. This acts the same as calling {@link #selectField(String)} multiple times.
273227 * @param fieldNames the List of field API names to select.
274228 **/
275229 public fflib_QueryFactory selectFields (List <String > fieldNames ){
230+ Set <QueryField > toAdd = new Set <QueryField >();
276231 for (String fieldName : fieldNames )
277- this .fields .add ( getFieldToken (fieldName ) );
232+ toAdd .add ( getFieldToken (fieldName ) );
233+ fields .addAll (toAdd );
278234 return this ;
279235 }
280236 /**
@@ -287,8 +243,8 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
287243 if (token == null )
288244 throw new InvalidFieldException ();
289245 if (enforceFLS )
290- fflib_SecurityUtils .checkFieldIsReadable (table , token );
291- this .fields .add (getQueryFieldFromToken (token ));
246+ fflib_SecurityUtils .checkFieldIsReadable (table , token );
247+ this .fields .add ( new QueryField (token ) );
292248 }
293249 return this ;
294250 }
@@ -302,8 +258,8 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
302258 if (token == null )
303259 throw new InvalidFieldException ();
304260 if (enforceFLS )
305- fflib_SecurityUtils .checkFieldIsReadable (table , token );
306- this .fields .add (getQueryFieldFromToken (token ));
261+ fflib_SecurityUtils .checkFieldIsReadable (table , token );
262+ this .fields .add ( new QueryField (token ) );
307263 }
308264 return this ;
309265 }
@@ -370,13 +326,12 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
370326 }
371327
372328 /**
373- * @deprecated Replaced by {@link #getSelectedFieldsAsList()}
374329 * @returns the selected fields
375330 **/
376331 public Set <QueryField > getSelectedFields () {
377- return new Set < QueryField >( this .fields ) ;
332+ return this .fields ;
378333 }
379-
334+
380335 /**
381336 * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned.
382337 * If not, a new one will be created and returned.
@@ -465,8 +420,11 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
465420 return subselectQueryMap .get (relationship );
466421 }
467422
468- fflib_QueryFactory subselectQuery = new fflib_QueryFactory (relationship , this . lightweight );
423+ fflib_QueryFactory subselectQuery = new fflib_QueryFactory (relationship );
469424
425+ // The child queryFactory should be configured in the same way as the parent by default - can override after if required
426+ subSelectQuery .setSortSelectFields (sortSelectFields );
427+
470428 if (assertIsAccessible ){
471429 subSelectQuery .assertIsAccessible ();
472430 }
@@ -543,7 +501,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
543501 **/
544502 public fflib_QueryFactory addOrdering (SObjectField field , SortOrder direction , Boolean nullsLast ){
545503 order .add (
546- new Ordering (getQueryFieldFromToken (field ), direction , nullsLast )
504+ new Ordering (new QueryField (field ), direction , nullsLast )
547505 );
548506 return this ;
549507 }
@@ -581,7 +539,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
581539 **/
582540 public fflib_QueryFactory addOrdering (SObjectField field , SortOrder direction ){
583541 order .add (
584- new Ordering (getQueryFieldFromToken (field ), direction )
542+ new Ordering (new QueryField (field ), direction )
585543 );
586544 return this ;
587545 }
@@ -596,20 +554,15 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
596554 if (fields .size () == 0 ){
597555 if (enforceFLS ) fflib_SecurityUtils .checkFieldIsReadable (table , ' Id' );
598556 result += ' Id ' ;
599- } else {
600- // This bit of code de-dupes the list of QueryFields. Since we've moved away from using a Set to back this collection
601- // (for performance reasons related to https://github.com/financialforcedev/fflib-apex-common/issues/79), we de-dupe
602- // by first sorting the List of QueryField objects (in order of the String representation of the field path),
603- // then making a pass through the list leaving dupes out of the "fieldsToQuery" collection.
604- fields .sort (); // Sorts based on QueryFields's "comparable" implementation
605- // Now that the QueryField list is sorted, we can de-dupe
606- QueryField previousQf = null ;
607- for (QueryField field : fields ){
608- if (! field .equals (previousQf )) {
609- result += field + ' , ' ;
610- }
611- previousQf = field ;
557+ }else if (sortSelectFields ){
558+ List <QueryField > fieldsToQuery = new List <QueryField >(fields );
559+ fieldsToQuery .sort (); // delegates to QueryFilter's comparable implementation
560+ for (QueryField field : fieldsToQuery ){
561+ result += field + ' , ' ;
612562 }
563+ }else {
564+ for (QueryField field : fields )
565+ result += field + ' , ' ;
613566 }
614567
615568 if (subselectQueryMap != null && ! subselectQueryMap .isEmpty ()){
@@ -639,7 +592,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
639592 **/
640593 public fflib_QueryFactory deepClone (){
641594
642- fflib_QueryFactory clone = new fflib_QueryFactory (this .table , this . lightweight )
595+ fflib_QueryFactory clone = new fflib_QueryFactory (this .table )
643596 .setLimit (this .limitCount )
644597 .setCondition (this .conditionExpression )
645598 .setEnforceFLS (this .enforceFLS );
@@ -711,54 +664,8 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
711664 }
712665 }
713666
714- public class LightweightQueryField extends QueryField implements Comparable {
715- String fieldName ;
716-
717- private LightweightQueryField () {}
718-
719- @TestVisible
720- private LightweightQueryField (String fieldName ) {
721- // Convert strings to lowercase so they sort in a case insensitive manner
722- this .fieldName = fieldName .toLowercase ();
723- }
724-
725- @TestVisible
726- private LightweightQueryField (Schema.SObjectField field ) {
727- // Convert strings to lowercase so they sort in a case insensitive manner
728- this .fieldName = field .getDescribe ().getLocalName ().toLowercase ();
729- }
730-
731- public override String toString () { return this .fieldName ; }
732-
733- public override Integer hashCode () {
734- return (this .fieldName == null ) ? 0 : this .fieldName .hashCode ();
735- }
736-
737- public override Boolean equals (Object obj ) {
738- return ((obj != null )
739- && (obj instanceof LightweightQueryField )
740- && (this .fieldName == ((LightweightQueryField ) obj ).fieldName ));
741- }
742-
743- public override Integer compareTo (Object obj ) {
744- if (obj == null || ! (obj instanceof LightweightQueryField ))
745- return 1 ;
746-
747- if (this .fieldName == null ) {
748- if (((LightweightQueryField ) obj ).fieldName == null )
749- // Both objects are non-null, but their fieldName is null
750- return 0 ;
751- else
752- // Our fieldName is null, but theirs isn't
753- return - 1 ;
754- }
755-
756- // Both objects have non-null fieldNames, so just return the result of String.compareTo
757- return this .fieldName .compareTo (((LightweightQueryField ) obj ).fieldName );
758- }
759- }
760667
761- public virtual class QueryField implements Comparable {
668+ public class QueryField implements Comparable {
762669 List <Schema .SObjectField > fields ;
763670
764671 /**
@@ -774,9 +681,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
774681 public List <SObjectField > getFieldPath (){
775682 return fields .clone ();
776683 }
777-
778- private QueryField () {}
779-
684+
780685 @testVisible
781686 private QueryField (List <Schema .SObjectField > fields ){
782687 if (fields == null || fields .size () == 0 )
@@ -789,7 +694,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
789694 throw new InvalidFieldException (' Invalid field: null' );
790695 fields = new List <Schema .SObjectField >{ field };
791696 }
792- public virtual override String toString (){
697+ public override String toString (){
793698 String result = ' ' ;
794699 Integer size = fields .size ();
795700 for (Integer i = 0 ; i < size ; i ++ )
@@ -807,10 +712,10 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
807712 }
808713 return result ;
809714 }
810- public virtual integer hashCode (){
715+ public integer hashCode (){
811716 return String .valueOf (this .fields ).hashCode ();
812717 }
813- public virtual boolean equals (Object obj ){
718+ public boolean equals (Object obj ){
814719 // Easy checks first
815720 if (obj == null || ! (obj instanceof QueryField ))
816721 return false ;
@@ -839,7 +744,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
839744 * - QueryFields with more joins give +1, while fewer joins give -1
840745 * - For anything else, compare the toStrings of this and the supplied object.
841746 **/
842- public virtual Integer compareTo (Object o ){
747+ public Integer compareTo (Object o ){
843748 if (o == null || ! (o instanceof QueryField ))
844749 return - 2 ; // We can't possibly do a sane comparison against an unknwon type, go athead and let it "win"
845750
@@ -869,6 +774,5 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr
869774 }
870775 public class InvalidFieldSetException extends Exception {}
871776 public class NonReferenceFieldException extends Exception {}
872- public class InvalidSubqueryRelationshipException extends Exception {}
873- public class InvalidOperationException extends Exception {}
874- }
777+ public class InvalidSubqueryRelationshipException extends Exception {}
778+ }
0 commit comments