-
Notifications
You must be signed in to change notification settings - Fork 22
/
Query.cls
330 lines (282 loc) · 9.76 KB
/
Query.cls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
public virtual class Query {
private Boolean isSoslEmpty = false;
public enum Operator {
EQUALS,
NOT_EQUALS,
LESS_THAN,
LESS_THAN_OR_EQUAL,
GREATER_THAN,
GREATER_THAN_OR_EQUAL,
ALIKE, // like is a reserved word
NOT_LIKE
}
public final Operator operator;
private final String field;
private final Schema.SObjectField fieldToken;
private final Object predicate;
private final Map<String, Object> bindVars = new Map<String, Object>();
private static final String BIND_VAR_MERGE = 'bindVar{0}';
private static Integer BIND_VAR_NUMBER = 0;
public Boolean isSoslEmpty() {
return this.isSoslEmpty;
}
public Query usingParent(Schema.SObjectField parentField) {
return this.usingParent(new List<Schema.SObjectField>{ parentField });
}
public Query usingParent(List<SObjectField> parentFields) {
parentFields.add(this.fieldToken);
return new ParentQuery(parentFields, this.operator, this.predicate);
}
public static Query subquery(Schema.SObjectField field, Schema.SObjectField innerMatchingField, Query subcondition) {
return subquery(field, innerMatchingField.getDescribe().getSObjectType(), innerMatchingField, subcondition);
}
public static Query subquery(
Schema.SObjectField field,
Schema.SObjectType objectType,
Schema.SObjectField innerMatchingField,
Query subcondition
) {
return new SubQuery(field, objectType, innerMatchingField, subcondition);
}
public static Query equals(SObjectField field, Object predicate) {
return new Query(field, Operator.EQUALS, predicate);
}
public static Query notEquals(SObjectField field, Object predicate) {
return new Query(field, Operator.NOT_EQUALS, predicate);
}
public static Query lessThan(SObjectField field, Object predicate) {
return new Query(field, Operator.LESS_THAN, predicate);
}
public static Query lessThanOrEqual(SObjectField field, Object predicate) {
return new Query(field, Operator.LESS_THAN_OR_EQUAL, predicate);
}
public static Query greaterThan(SObjectField field, Object predicate) {
return new Query(field, Operator.GREATER_THAN, predicate);
}
public static Query greaterThanOrEqual(SObjectField field, Object predicate) {
return new Query(field, Operator.GREATER_THAN_OR_EQUAL, predicate);
}
// like is a reserved keyword
public static Query likeQuery(SObjectField field, Object predicate) {
return new Query(field, Operator.ALIKE, predicate);
}
public static Query notLike(SObjectField field, Object predicate) {
return new Query(field, Operator.NOT_LIKE, predicate);
}
// or is a reserved keyword
public static Query orQuery(Query innerQuery, Query secondInnerQuery) {
return orQuery(new List<Query>{ innerQuery, secondInnerQuery });
}
public static Query orQuery(List<Query> innerQueries) {
return new OrQuery(innerQueries);
}
// and is a reserved keyword
public static Query andQuery(Query innerQuery, Query secondInnerQuery) {
return andQuery(new List<Query>{ innerQuery, secondInnerQuery });
}
public static Query andQuery(List<Query> innerQueries) {
return new AndQuery(innerQueries);
}
public static String getBuiltUpParentFieldName(List<Schema.SObjectField> parentFields) {
String builtUpFieldName = '';
for (Integer index = 0; index < parentFields.size(); index++) {
Schema.DescribeFieldResult parentFieldDescribe = parentFields[index].getDescribe();
builtUpFieldName +=
index == parentFields.size() - 1
? parentFieldDescribe.getName()
: (parentFieldDescribe.getRelationshipName() ?? parentFieldDescribe.getName().replace('__c', '__r')) + '.';
}
return builtUpFieldName;
}
private class SubQuery extends Query {
private final Schema.SObjectField field;
private final Schema.SObjectType objectType;
private final Schema.SObjectField innerMatchingField;
private final Query subcondition;
public SubQuery(
Schema.SObjectField field,
Schema.SObjectType objectType,
Schema.SObjectField innerMatchingField,
Query subcondition
) {
this.field = field;
this.objectType = objectType;
this.innerMatchingField = innerMatchingField;
this.subcondition = subcondition;
}
public override String toString() {
String whereClause = ' WHERE ' + this.subcondition.toString();
this.bindVars.putAll(this.subcondition.getBindVars());
return this.field.getDescribe().getName() +
' IN (SELECT ' +
this.innerMatchingField +
' FROM ' +
this.objectType +
whereClause +
')';
}
}
private abstract class DelimitedQuery extends Query {
private final List<Query> queries;
public DelimitedQuery(List<Query> queries) {
super();
this.queries = queries;
}
public abstract String getDelimiter();
public override String toString() {
String baseString = '(';
for (Query innerQuery : this.queries) {
baseString += innerQuery.toString() + this.getDelimiter();
this.bindVars.putAll(innerQuery.getBindVars());
}
return baseString.removeEnd(this.getDelimiter()) + ')';
}
}
private class AndQuery extends DelimitedQuery {
private final String delimiter = ' AND ';
public AndQuery(List<Query> queries) {
super(queries);
}
public override String getDelimiter() {
return this.delimiter;
}
}
private class OrQuery extends DelimitedQuery {
private final String delimiter = ' OR ';
public OrQuery(List<Query> queries) {
super(queries);
}
public override String getDelimiter() {
return this.delimiter;
}
}
private class ParentQuery extends Query {
private ParentQuery(List<SObjectField> parentFields, Operator operator, Object predicate) {
super(getBuiltUpParentFieldName(parentFields), operator, predicate);
}
}
protected Query() {
}
protected Query(String fieldName, Operator operator, Object predicate) {
this.field = fieldName;
this.operator = operator;
this.predicate = predicate;
}
private Query(SObjectField fieldToken, Operator operator, Object predicate) {
this(fieldToken.getDescribe().getName(), operator, predicate);
this.fieldToken = fieldToken;
}
public Map<String, Object> getBindVars() {
return this.bindVars;
}
public virtual override String toString() {
String predicateValue = this.getPredicate(this.predicate);
if (this.operator == Query.Operator.NOT_LIKE) {
// who knows why this is the format they wanted
return String.format(this.getOperator(), new List<String>{ this.field }) + ' ' + predicateValue;
}
return this.field + ' ' + this.getOperator() + ' ' + predicateValue;
}
public String toSoslString() {
String startingString = this.toString();
for (String key : this.bindVars.keySet()) {
startingString = startingString.replace(':' + key, this.getSoslPredicate(this.bindVars.get(key)));
}
if (this.predicate instanceof Iterable<Object>) {
String operatorToReplace;
String newOperator;
switch on this.operator {
when EQUALS {
operatorToReplace = '=';
newOperator = 'IN';
}
when NOT_EQUALS {
operatorToReplace = '!=';
newOperator = 'NOT IN';
}
}
if (operatorToReplace != null) {
startingString = startingString.replace(operatorToReplace, newOperator);
}
}
if (startingString.endsWith('()')) {
this.isSoslEmpty = true;
}
return startingString;
}
public Boolean equals(Object thatObject) {
if (thatObject instanceof Query) {
Query that = (Query) thatObject;
return this.field == that.field &&
this.operator == that.operator &&
this.bindVars.values() == that.bindVars.values();
}
return false;
}
private String getOperator() {
String returnVal = '';
switch on this.operator {
when EQUALS {
returnVal = '=';
}
when NOT_EQUALS {
returnVal = '!=';
}
when LESS_THAN {
returnVal = '<';
}
when LESS_THAN_OR_EQUAL {
returnVal = '<=';
}
when GREATER_THAN {
returnVal = '>';
}
when GREATER_THAN_OR_EQUAL {
returnVal = '>=';
}
when ALIKE {
returnVal = 'LIKE';
}
when NOT_LIKE {
returnVal = 'NOT {0} LIKE';
}
}
return returnVal;
}
private String getPredicate(Object predicate) {
if (predicate == null || predicate instanceof Boolean) {
return '' + predicate;
}
String predicateKey = String.format(BIND_VAR_MERGE, new List<String>{ BIND_VAR_NUMBER.format() });
BIND_VAR_NUMBER++;
this.bindVars.put(predicateKey, predicate);
return ':' + predicateKey;
}
private String getSoslPredicate(Object predicate) {
if (predicate == null) {
return 'null';
} else if (predicate instanceof Datetime) {
// the most annoying one
Datetime dt = (Datetime) predicate;
return dt.format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'', 'Greenwich Mean Time');
} else if (predicate instanceof Iterable<Object>) {
Iterable<Object> localPredicates = (Iterable<Object>) predicate;
if (localPredicates.iterator().hasNext() == false) {
return '()';
}
List<String> innerStrings = new List<String>();
for (Object innerPred : localPredicates) {
// recurse for string value
String innerString = this.getSoslPredicate(innerPred);
innerStrings.add(innerString);
}
String start = '(';
String ending = ')';
return start + String.join(innerStrings, ',') + ending;
} else if (predicate instanceof String) {
String input = (String) predicate;
return '\'' + String.escapeSingleQuotes(input) + '\'';
}
return String.valueOf(predicate);
}
}