Skip to content

Commit c3ed37f

Browse files
committed
feat: Add custom operator to Criteria.
1 parent e4ba477 commit c3ed37f

File tree

4 files changed

+119
-44
lines changed

4 files changed

+119
-44
lines changed

spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/query/CriteriaUnitTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import static org.springframework.data.relational.core.query.Criteria.*;
2020

2121
import java.util.Arrays;
22+
import java.util.List;
23+
import java.util.stream.Collectors;
2224

2325
import org.assertj.core.api.SoftAssertions;
2426
import org.junit.jupiter.api.Test;
@@ -290,4 +292,17 @@ void shouldBuildIsFalseCriteria() {
290292
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
291293
assertThat(criteria.getComparator()).isEqualTo(Comparator.IS_FALSE);
292294
}
295+
296+
@Test
297+
void shouldBuildCustomCriteria() {
298+
List<String> values = List.of("bar", "baz", "qux");
299+
Criteria criteria = where("foo").custom("CUSTOM",
300+
value -> "ARRAY[" + values.stream().map(s -> "'" + s + "'").collect(Collectors.joining(",")) + "]",
301+
List.of("bar", "baz", "qux"));
302+
303+
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
304+
assertThat(criteria.getComparator()).isEqualTo(Comparator.CUSTOM);
305+
assertThat(criteria.toString()).isEqualTo("foo CUSTOM ARRAY['bar','baz','qux']");
306+
}
307+
293308
}

spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Criteria.java

Lines changed: 11 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
import java.util.HashMap;
2222
import java.util.List;
2323
import java.util.Map;
24-
import java.util.StringJoiner;
24+
import java.util.Objects;
25+
import java.util.function.Function;
2526

2627
import org.springframework.dao.InvalidDataAccessApiUsageException;
2728
import org.springframework.data.relational.core.sql.IdentifierProcessing;
@@ -456,47 +457,8 @@ private void render(CriteriaDefinition criteria, StringBuilder stringBuilder) {
456457
stringBuilder.append(criteria.getColumn().toSql(IdentifierProcessing.NONE)).append(' ')
457458
.append(criteria.getComparator().getComparator());
458459

459-
switch (criteria.getComparator()) {
460-
case BETWEEN:
461-
case NOT_BETWEEN:
462-
Pair<Object, Object> pair = (Pair<Object, Object>) criteria.getValue();
463-
stringBuilder.append(' ').append(pair.getFirst()).append(" AND ").append(pair.getSecond());
464-
break;
465-
466-
case IS_NULL:
467-
case IS_NOT_NULL:
468-
case IS_TRUE:
469-
case IS_FALSE:
470-
break;
471-
472-
case IN:
473-
case NOT_IN:
474-
stringBuilder.append(" (").append(renderValue(criteria.getValue())).append(')');
475-
break;
476-
477-
default:
478-
stringBuilder.append(' ').append(renderValue(criteria.getValue()));
479-
}
480-
}
481-
482-
private static String renderValue(@Nullable Object value) {
483-
484-
if (value instanceof Number) {
485-
return value.toString();
486-
}
487-
488-
if (value instanceof Collection) {
489-
490-
StringJoiner joiner = new StringJoiner(", ");
491-
((Collection<?>) value).forEach(o -> joiner.add(renderValue(o)));
492-
return joiner.toString();
493-
}
494-
495-
if (value != null) {
496-
return String.format("'%s'", value);
497-
}
498-
499-
return "null";
460+
String renderValue = criteria.getComparator().render(Objects.requireNonNull(criteria.getValue()));
461+
stringBuilder.append(renderValue);
500462
}
501463

502464
/**
@@ -630,6 +592,8 @@ public interface CriteriaStep {
630592
* @return a new {@link Criteria} object
631593
*/
632594
Criteria isFalse();
595+
596+
Criteria custom(String comparator, Function<Object, String> renderFunc, Object value);
633597
}
634598

