Skip to content

Commit ed30382

Browse files
authored
Revert "Lightweight Query Factory"
1 parent 4e5f8c2 commit ed30382

File tree

3 files changed

+89
-471
lines changed

3 files changed

+89
-471
lines changed

fflib/src/classes/fflib_QueryFactory.cls

Lines changed: 51 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)