Skip to content

Commit e5abd58

Browse files
committed
spring-projects#282 - Use 'StatementMapper' to create 'BindableQuery' instead of 'StatementBuilder'
1 parent ab7a3ac commit e5abd58

14 files changed

+722
-767
lines changed

Diff for: src/main/java/org/springframework/data/r2dbc/query/Criteria.java

+83-2
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,28 @@ public class Criteria {
5757
private final @Nullable SqlIdentifier column;
5858
private final @Nullable Comparator comparator;
5959
private final @Nullable Object value;
60+
private final boolean ignoreCase;
6061

6162
private Criteria(SqlIdentifier column, Comparator comparator, @Nullable Object value) {
62-
this(null, Combinator.INITIAL, Collections.emptyList(), column, comparator, value);
63+
this(null, Combinator.INITIAL, Collections.emptyList(), column, comparator, value, false);
6364
}
6465

6566
private Criteria(@Nullable Criteria previous, Combinator combinator, List<Criteria> group,
6667
@Nullable SqlIdentifier column, @Nullable Comparator comparator, @Nullable Object value) {
68+
this(previous, combinator, group, column, comparator, value, false);
69+
}
70+
71+
private Criteria(@Nullable Criteria previous, Combinator combinator, List<Criteria> group,
72+
@Nullable SqlIdentifier column, @Nullable Comparator comparator, @Nullable Object value,
73+
boolean ignoreCase) {
6774

6875
this.previous = previous;
6976
this.combinator = previous != null && previous.isEmpty() ? Combinator.INITIAL : combinator;
7077
this.group = group;
7178
this.column = column;
7279
this.comparator = comparator;
7380
this.value = value;
81+
this.ignoreCase = ignoreCase;
7482
}
7583

7684
private Criteria(@Nullable Criteria previous, Combinator combinator, List<Criteria> group) {
@@ -81,6 +89,7 @@ private Criteria(@Nullable Criteria previous, Combinator combinator, List<Criter
8189
this.column = null;
8290
this.comparator = null;
8391
this.value = null;
92+
this.ignoreCase = false;
8493
}
8594

8695
/**
@@ -236,6 +245,19 @@ public Criteria or(List<Criteria> criteria) {
236245
return new Criteria(Criteria.this, Combinator.OR, criteria);
237246
}
238247

248+
/**
249+
* Creates a new {@link Criteria} with the given "ignore case" flag.
250+
*
251+
* @param ignoreCase {@literal true} if comparison should be done in case-insensitive way
252+
* @return a new {@link Criteria} object
253+
*/
254+
public Criteria ignoreCase(boolean ignoreCase) {
255+
if (this.ignoreCase != ignoreCase) {
256+
return new Criteria(previous, combinator, group, column, comparator, value, ignoreCase);
257+
}
258+
return this;
259+
}
260+
239261
/**
240262
* @return the previous {@link Criteria} object. Can be {@literal null} if there is no previous {@link Criteria}.
241263
* @see #hasPrevious()
@@ -338,8 +360,17 @@ Object getValue() {
338360
return value;
339361
}
340362

363+
/**
364+
* Checks whether comparison should be done in case-insensitive way.
365+
*
366+
* @return {@literal true} if comparison should be done in case-insensitive way
367+
*/
368+
boolean isIgnoreCase() {
369+
return ignoreCase;
370+
}
371+
341372
enum Comparator {
342-
INITIAL, EQ, NEQ, LT, LTE, GT, GTE, IS_NULL, IS_NOT_NULL, LIKE, NOT_IN, IN,
373+
INITIAL, EQ, NEQ, LT, LTE, GT, GTE, IS_NULL, IS_NOT_NULL, LIKE, NOT_LIKE, NOT_IN, IN, IS_TRUE, IS_FALSE
343374
}
344375

345376
enum Combinator {
@@ -428,6 +459,14 @@ public interface CriteriaStep {
428459
*/
429460
Criteria like(Object value);
430461

462+
/**
463+
* Creates a {@link Criteria} using {@code NOT LIKE}.
464+
*
465+
* @param value must not be {@literal null}
466+
* @return a new {@link Criteria} object
467+
*/
468+
Criteria notLike(Object value);
469+
431470
/**
432471
* Creates a {@link Criteria} using {@code IS NULL}.
433472
*/
@@ -437,6 +476,20 @@ public interface CriteriaStep {
437476
* Creates a {@link Criteria} using {@code IS NOT NULL}.
438477
*/
439478
Criteria isNotNull();
479+
480+
/**
481+
* Creates a {@link Criteria} using {@code IS TRUE}.
482+
*
483+
* @return a new {@link Criteria} object
484+
*/
485+
Criteria isTrue();
486+
487+
/**
488+
* Creates a {@link Criteria} using {@code IS FALSE}.
489+
*
490+
* @return a new {@link Criteria} object
491+
*/
492+
Criteria isFalse();
440493
}
441494

442495
/**
@@ -596,6 +649,16 @@ public Criteria like(Object value) {
596649
return createCriteria(Comparator.LIKE, value);
597650
}
598651

652+
/*
653+
* (non-Javadoc)
654+
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#notLike(java.lang.Object)
655+
*/
656+
@Override
657+
public Criteria notLike(Object value) {
658+
Assert.notNull(value, "Value must not be null!");
659+
return createCriteria(Comparator.NOT_LIKE, value);
660+
}
661+
599662
/*
600663
* (non-Javadoc)
601664
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#isNull()
@@ -614,6 +677,24 @@ public Criteria isNotNull() {
614677
return createCriteria(Comparator.IS_NOT_NULL, null);
615678
}
616679

680+
/*
681+
* (non-Javadoc)
682+
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#isTrue()
683+
*/
684+
@Override
685+
public Criteria isTrue() {
686+
return createCriteria(Comparator.IS_TRUE, null);
687+
}
688+
689+
/*
690+
* (non-Javadoc)
691+
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#isFalse()
692+
*/
693+
@Override
694+
public Criteria isFalse() {
695+
return createCriteria(Comparator.IS_FALSE, null);
696+
}
697+
617698
protected Criteria createCriteria(Comparator comparator, Object value) {
618699
return new Criteria(this.property, comparator, value);
619700
}

Diff for: src/main/java/org/springframework/data/r2dbc/query/QueryMapper.java

+138-16
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,8 @@ private Condition mapCondition(Criteria criteria, MutableBindings bindings, Tabl
320320
typeHint = actualType.getType();
321321
}
322322

323-
return createCondition(column, mappedValue, typeHint, bindings, criteria.getComparator());
323+
return createCondition(column, mappedValue, typeHint, bindings, criteria.getComparator(),
324+
criteria.isIgnoreCase());
324325
}
325326

326327
/**
@@ -370,7 +371,7 @@ protected MappingContext<? extends RelationalPersistentEntity<?>, RelationalPers
370371
}
371372

372373
private Condition createCondition(Column column, @Nullable Object mappedValue, Class<?> valueType,
373-
MutableBindings bindings, Comparator comparator) {
374+
MutableBindings bindings, Comparator comparator, boolean ignoreCase) {
374375

375376
if (comparator.equals(Comparator.IS_NULL)) {
376377
return column.isNull();
@@ -380,6 +381,19 @@ private Condition createCondition(Column column, @Nullable Object mappedValue, C
380381
return column.isNotNull();
381382
}
382383

384+
if (comparator == Comparator.IS_TRUE) {
385+
return column.isEqualTo(SQL.literalOf((Object) ("TRUE")));
386+
}
387+
388+
if (comparator == Comparator.IS_FALSE) {
389+
return column.isEqualTo(SQL.literalOf((Object) ("FALSE")));
390+
}
391+
392+
Expression columnExpression = column;
393+
if (ignoreCase && String.class == valueType) {
394+
columnExpression = new Upper(column);
395+
}
396+
383397
if (comparator == Comparator.NOT_IN || comparator == Comparator.IN) {
384398

385399
Condition condition;
@@ -395,14 +409,14 @@ private Condition createCondition(Column column, @Nullable Object mappedValue, C
395409
expressions.add(bind(o, valueType, bindings, bindMarker));
396410
}
397411

398-
condition = column.in(expressions.toArray(new Expression[0]));
412+
condition = Conditions.in(columnExpression, expressions.toArray(new Expression[0]));
399413

400414
} else {
401415

402416
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
403417
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
404418

405-
condition = column.in(expression);
419+
condition = Conditions.in(columnExpression, expression);
406420
}
407421

408422
if (comparator == Comparator.NOT_IN) {
@@ -413,23 +427,40 @@ private Condition createCondition(Column column, @Nullable Object mappedValue, C
413427
}
414428

415429
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
416-
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
417430

418431
switch (comparator) {
419-
case EQ:
420-
return column.isEqualTo(expression);
421-
case NEQ:
422-
return column.isNotEqualTo(expression);
423-
case LT:
432+
case EQ: {
433+
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
434+
return Conditions.isEqual(columnExpression, expression);
435+
}
436+
case NEQ: {
437+
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
438+
return Conditions.isEqual(columnExpression, expression).not();
439+
}
440+
case LT: {
441+
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
424442
return column.isLess(expression);
425-
case LTE:
443+
}
444+
case LTE: {
445+
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
426446
return column.isLessOrEqualTo(expression);
427-
case GT:
447+
}
448+
case GT: {
449+
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
428450
return column.isGreater(expression);
429-
case GTE:
451+
}
452+
case GTE: {
453+
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
430454
return column.isGreaterOrEqualTo(expression);
431-
case LIKE:
432-
return column.like(expression);
455+
}
456+
case LIKE: {
457+
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
458+
return Conditions.like(columnExpression, expression);
459+
}
460+
case NOT_LIKE: {
461+
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
462+
return NotLike.create(columnExpression, expression);
463+
}
433464
default:
434465
throw new UnsupportedOperationException("Comparator " + comparator + " not supported");
435466
}
@@ -459,14 +490,20 @@ Class<?> getTypeHint(@Nullable Object mappedValue, Class<?> propertyType, Settab
459490

460491
private Expression bind(@Nullable Object mappedValue, Class<?> valueType, MutableBindings bindings,
461492
BindMarker bindMarker) {
493+
return bind(mappedValue, valueType, bindings, bindMarker, false);
494+
}
495+
496+
private Expression bind(@Nullable Object mappedValue, Class<?> valueType, MutableBindings bindings,
497+
BindMarker bindMarker, boolean ignoreCase) {
462498

463499
if (mappedValue != null) {
464500
bindings.bind(bindMarker, mappedValue);
465501
} else {
466502
bindings.bindNull(bindMarker, valueType);
467503
}
468504

469-
return SQL.bindMarker(bindMarker.getPlaceholder());
505+
return ignoreCase ? new Upper(SQL.bindMarker(bindMarker.getPlaceholder()))
506+
: SQL.bindMarker(bindMarker.getPlaceholder());
470507
}
471508

472509
/**
@@ -665,4 +702,89 @@ public String toString() {
665702
return toSql(IdentifierProcessing.ANSI);
666703
}
667704
}
705+
706+
// TODO: include support of NOT LIKE operator into spring-data-relational
707+
/**
708+
* Negated LIKE {@link Condition} comparing two {@link Expression}s.
709+
* <p/>
710+
* Results in a rendered condition: {@code <left> NOT LIKE <right>}.
711+
*/
712+
private static class NotLike implements Segment, Condition {
713+
private final Comparison delegate;
714+
715+
private NotLike(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
716+
this.delegate = Comparison.create(leftColumnOrExpression, "NOT LIKE", rightColumnOrExpression);
717+
}
718+
719+
/**
720+
* Creates new instance of this class with the given {@link Expression}s.
721+
*
722+
* @param leftColumnOrExpression the left {@link Expression}
723+
* @param rightColumnOrExpression the right {@link Expression}
724+
* @return {@link NotLike} condition
725+
*/
726+
public static NotLike create(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
727+
Assert.notNull(leftColumnOrExpression, "Left expression must not be null!");
728+
Assert.notNull(rightColumnOrExpression, "Right expression must not be null!");
729+
return new NotLike(leftColumnOrExpression, rightColumnOrExpression);
730+
}
731+
732+
@Override
733+
public void visit(Visitor visitor) {
734+
Assert.notNull(visitor, "Visitor must not be null!");
735+
delegate.visit(visitor);
736+
}
737+
738+
@Override
739+
public String toString() {
740+
return delegate.toString();
741+
}
742+
}
743+
744+
// TODO: include support of functions in WHERE conditions into spring-data-relational
745+
/**
746+
* Models the ANSI SQL {@code UPPER} function.
747+
* <p>
748+
* Results in a rendered function: {@code UPPER(<expression>)}.
749+
*/
750+
private class Upper implements Expression {
751+
private Literal<Object> delegate;
752+
753+
/**
754+
* Creates new instance of this class with the given expression. Only expressions of type {@link Column} and
755+
* {@link org.springframework.data.relational.core.sql.BindMarker} are supported.
756+
*
757+
* @param expression expression to be uppercased (must not be {@literal null})
758+
*/
759+
private Upper(Expression expression) {
760+
Assert.notNull(expression, "Expression must not be null!");
761+
String functionArgument;
762+
if (expression instanceof org.springframework.data.relational.core.sql.BindMarker) {
763+
functionArgument = expression instanceof Named ? ((Named) expression).getName().getReference()
764+
: expression.toString();
765+
} else if (expression instanceof Column) {
766+
functionArgument = "";
767+
Table table = ((Column) expression).getTable();
768+
if (table != null) {
769+
functionArgument = toSql(table.getName()) + ".";
770+
}
771+
functionArgument += toSql(((Column) expression).getName());
772+
} else {
773+
throw new IllegalArgumentException("Unable to ignore case expression of type " + expression.getClass().getName()
774+
+ ". Only " + Column.class.getName() + " and "
775+
+ org.springframework.data.relational.core.sql.BindMarker.class.getName() + " types are supported");
776+
}
777+
this.delegate = SQL.literalOf((Object) ("UPPER(" + functionArgument + ")"));
778+
}
779+
780+
@Override
781+
public void visit(Visitor visitor) {
782+
delegate.visit(visitor);
783+
}
784+
785+
@Override
786+
public String toString() {
787+
return delegate.toString();
788+
}
789+
}
668790
}

0 commit comments

Comments
 (0)