forked from jamessimone/apex-dml-mocking
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathQuery.cls
272 lines (228 loc) · 7.82 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
public virtual class Query {
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 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);
}
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);
}
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, Query innerQuery) {
super(innerQuery);
this.field = getBuiltUpParentFieldName(parentFields);
}
}
protected Query() {
}
protected Query(Query innerQuery) {
this.operator = innerQuery.operator;
this.predicate = innerQuery.predicate;
this.bindVars.putAll(innerQuery.getBindVars());
}
protected Query(String fieldName, Operator operator, Object predicate) {
this.field = fieldName;
this.operator = operator;
this.predicate = this.getPredicate(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() {
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, this.predicate.toString() });
}
return this.field + ' ' + this.getOperator() + ' ' + this.predicate;
}
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 {1})';
}
}
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 static String getBuiltUpParentFieldName(List<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() + '.';
}
return builtUpFieldName;
}
}