15
15
16
16
import com .google .common .collect .Lists ;
17
17
import jakarta .persistence .criteria .*;
18
+ import lombok .extern .slf4j .Slf4j ;
18
19
import org .gridsuite .computation .dto .ResourceFilterDTO ;
19
20
import org .jetbrains .annotations .NotNull ;
20
21
import org .springframework .data .jpa .domain .Specification ;
32
33
*
33
34
* @author Kevin Le Saulnier <[email protected] >
34
35
*/
36
+ @ Slf4j
35
37
public final class SpecificationUtils {
36
38
/**
37
39
* Maximum values per IN clause chunk to avoid StackOverflow exceptions.
@@ -41,6 +43,11 @@ public final class SpecificationUtils {
41
43
42
44
public static final String FIELD_SEPARATOR = "." ;
43
45
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 {} : {} - {}" ;
49
+
50
+
44
51
// Utility class, so no constructor
45
52
private SpecificationUtils () { }
46
53
@@ -54,6 +61,11 @@ public static <X> Specification<X> equals(String field, String value) {
54
61
);
55
62
}
56
63
64
+ public static <X > Specification <X > in (String field , List <String > values ) {
65
+ return (root , cq , cb ) ->
66
+ getColumnPath (root , field ).in (values );
67
+ }
68
+
57
69
public static <X > Specification <X > notEqual (String field , String value ) {
58
70
return (root , cq , cb ) -> cb .notEqual (getColumnPath (root , field ), value );
59
71
}
@@ -131,6 +143,75 @@ public static <X> Specification<X> appendFiltersToSpecification(Specification<X>
131
143
return completedSpecification ;
132
144
}
133
145
146
+ /**
147
+ * Better use and abuse from built-in performance of IN clause in Postgresql
148
+ * TODO : suppress when reported in appendFiltersToSpecification - test phase for shortcircuit results
149
+ * @param specification : specification to complement
150
+ * @param resourceFilters : filters to add in IN clause
151
+ * @return : new specification with "AND (field IN (?, ?, ..., ?))"
152
+ * @param <X>
153
+ */
154
+ public static <X > Specification <X > appendInTextClauseToSpecification (Specification <X > specification , List <ResourceFilterDTO > resourceFilters ) {
155
+ Objects .requireNonNull (specification );
156
+ if (resourceFilters != null && !resourceFilters .isEmpty ()) {
157
+ Specification <X > completedSpecification = specification ;
158
+
159
+ for (ResourceFilterDTO resourceFilter : resourceFilters ) {
160
+ if (resourceFilter .dataType () == ResourceFilterDTO .DataType .TEXT ) {
161
+ completedSpecification = appendInTextFilterToSpecification (completedSpecification , resourceFilter );
162
+ } else {
163
+ doLogWarn (resourceFilter );
164
+ }
165
+ }
166
+
167
+ return completedSpecification ;
168
+ } else {
169
+ return specification ;
170
+ }
171
+ }
172
+
173
+ /*
174
+ TODO : suppress when reported in appendTextFilterToSpecification - test phase for shortcircuit results
175
+ */
176
+ private static <X > Specification <X > appendInTextFilterToSpecification (Specification <X > specification , ResourceFilterDTO resourceFilter ) {
177
+ Specification <X > completedSpecification = specification ;
178
+ if (resourceFilter .type () == ResourceFilterDTO .Type .IN ) {
179
+ if (resourceFilter .value () instanceof Collection <?> valueList ) {
180
+ List <String > inValues = valueList .stream ().map (Object ::toString ).toList ();
181
+ completedSpecification = specification .and (generateInClauseSpecification (resourceFilter .column (), inValues ));
182
+ } else {
183
+ doLogWarn (resourceFilter );
184
+ }
185
+ } else {
186
+ doLogWarn (resourceFilter );
187
+ }
188
+
189
+ return completedSpecification ;
190
+ }
191
+
192
+ /*
193
+ TODO : suppress when reported in generateInSpecification - test phase for shortcircuit results
194
+ */
195
+ private static <X > Specification <X > generateInClauseSpecification (String column , List <String > inPossibleValues ) {
196
+ if (inPossibleValues .size () > CHUNK_SIZE ) {
197
+ List <List <String >> chunksOfInValues = Lists .partition (inPossibleValues , CHUNK_SIZE );
198
+ Specification <X > containerSpec = null ;
199
+
200
+ for (List <String > chunk : chunksOfInValues ) {
201
+ Specification <X > multiOrEqualSpec = Specification .anyOf (in (column , chunk ));
202
+ if (containerSpec == null ) {
203
+ containerSpec = multiOrEqualSpec ;
204
+ } else {
205
+ containerSpec = containerSpec .or (multiOrEqualSpec );
206
+ }
207
+ }
208
+
209
+ return containerSpec ;
210
+ } else {
211
+ return Specification .anyOf (in (column , inPossibleValues ));
212
+ }
213
+ }
214
+
134
215
@ NotNull
135
216
private static <X > Specification <X > appendTextFilterToSpecification (Specification <X > specification , ResourceFilterDTO resourceFilter ) {
136
217
Specification <X > completedSpecification = specification ;
@@ -264,4 +345,8 @@ private static <X, Y> Path<Y> getColumnPath(Root<X> root, String dotSeparatedFie
264
345
return root .get (dotSeparatedFields );
265
346
}
266
347
}
348
+
349
+ private static void doLogWarn (ResourceFilterDTO resourceFilter ) {
350
+ log .warn (WARN_UNEXPECTED_TYPE_ENCOUNTERED_FOR , resourceFilter .column (), resourceFilter .type (), resourceFilter .dataType ());
351
+ }
267
352
}
0 commit comments