35
35
*/
36
36
@ Slf4j
37
37
public final class SpecificationUtils {
38
- /**
39
- * Maximum values per IN clause chunk to avoid StackOverflow exceptions.
40
- * Current value (500) is a safe default but can be changed
41
- */
42
- public static final int MAX_IN_CLAUSE_SIZE = 500 ;
43
38
44
39
public static final String FIELD_SEPARATOR = "." ;
45
40
46
- public static final int CHUNK_SIZE = 10000 ; // prefer less than 1000 for Oracle DB / good compromise for Postgres
47
-
48
- private static final String WARN_UNEXPECTED_TYPE_ENCOUNTERED_FOR = "Unexpected type encountered for {} : {} - {}" ;
41
+ /**
42
+ Prefer less than 1000 for Oracle DB / good compromise for Postgres
43
+ */
44
+ public static final int MAX_IN_CLAUSE_SIZE = 10000 ;
49
45
50
46
// Utility class, so no constructor
51
47
private SpecificationUtils () { }
@@ -62,7 +58,7 @@ public static <X> Specification<X> equals(String field, String value) {
62
58
63
59
public static <X > Specification <X > in (String field , List <String > values ) {
64
60
return (root , cq , cb ) ->
65
- getColumnPath (root , field ).in (values );
61
+ cb . upper ( getColumnPath (root , field ). as ( String . class ) ).in (values );
66
62
}
67
63
68
64
public static <X > Specification <X > notEqual (String field , String value ) {
@@ -142,95 +138,32 @@ public static <X> Specification<X> appendFiltersToSpecification(Specification<X>
142
138
return completedSpecification ;
143
139
}
144
140
145
- /**
146
- * Better use and abuse from built-in performance of IN clause in Postgresql
147
- * TODO : suppress when reported in appendFiltersToSpecification - test phase for shortcircuit results
148
- * @param specification : specification to complement
149
- * @param resourceFilters : filters to add in IN clause
150
- * @return : new specification with "AND (field IN (?, ?, ..., ?))"
151
- * @param <X>
152
- */
153
- public static <X > Specification <X > appendInTextClauseToSpecification (Specification <X > specification , List <ResourceFilterDTO > resourceFilters ) {
154
- Objects .requireNonNull (specification );
155
- if (resourceFilters != null && !resourceFilters .isEmpty ()) {
156
- Specification <X > completedSpecification = specification ;
157
-
158
- for (ResourceFilterDTO resourceFilter : resourceFilters ) {
159
- if (resourceFilter .dataType () == ResourceFilterDTO .DataType .TEXT ) {
160
- completedSpecification = appendInTextFilterToSpecification (completedSpecification , resourceFilter );
161
- } else {
162
- doLogWarn (resourceFilter );
163
- }
164
- }
165
-
166
- return completedSpecification ;
167
- } else {
168
- return specification ;
169
- }
170
- }
171
-
172
- /*
173
- TODO : suppress when reported in appendTextFilterToSpecification - test phase for shortcircuit results
174
- */
175
- private static <X > Specification <X > appendInTextFilterToSpecification (Specification <X > specification , ResourceFilterDTO resourceFilter ) {
176
- Specification <X > completedSpecification = specification ;
177
- if (resourceFilter .type () == ResourceFilterDTO .Type .IN ) {
178
- if (resourceFilter .value () instanceof Collection <?> valueList ) {
179
- List <String > inValues = valueList .stream ().map (Object ::toString ).toList ();
180
- completedSpecification = specification .and (generateInClauseSpecification (resourceFilter .column (), inValues ));
181
- } else {
182
- doLogWarn (resourceFilter );
183
- }
184
- } else {
185
- doLogWarn (resourceFilter );
186
- }
187
-
188
- return completedSpecification ;
189
- }
190
-
191
- /*
192
- TODO : suppress when reported in generateInSpecification - test phase for shortcircuit results
193
- */
194
- private static <X > Specification <X > generateInClauseSpecification (String column , List <String > inPossibleValues ) {
195
- if (inPossibleValues .size () > CHUNK_SIZE ) {
196
- List <List <String >> chunksOfInValues = Lists .partition (inPossibleValues , CHUNK_SIZE );
197
- Specification <X > containerSpec = null ;
198
-
199
- for (List <String > chunk : chunksOfInValues ) {
200
- Specification <X > multiOrEqualSpec = Specification .anyOf (in (column , chunk ));
201
- if (containerSpec == null ) {
202
- containerSpec = multiOrEqualSpec ;
203
- } else {
204
- containerSpec = containerSpec .or (multiOrEqualSpec );
205
- }
206
- }
207
-
208
- return containerSpec ;
209
- } else {
210
- return Specification .anyOf (in (column , inPossibleValues ));
211
- }
212
- }
213
-
214
141
@ NotNull
215
142
private static <X > Specification <X > appendTextFilterToSpecification (Specification <X > specification , ResourceFilterDTO resourceFilter ) {
216
143
Specification <X > completedSpecification = specification ;
217
144
218
145
switch (resourceFilter .type ()) {
219
- case NOT_EQUAL , EQUALS , IN -> {
146
+ case NOT_EQUAL , EQUALS -> {
147
+ if (resourceFilter .value () == null ) {
148
+ // if the value is null, we build an impossible specification (trick to remove later on ?)
149
+ completedSpecification = completedSpecification .and (not (completedSpecification ));
150
+ } else {
151
+ completedSpecification = completedSpecification .and (equals (resourceFilter .column (), resourceFilter .value ().toString ()));
152
+ }
153
+ }
154
+ case IN -> {
220
155
// this type can manage one value or a list of values (with OR)
221
156
if (resourceFilter .value () instanceof Collection <?> valueList ) {
222
157
// implicitly an IN resourceFilter type because only IN may have value lists as filter value
223
158
List <String > inValues = valueList .stream ()
224
159
.map (Object ::toString )
160
+ .map (String ::toUpperCase )
225
161
.toList ();
226
- completedSpecification = completedSpecification .and (
227
- generateInSpecification (resourceFilter .column (), inValues )
162
+ completedSpecification = completedSpecification .and (generateInSpecification (resourceFilter .column (), inValues )
228
163
);
229
164
} else if (resourceFilter .value () == null ) {
230
165
// if the value is null, we build an impossible specification (trick to remove later on ?)
231
166
completedSpecification = completedSpecification .and (not (completedSpecification ));
232
- } else {
233
- completedSpecification = completedSpecification .and (equals (resourceFilter .column (), resourceFilter .value ().toString ()));
234
167
}
235
168
}
236
169
case CONTAINS -> {
@@ -263,32 +196,17 @@ private static <X> Specification<X> appendTextFilterToSpecification(Specificatio
263
196
* @return a specification for the IN clause
264
197
*/
265
198
private static <X > Specification <X > generateInSpecification (String column , List <String > inPossibleValues ) {
266
-
267
- if (inPossibleValues .size () > MAX_IN_CLAUSE_SIZE ) {
268
- // there are too many values for only one call to anyOf() : it might cause a StackOverflow
269
- // => the specification is divided into several specifications which have an OR between them :
270
- List <List <String >> chunksOfInValues = Lists .partition (inPossibleValues , MAX_IN_CLAUSE_SIZE );
271
- Specification <X > containerSpec = null ;
272
- for (List <String > chunk : chunksOfInValues ) {
273
- Specification <X > multiOrEqualSpec = anyOf (
274
- chunk
275
- .stream ()
276
- .map (value -> SpecificationUtils .<X >equals (column , value ))
277
- .toList ()
278
- );
279
- if (containerSpec == null ) {
280
- containerSpec = multiOrEqualSpec ;
281
- } else {
282
- containerSpec = containerSpec .or (multiOrEqualSpec );
283
- }
199
+ List <List <String >> chunksOfInValues = Lists .partition (inPossibleValues , MAX_IN_CLAUSE_SIZE );
200
+ Specification <X > containerSpec = null ;
201
+ for (List <String > chunk : chunksOfInValues ) {
202
+ Specification <X > multiOrEqualSpec = Specification .anyOf (in (column , chunk ));
203
+ if (containerSpec == null ) {
204
+ containerSpec = multiOrEqualSpec ;
205
+ } else {
206
+ containerSpec = containerSpec .or (multiOrEqualSpec );
284
207
}
285
- return containerSpec ;
286
208
}
287
- return anyOf (inPossibleValues
288
- .stream ()
289
- .map (value -> SpecificationUtils .<X >equals (column , value ))
290
- .toList ()
291
- );
209
+ return containerSpec ;
292
210
}
293
211
294
212
@ NotNull
@@ -344,8 +262,4 @@ private static <X, Y> Path<Y> getColumnPath(Root<X> root, String dotSeparatedFie
344
262
return root .get (dotSeparatedFields );
345
263
}
346
264
}
347
-
348
- private static void doLogWarn (ResourceFilterDTO resourceFilter ) {
349
- log .warn (WARN_UNEXPECTED_TYPE_ENCOUNTERED_FOR , resourceFilter .column (), resourceFilter .type (), resourceFilter .dataType ());
350
- }
351
265
}
0 commit comments