635599
/**
@@ -789,6 +753,11 @@ public Criteria isFalse() {
789753
return createCriteria(Comparator.IS_FALSE, false);
790754
}
791755

756+
@Override
757+
public Criteria custom(String comparator, Function<Object, String> renderFunc, Object value) {
758+
return createCriteria(Comparator.CUSTOM.setCustomComparator(comparator, renderFunc), value);
759+
}
760+
792761
protected Criteria createCriteria(Comparator comparator, @Nullable Object value) {
793762
return new Criteria(this.property, comparator, value);
794763
}

spring-data-relational/src/main/java/org/springframework/data/relational/core/query/CriteriaDefinition.java

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@
1616
package org.springframework.data.relational.core.query;
1717

1818
import java.util.Arrays;
19+
import java.util.Collection;
1920
import java.util.List;
21+
import java.util.StringJoiner;
22+
import java.util.function.Function;
2023

2124
import org.springframework.data.relational.core.sql.SqlIdentifier;
25+
import org.springframework.data.util.Pair;
2226
import org.springframework.lang.Nullable;
2327
import org.springframework.util.Assert;
2428

@@ -139,17 +143,89 @@ enum Combinator {
139143

140144
enum Comparator {
141145
INITIAL(""), EQ("="), NEQ("!="), BETWEEN("BETWEEN"), NOT_BETWEEN("NOT BETWEEN"), LT("<"), LTE("<="), GT(">"), GTE(
142-
">="), IS_NULL("IS NULL"), IS_NOT_NULL("IS NOT NULL"), LIKE(
143-
"LIKE"), NOT_LIKE("NOT LIKE"), NOT_IN("NOT IN"), IN("IN"), IS_TRUE("IS TRUE"), IS_FALSE("IS FALSE");
146+
">="), IS_NULL("IS NULL"), IS_NOT_NULL("IS NOT NULL"), LIKE("LIKE"), NOT_LIKE(
147+
"NOT LIKE"), NOT_IN("NOT IN"), IN("IN"), IS_TRUE("IS TRUE"), IS_FALSE("IS FALSE"), CUSTOM("CUSTOM");
144148

145149
private final String comparator;
150+
private Function<Object, String> customRenderValueFunc = Object::toString;
151+
private String customComparator = "";
146152

147153
Comparator(String comparator) {
148154
this.comparator = comparator;
149155
}
150156

157+
Comparator setCustomComparator(String customComparator, Function<Object, String> customRenderValueFunc) {
158+
if (this == CUSTOM) {
159+
this.customComparator = customComparator;
160+
this.customRenderValueFunc = customRenderValueFunc;
161+
} else {
162+
throw new UnsupportedOperationException("Only CUSTOM comparator can be customized.");
163+
}
164+
return this;
165+
}
166+
151167
public String getComparator() {
168+
if (this == CUSTOM) {
169+
if (customComparator.isEmpty()) {
170+
throw new UnsupportedOperationException("CUSTOM comparator must be customized.");
171+
}
172+
return customComparator;
173+
}
152174
return comparator;
153175
}
176+
177+
private static String renderValue(@Nullable Object value) {
178+
179+
if (value instanceof Number) {
180+
return value.toString();
181+
}
182+
183+
if (value instanceof Collection) {
184+
185+
StringJoiner joiner = new StringJoiner(", ");
186+
((Collection<?>) value).forEach(o -> joiner.add(renderValue(o)));
187+
return joiner.toString();
188+
}
189+
190+
if (value != null) {
191+
return String.format("'%s'", value);
192+
}
193+
194+
return "null";
195+
}
196+
197+
public String render(Object value) {
198+
StringBuilder stringBuilder = new StringBuilder();
199+
switch (this) {
200+
case BETWEEN:
201+
case NOT_BETWEEN:
202+
if (value instanceof Pair<?, ?> pair) {
203+
stringBuilder.append(pair.getFirst()).append(" AND ").append(pair.getSecond());
204+
} else {
205+
throw new IllegalArgumentException("Value must be a Pair");
206+
}
207+
break;
208+
209+
case IS_NULL:
210+
case IS_NOT_NULL:
211+
case IS_TRUE:
212+
case IS_FALSE:
213+
break;
214+
215+
case IN:
216+
case NOT_IN:
217+
stringBuilder.append(" (").append(renderValue(value)).append(')');
218+
break;
219+
case CUSTOM:
220+
stringBuilder.append(' ').append(customRenderValueFunc.apply(value));
221+
break;
222+
223+
default:
224+
stringBuilder.append(' ').append(renderValue(value));
225+
}
226+
return stringBuilder.toString();
227+
}
228+
154229
}
230+
155231
}

spring-data-relational/src/test/java/org/springframework/data/relational/core/query/CriteriaUnitTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import static org.springframework.data.relational.core.query.Criteria.*;
2121

2222
import java.util.Arrays;
23+
import java.util.List;
24+
import java.util.stream.Collectors;
2325

2426
import org.junit.jupiter.api.Test;
2527

@@ -297,4 +299,17 @@ void shouldBuildIsFalseCriteria() {
297299
assertThat(criteria.getComparator()).isEqualTo(CriteriaDefinition.Comparator.IS_FALSE);
298300
assertThat(criteria.getValue()).isEqualTo(false);
299301
}
302+
303+
@Test
304+
void shouldBuildCustomCriteria() {
305+
List<String> values = List.of("bar", "baz", "qux");
306+
Criteria criteria = where("foo").custom("CUSTOM",
307+
value -> "ARRAY[" + values.stream().map(s -> "'" + s + "'").collect(Collectors.joining(",")) + "]",
308+
List.of("bar", "baz", "qux"));
309+
310+
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
311+
assertThat(criteria.getComparator()).isEqualTo(Comparator.CUSTOM);
312+
assertThat(criteria.toString()).isEqualTo("foo CUSTOM ARRAY['bar','baz','qux']");
313+
}
314+
300315
}

0 commit comments

Comments
 (0)