Skip to content

Commit a1e83f1

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

File tree

4 files changed

+123
-44
lines changed

4 files changed

+123
-44
lines changed

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

+16
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;
@@ -31,6 +33,7 @@
3133
*
3234
* @author Mark Paluch
3335
* @author Mingyuan Wu
36+
* @author Zhengyu Wu
3437
*/
3538
class CriteriaUnitTests {
3639

@@ -290,4 +293,17 @@ void shouldBuildIsFalseCriteria() {
290293
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
291294
assertThat(criteria.getComparator()).isEqualTo(Comparator.IS_FALSE);
292295
}
296+
297+
@Test
298+
void shouldBuildCustomCriteria() {
299+
List<String> values = List.of("bar", "baz", "qux");
300+
Criteria criteria = where("foo").custom("@>",
301+
value -> "ARRAY[" + values.stream().map(s -> "'" + s + "'").collect(Collectors.joining(",")) + "]",
302+
List.of("bar", "baz", "qux"));
303+
304+
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
305+
assertThat(criteria.getComparator()).isEqualTo(Comparator.CUSTOM);
306+
assertThat(criteria.toString()).isEqualTo("foo @> ARRAY['bar','baz','qux']");
307+
}
308+
293309
}

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

+12-42
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;
@@ -52,6 +53,7 @@
5253
* @author Oliver Drotbohm
5354
* @author Roman Chigvintsev
5455
* @author Jens Schauder
56+
* @author Zhengyu Wu
5557
* @since 2.0
5658
*/
5759
public class Criteria implements CriteriaDefinition {
@@ -456,47 +458,8 @@ private void render(CriteriaDefinition criteria, StringBuilder stringBuilder) {
456458
stringBuilder.append(criteria.getColumn().toSql(IdentifierProcessing.NONE)).append(' ')
457459
.append(criteria.getComparator().getComparator());
458460

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";
461+
String renderValue = criteria.getComparator().render(criteria.getValue());
462+
stringBuilder.append(renderValue);
500463
}
501464

502465
/**
@@ -630,6 +593,8 @@ public interface CriteriaStep {
630593
* @return a new {@link Criteria} object
631594
*/
632595
Criteria isFalse();
596+
597+
Criteria custom(String comparator, Function<Object, String> renderFunc, Object value);
633598
}
634599

635600
/**
@@ -789,6 +754,11 @@ public Criteria isFalse() {
789754
return createCriteria(Comparator.IS_FALSE, false);
790755
}
791756

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

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

+79-2
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

@@ -28,6 +32,7 @@
2832
*
2933
* @author Mark Paluch
3034
* @author Jens Schauder
35+
* @author Zhengyu Wu
3136
* @since 2.0
3237
*/
3338
public interface CriteriaDefinition {
@@ -139,17 +144,89 @@ enum Combinator {
139144

140145
enum Comparator {
141146
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");
147+
">="), IS_NULL("IS NULL"), IS_NOT_NULL("IS NOT NULL"), LIKE("LIKE"), NOT_LIKE(
148+
"NOT LIKE"), NOT_IN("NOT IN"), IN("IN"), IS_TRUE("IS TRUE"), IS_FALSE("IS FALSE"), CUSTOM("CUSTOM");
144149

145150
private final String comparator;
151+
private Function<Object, String> customRenderValueFunc = Object::toString;
152+
private String customComparator = "";
146153

147154
Comparator(String comparator) {
148155
this.comparator = comparator;
149156
}
150157

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

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

+16
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

@@ -31,6 +33,7 @@
3133
* @author Mark Paluch
3234
* @author Jens Schauder
3335
* @author Roman Chigvintsev
36+
* @author Zhengyu Wu
3437
*/
3538
class CriteriaUnitTests {
3639

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

0 commit comments

Comments
 (0)