Skip to content

Commit c1365d0

Browse files
committed
Generalize use of IN clause
Signed-off-by: Igor PIROG <[email protected]>
1 parent 260b6b8 commit c1365d0

File tree

1 file changed

+25
-111
lines changed

1 file changed

+25
-111
lines changed

src/main/java/org/gridsuite/computation/utils/SpecificationUtils.java

Lines changed: 25 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,13 @@
3535
*/
3636
@Slf4j
3737
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;
4338

4439
public static final String FIELD_SEPARATOR = ".";
4540

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;
4945

5046
// Utility class, so no constructor
5147
private SpecificationUtils() { }
@@ -62,7 +58,7 @@ public static <X> Specification<X> equals(String field, String value) {
6258

6359
public static <X> Specification<X> in(String field, List<String> values) {
6460
return (root, cq, cb) ->
65-
getColumnPath(root, field).in(values);
61+
cb.upper(getColumnPath(root, field).as(String.class)).in(values);
6662
}
6763

6864
public static <X> Specification<X> notEqual(String field, String value) {
@@ -142,95 +138,32 @@ public static <X> Specification<X> appendFiltersToSpecification(Specification<X>
142138
return completedSpecification;
143139
}
144140

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-
214141
@NotNull
215142
private static <X> Specification<X> appendTextFilterToSpecification(Specification<X> specification, ResourceFilterDTO resourceFilter) {
216143
Specification<X> completedSpecification = specification;
217144

218145
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 -> {
220155
// this type can manage one value or a list of values (with OR)
221156
if (resourceFilter.value() instanceof Collection<?> valueList) {
222157
// implicitly an IN resourceFilter type because only IN may have value lists as filter value
223158
List<String> inValues = valueList.stream()
224159
.map(Object::toString)
160+
.map(String::toUpperCase)
225161
.toList();
226-
completedSpecification = completedSpecification.and(
227-
generateInSpecification(resourceFilter.column(), inValues)
162+
completedSpecification = completedSpecification.and(generateInSpecification(resourceFilter.column(), inValues)
228163
);
229164
} else if (resourceFilter.value() == null) {
230165
// if the value is null, we build an impossible specification (trick to remove later on ?)
231166
completedSpecification = completedSpecification.and(not(completedSpecification));
232-
} else {
233-
completedSpecification = completedSpecification.and(equals(resourceFilter.column(), resourceFilter.value().toString()));
234167
}
235168
}
236169
case CONTAINS -> {
@@ -263,32 +196,17 @@ private static <X> Specification<X> appendTextFilterToSpecification(Specificatio
263196
* @return a specification for the IN clause
264197
*/
265198
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);
284207
}
285-
return containerSpec;
286208
}
287-
return anyOf(inPossibleValues
288-
.stream()
289-
.map(value -> SpecificationUtils.<X>equals(column, value))
290-
.toList()
291-
);
209+
return containerSpec;
292210
}
293211

294212
@NotNull
@@ -344,8 +262,4 @@ private static <X, Y> Path<Y> getColumnPath(Root<X> root, String dotSeparatedFie
344262
return root.get(dotSeparatedFields);
345263
}
346264
}
347-
348-
private static void doLogWarn(ResourceFilterDTO resourceFilter) {
349-
log.warn(WARN_UNEXPECTED_TYPE_ENCOUNTERED_FOR, resourceFilter.column(), resourceFilter.type(), resourceFilter.dataType());
350-
}
351265
}

0 commit comments

Comments
 (0)