Browse Source

DATAJDBC-514 - Add support for Between and Not Like to Criteria API and SQL generation.

We now support Conditions.between, notBetween, and notLike as additional criteria conditions and support case-insensitive comparisons.
For LIKE escaping we pick up the Escaper configured at Dialect level. The newly introduced ValueFunction allows string transformation before computing a value by applying the Escaper to the raw value. Escaping is required for StartingWith, Contains and EndsWith PartTree operations.

Original pull request: spring-projects/spring-data-r2dbc#295.
pull/197/head
Mark Paluch 6 years ago
parent
commit
15f868120a
No known key found for this signature in database
GPG Key ID: 51A00FA751B91849
  1. 12
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java
  2. 11
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java
  3. 46
      spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Criteria.java
  4. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/query/CriteriaDefinition.java
  5. 59
      spring-data-relational/src/main/java/org/springframework/data/relational/core/query/ValueFunction.java
  6. 96
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Between.java
  7. 47
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BooleanLiteral.java
  8. 41
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java
  9. 38
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java
  10. 29
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java
  11. 15
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Like.java
  12. 12
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java
  13. 123
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/BetweenVisitor.java
  14. 1
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ComparisonVisitor.java
  15. 7
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java
  16. 8
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java
  17. 11
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/LikeVisitor.java
  18. 93
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SimpleFunctionVisitor.java
  19. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSingleConditionRenderSupport.java
  20. 3
      spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/CriteriaFactory.java
  21. 57
      spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/ParameterMetadataProvider.java
  22. 25
      spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalQueryCreator.java
  23. 25
      spring-data-relational/src/test/java/org/springframework/data/relational/core/query/CriteriaTests.java
  24. 37
      spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java
  25. 1
      spring-data-relational/src/test/java/org/springframework/data/relational/degraph/DependencyTests.java
  26. 101
      spring-data-relational/src/test/java/org/springframework/data/relational/repository/query/ParameterMetadataProviderUnitTests.java

12
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java

@ -15,9 +15,9 @@ @@ -15,9 +15,9 @@
*/
package org.springframework.data.relational.core.dialect;
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
/**
* Represents a dialect that is implemented by a particular database. Please note that not all features are supported by
@ -63,4 +63,14 @@ public interface Dialect { @@ -63,4 +63,14 @@ public interface Dialect {
default IdentifierProcessing getIdentifierProcessing() {
return IdentifierProcessing.ANSI;
}
/**
* Returns the {@link Escaper} used for {@code LIKE} value escaping.
*
* @return the {@link Escaper} used for {@code LIKE} value escaping.
* @since 2.0
*/
default Escaper getLikeEscaper() {
return Escaper.DEFAULT;
}
}

11
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java

@ -31,7 +31,7 @@ public class SqlServerDialect extends AbstractDialect { @@ -31,7 +31,7 @@ public class SqlServerDialect extends AbstractDialect {
*/
public static final SqlServerDialect INSTANCE = new SqlServerDialect();
protected SqlServerDialect() { }
protected SqlServerDialect() {}
private static final LimitClause LIMIT_CLAUSE = new LimitClause() {
@ -84,6 +84,15 @@ public class SqlServerDialect extends AbstractDialect { @@ -84,6 +84,15 @@ public class SqlServerDialect extends AbstractDialect {
return LIMIT_CLAUSE;
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.dialect.Dialect#getLikeEscaper()
*/
@Override
public Escaper getLikeEscaper() {
return Escaper.DEFAULT.withRewriteFor("[", "]");
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.dialect.AbstractDialect#getSelectContext()

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

@ -22,6 +22,7 @@ import java.util.List; @@ -22,6 +22,7 @@ import java.util.List;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.util.Pair;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@ -93,7 +94,6 @@ public class Criteria implements CriteriaDefinition { @@ -93,7 +94,6 @@ public class Criteria implements CriteriaDefinition {
this.ignoreCase = false;
}
/**
* Static factory method to create an empty Criteria.
*
@ -417,6 +417,24 @@ public class Criteria implements CriteriaDefinition { @@ -417,6 +417,24 @@ public class Criteria implements CriteriaDefinition {
*/
Criteria notIn(Collection<?> values);
/**
* Creates a {@link Criteria} using between ({@literal BETWEEN begin AND end}).
*
* @param begin must not be {@literal null}.
* @param end must not be {@literal null}.
* @since 2.2
*/
Criteria between(Object begin, Object end);
/**
* Creates a {@link Criteria} using not between ({@literal NOT BETWEEN begin AND end}).
*
* @param begin must not be {@literal null}.
* @param end must not be {@literal null}.
* @since 2.2
*/
Criteria notBetween(Object begin, Object end);
/**
* Creates a {@link Criteria} using less-than ({@literal <}).
*
@ -582,6 +600,32 @@ public class Criteria implements CriteriaDefinition { @@ -582,6 +600,32 @@ public class Criteria implements CriteriaDefinition {
return createCriteria(Comparator.NOT_IN, values);
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#between(java.lang.Object, java.lang.Object)
*/
@Override
public Criteria between(Object begin, Object end) {
Assert.notNull(begin, "Begin value must not be null!");
Assert.notNull(end, "End value must not be null!");
return createCriteria(Comparator.BETWEEN, Pair.of(begin, end));
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#notBetween(java.lang.Object, java.lang.Object)
*/
@Override
public Criteria notBetween(Object begin, Object end) {
Assert.notNull(begin, "Begin value must not be null!");
Assert.notNull(end, "End value must not be null!");
return createCriteria(Comparator.NOT_BETWEEN, Pair.of(begin, end));
}
/*
* (non-Javadoc)
* @see org.springframework.data.r2dbc.function.query.Criteria.CriteriaStep#lessThan(java.lang.Object)

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

@ -135,6 +135,6 @@ public interface CriteriaDefinition { @@ -135,6 +135,6 @@ public interface CriteriaDefinition {
}
enum Comparator {
INITIAL, EQ, NEQ, LT, LTE, GT, GTE, IS_NULL, IS_NOT_NULL, LIKE, NOT_LIKE, NOT_IN, IN, IS_TRUE, IS_FALSE
INITIAL, EQ, NEQ, BETWEEN, NOT_BETWEEN, LT, LTE, GT, GTE, IS_NULL, IS_NOT_NULL, LIKE, NOT_LIKE, NOT_IN, IN, IS_TRUE, IS_FALSE
}
}

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

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.query;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.data.relational.core.dialect.Escaper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Represents a value function to return arbitrary values that can be escaped before returning the actual value. Can be
* used with the criteria API for deferred value retrieval.
*
* @author Mark Paluch
* @since 2.0
* @see Escaper
* @see Supplier
*/
@FunctionalInterface
public interface ValueFunction<T> extends Function<Escaper, T> {
/**
* Produces a value by considering the given {@link Escaper}.
*
* @param escaper the escaper to use.
* @return the return value, may be {@literal null}.
*/
@Nullable
@Override
T apply(Escaper escaper);
/**
* Adapts this value factory into a {@link Supplier} by using the given {@link Escaper}.
*
* @param escaper the escaper to use.
* @return the value factory
*/
default Supplier<T> toSupplier(Escaper escaper) {
Assert.notNull(escaper, "Escaper must not be null");
return () -> apply(escaper);
}
}

96
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Between.java

@ -0,0 +1,96 @@ @@ -0,0 +1,96 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.sql;
import org.springframework.util.Assert;
/**
* BETWEEN {@link Condition} comparing between {@link Expression}s.
* <p/>
* Results in a rendered condition: {@code <left> BETWEEN <begin> AND <end>}.
*
* @author Mark Paluch
* @since 2.2
*/
public class Between extends AbstractSegment implements Condition {
private final Expression column;
private final Expression begin;
private final Expression end;
private final boolean negated;
private Between(Expression column, Expression begin, Expression end, boolean negated) {
super(column, begin, end);
this.column = column;
this.begin = begin;
this.end = end;
this.negated = negated;
}
/**
* Creates a new {@link Between} {@link Condition} given two {@link Expression}s.
*
* @param columnOrExpression left side of the comparison.
* @param begin begin value of the comparison.
* @param end end value of the comparison.
* @return the {@link Between} condition.
*/
public static Between create(Expression columnOrExpression, Expression begin, Expression end) {
Assert.notNull(columnOrExpression, "Column or expression must not be null!");
Assert.notNull(begin, "Begin value must not be null!");
Assert.notNull(end, "end value must not be null!");
return new Between(columnOrExpression, begin, end, false);
}
/**
* @return the column {@link Expression}.
*/
public Expression getColumn() {
return column;
}
/**
* @return the begin {@link Expression}.
*/
public Expression getBegin() {
return begin;
}
/**
* @return the end {@link Expression}.
*/
public Expression getEnd() {
return end;
}
public boolean isNegated() {
return negated;
}
@Override
public Between not() {
return new Between(this.column, this.begin, this.end, !negated);
}
@Override
public String toString() {
return column.toString() + " BETWEEN " + begin.toString() + " AND " + end.toString();
}
}

47
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BooleanLiteral.java

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.sql;
/**
* Represents a {@link Boolean} literal.
*
* @author Mark Paluch
* @since 2.0
*/
public class BooleanLiteral extends Literal<Boolean> {
BooleanLiteral(boolean content) {
super(content);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.Literal#getContent()
*/
@Override
public Boolean getContent() {
return super.getContent();
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.Literal#toString()
*/
@Override
public String toString() {
return getContent() ? "TRUE" : "FALSE";
}
}

41
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java

@ -163,7 +163,31 @@ public class Column extends AbstractSegment implements Expression, Named { @@ -163,7 +163,31 @@ public class Column extends AbstractSegment implements Expression, Named {
}
/**
* Creates a {@code <} (less) {@link Condition} {@link Condition}.
* Creates a {@code BETWEEN} {@link Condition}.
*
* @param begin begin value for the comparison.
* @param end end value for the comparison.
* @return the {@link Between} condition.
* @since 2.0
*/
public Between between(Expression begin, Expression end) {
return Conditions.between(this, begin, end);
}
/**
* Creates a {@code NOT BETWEEN} {@link Condition}.
*
* @param begin begin value for the comparison.
* @param end end value for the comparison.
* @return the {@link Between} condition.
* @since 2.0
*/
public Between notBetween(Expression begin, Expression end) {
return Conditions.notBetween(this, begin, end);
}
/**
* Creates a {@code <} (less) {@link Condition}.
*
* @param expression right side of the comparison.
* @return the {@link Comparison} condition.
@ -173,7 +197,7 @@ public class Column extends AbstractSegment implements Expression, Named { @@ -173,7 +197,7 @@ public class Column extends AbstractSegment implements Expression, Named {
}
/**
* CCreates a {@code <=} (greater ) {@link Condition} {@link Condition}.
* CCreates a {@code <=} (greater) {@link Condition}.
*
* @param expression right side of the comparison.
* @return the {@link Comparison} condition.
@ -193,7 +217,7 @@ public class Column extends AbstractSegment implements Expression, Named { @@ -193,7 +217,7 @@ public class Column extends AbstractSegment implements Expression, Named {
}
/**
* Creates a {@code <=} (greater or equal to) {@link Condition} {@link Condition}.
* Creates a {@code <=} (greater or equal to) {@link Condition}.
*
* @param expression right side of the comparison.
* @return the {@link Comparison} condition.
@ -212,6 +236,17 @@ public class Column extends AbstractSegment implements Expression, Named { @@ -212,6 +236,17 @@ public class Column extends AbstractSegment implements Expression, Named {
return Conditions.like(this, expression);
}
/**
* Creates a {@code NOT LIKE} {@link Condition}.
*
* @param expression right side of the comparison.
* @return the {@link Like} condition.
* @since 2.0
*/
public Like notLike(Expression expression) {
return Conditions.notLike(this, expression);
}
/**
* Creates a new {@link In} {@link Condition} given right {@link Expression}s.
*

38
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Conditions.java

@ -87,6 +87,32 @@ public abstract class Conditions { @@ -87,6 +87,32 @@ public abstract class Conditions {
return Comparison.create(leftColumnOrExpression, "!=", rightColumnOrExpression);
}
/**
* Creates a {@code BETWEEN} {@link Condition}.
*
* @param columnOrExpression left side of the comparison.
* @param begin begin value of the comparison.
* @param end end value of the comparison.
* @return the {@link Comparison} condition.
* @since 2.0
*/
public static Between between(Expression columnOrExpression, Expression begin, Expression end) {
return Between.create(columnOrExpression, begin, end);
}
/**
* Creates a {@code NOT BETWEEN} {@link Condition}.
*
* @param columnOrExpression left side of the comparison.
* @param begin begin value of the comparison.
* @param end end value of the comparison.
* @return the {@link Comparison} condition.
* @since 2.0
*/
public static Between notBetween(Expression columnOrExpression, Expression begin, Expression end) {
return between(columnOrExpression, begin, end).not();
}
/**
* Creates a {@code <} (less) {@link Condition} comparing {@code left} is less than {@code right}.
*
@ -144,6 +170,18 @@ public abstract class Conditions { @@ -144,6 +170,18 @@ public abstract class Conditions {
return Like.create(leftColumnOrExpression, rightColumnOrExpression);
}
/**
* Creates a {@code NOT LIKE} {@link Condition}.
*
* @param leftColumnOrExpression left side of the comparison.
* @param rightColumnOrExpression right side of the comparison.
* @return the {@link Comparison} condition.
* @since 2.0
*/
public static Like notLike(Expression leftColumnOrExpression, Expression rightColumnOrExpression) {
return Like.create(leftColumnOrExpression, rightColumnOrExpression).not();
}
/**
* Creates a {@code IN} {@link Condition clause}.
*

29
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Functions.java

@ -18,6 +18,7 @@ package org.springframework.data.relational.core.sql; @@ -18,6 +18,7 @@ package org.springframework.data.relational.core.sql;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import org.springframework.util.Assert;
@ -60,6 +61,34 @@ public class Functions { @@ -60,6 +61,34 @@ public class Functions {
return SimpleFunction.create("COUNT", new ArrayList<>(columns));
}
/**
* Creates a new {@code UPPER} function.
*
* @param expression expression to apply count, must not be {@literal null}.
* @return the new {@link SimpleFunction upper function} for {@code expression}.
* @since 2.0
*/
public static SimpleFunction upper(Expression expression) {
Assert.notNull(expression, "Expression must not be null!");
return SimpleFunction.create("UPPER", Collections.singletonList(expression));
}
/**
* Creates a new {@code LOWER} function.
*
* @param expression expression to apply lower, must not be {@literal null}.
* @return the new {@link SimpleFunction lower function} for {@code expression}.
* @since 2.0
*/
public static SimpleFunction lower(Expression expression) {
Assert.notNull(expression, "Columns must not be null!");
return SimpleFunction.create("LOWER", Collections.singletonList(expression));
}
// Utility constructor.
private Functions() {}
}

15
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Like.java

@ -29,13 +29,15 @@ public class Like extends AbstractSegment implements Condition { @@ -29,13 +29,15 @@ public class Like extends AbstractSegment implements Condition {
private final Expression left;
private final Expression right;
private final boolean negated;
private Like(Expression left, Expression right) {
private Like(Expression left, Expression right, boolean negated) {
super(left, right);
this.left = left;
this.right = right;
this.negated = negated;
}
/**
@ -50,7 +52,7 @@ public class Like extends AbstractSegment implements Condition { @@ -50,7 +52,7 @@ public class Like extends AbstractSegment implements Condition {
Assert.notNull(leftColumnOrExpression, "Left expression must not be null!");
Assert.notNull(rightColumnOrExpression, "Right expression must not be null!");
return new Like(leftColumnOrExpression, rightColumnOrExpression);
return new Like(leftColumnOrExpression, rightColumnOrExpression, false);
}
/**
@ -67,6 +69,15 @@ public class Like extends AbstractSegment implements Condition { @@ -67,6 +69,15 @@ public class Like extends AbstractSegment implements Condition {
return right;
}
public boolean isNegated() {
return negated;
}
@Override
public Like not() {
return new Like(this.left, this.right, !negated);
}
@Override
public String toString() {
return left.toString() + " LIKE " + right.toString();

12
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java

@ -78,6 +78,18 @@ public abstract class SQL { @@ -78,6 +78,18 @@ public abstract class SQL {
return new NamedBindMarker(name);
}
/**
* Creates a new {@link BooleanLiteral} rendering either {@code TRUE} or {@literal FALSE} depending on the given
* {@code value}.
*
* @param value the literal content.
* @return a new {@link BooleanLiteral}.
* @since 2.0
*/
public static BooleanLiteral literalOf(boolean value) {
return new BooleanLiteral(value);
}
/**
* Creates a new {@link StringLiteral} from the {@code content}.
*

123
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/BetweenVisitor.java

@ -0,0 +1,123 @@ @@ -0,0 +1,123 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.sql.render;
import org.springframework.data.relational.core.sql.Between;
import org.springframework.data.relational.core.sql.Condition;
import org.springframework.data.relational.core.sql.Expression;
import org.springframework.data.relational.core.sql.Visitable;
import org.springframework.lang.Nullable;
/**
* {@link org.springframework.data.relational.core.sql.Visitor} rendering comparison {@link Condition}. Uses a
* {@link RenderTarget} to call back for render results.
*
* @author Mark Paluch
* @see Between
* @since 2.0
*/
class BetweenVisitor extends FilteredSubtreeVisitor {
private final Between between;
private final RenderContext context;
private final RenderTarget target;
private final StringBuilder part = new StringBuilder();
private boolean renderedTestExpression = false;
private boolean renderedPreamble = false;
private boolean done = false;
private @Nullable PartRenderer current;
BetweenVisitor(Between condition, RenderContext context, RenderTarget target) {
super(it -> it == condition);
this.between = condition;
this.context = context;
this.target = target;
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.render.FilteredSubtreeVisitor#enterNested(org.springframework.data.relational.core.sql.Visitable)
*/
@Override
Delegation enterNested(Visitable segment) {
if (segment instanceof Expression) {
ExpressionVisitor visitor = new ExpressionVisitor(context);
current = visitor;
return Delegation.delegateTo(visitor);
}
if (segment instanceof Condition) {
ConditionVisitor visitor = new ConditionVisitor(context);
current = visitor;
return Delegation.delegateTo(visitor);
}
throw new IllegalStateException("Cannot provide visitor for " + segment);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.render.FilteredSubtreeVisitor#leaveNested(org.springframework.data.relational.core.sql.Visitable)
*/
@Override
Delegation leaveNested(Visitable segment) {
if (current != null && !done) {
if (renderedPreamble) {
part.append(" AND ");
part.append(current.getRenderedPart());
done = true;
}
if (renderedTestExpression && !renderedPreamble) {
part.append(' ');
if (between.isNegated()) {
part.append("NOT ");
}
part.append("BETWEEN ");
renderedPreamble = true;
part.append(current.getRenderedPart());
}
if (!renderedTestExpression) {
part.append(current.getRenderedPart());
renderedTestExpression = true;
}
current = null;
}
return super.leaveNested(segment);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.render.FilteredSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable)
*/
@Override
Delegation leaveMatched(Visitable segment) {
target.onRendered(part);
return super.leaveMatched(segment);
}
}

1
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ComparisonVisitor.java

@ -18,6 +18,7 @@ package org.springframework.data.relational.core.sql.render; @@ -18,6 +18,7 @@ package org.springframework.data.relational.core.sql.render;
import org.springframework.data.relational.core.sql.Comparison;
import org.springframework.data.relational.core.sql.Condition;
import org.springframework.data.relational.core.sql.Expression;
import org.springframework.data.relational.core.sql.SimpleFunction;
import org.springframework.data.relational.core.sql.Visitable;
import org.springframework.lang.Nullable;

7
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ConditionVisitor.java

@ -16,12 +16,13 @@ @@ -16,12 +16,13 @@
package org.springframework.data.relational.core.sql.render;
import org.springframework.data.relational.core.sql.AndCondition;
import org.springframework.data.relational.core.sql.Between;
import org.springframework.data.relational.core.sql.Comparison;
import org.springframework.data.relational.core.sql.Condition;
import org.springframework.data.relational.core.sql.NestedCondition;
import org.springframework.data.relational.core.sql.In;
import org.springframework.data.relational.core.sql.IsNull;
import org.springframework.data.relational.core.sql.Like;
import org.springframework.data.relational.core.sql.NestedCondition;
import org.springframework.data.relational.core.sql.OrCondition;
import org.springframework.lang.Nullable;
@ -75,6 +76,10 @@ class ConditionVisitor extends TypedSubtreeVisitor<Condition> implements PartRen @@ -75,6 +76,10 @@ class ConditionVisitor extends TypedSubtreeVisitor<Condition> implements PartRen
return new IsNullVisitor(context, builder::append);
}
if (segment instanceof Between) {
return new BetweenVisitor((Between) segment, context, builder::append);
}
if (segment instanceof Comparison) {
return new ComparisonVisitor(context, (Comparison) segment, builder::append);
}

8
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/ExpressionVisitor.java

@ -21,6 +21,7 @@ import org.springframework.data.relational.core.sql.Condition; @@ -21,6 +21,7 @@ import org.springframework.data.relational.core.sql.Condition;
import org.springframework.data.relational.core.sql.Expression;
import org.springframework.data.relational.core.sql.Literal;
import org.springframework.data.relational.core.sql.Named;
import org.springframework.data.relational.core.sql.SimpleFunction;
import org.springframework.data.relational.core.sql.SubselectExpression;
import org.springframework.data.relational.core.sql.Visitable;
import org.springframework.lang.Nullable;
@ -59,6 +60,13 @@ class ExpressionVisitor extends TypedSubtreeVisitor<Expression> implements PartR @@ -59,6 +60,13 @@ class ExpressionVisitor extends TypedSubtreeVisitor<Expression> implements PartR
return Delegation.delegateTo(visitor);
}
if (segment instanceof SimpleFunction) {
SimpleFunctionVisitor visitor = new SimpleFunctionVisitor(context);
partRenderer = visitor;
return Delegation.delegateTo(visitor);
}
if (segment instanceof Column) {
Column column = (Column) segment;

11
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/LikeVisitor.java

@ -31,6 +31,7 @@ import org.springframework.lang.Nullable; @@ -31,6 +31,7 @@ import org.springframework.lang.Nullable;
*/
class LikeVisitor extends FilteredSubtreeVisitor {
private final Like like;
private final RenderContext context;
private final RenderTarget target;
private final StringBuilder part = new StringBuilder();
@ -38,6 +39,7 @@ class LikeVisitor extends FilteredSubtreeVisitor { @@ -38,6 +39,7 @@ class LikeVisitor extends FilteredSubtreeVisitor {
LikeVisitor(Like condition, RenderContext context, RenderTarget target) {
super(it -> it == condition);
this.like = condition;
this.context = context;
this.target = target;
}
@ -73,7 +75,14 @@ class LikeVisitor extends FilteredSubtreeVisitor { @@ -73,7 +75,14 @@ class LikeVisitor extends FilteredSubtreeVisitor {
if (current != null) {
if (part.length() != 0) {
part.append(" LIKE ");
part.append(' ');
if (like.isNegated()) {
part.append("NOT ");
}
part.append("LIKE ");
}
part.append(current.getRenderedPart());

93
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/SimpleFunctionVisitor.java

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.sql.render;
import org.springframework.data.relational.core.sql.SimpleFunction;
import org.springframework.data.relational.core.sql.Visitable;
/**
* Renderer for {@link org.springframework.data.relational.core.sql.SimpleFunction}. Uses a {@link RenderTarget} to call
* back for render results.
*
* @author Mark Paluch
* @author Jens Schauder
* @since 1.1
*/
class SimpleFunctionVisitor extends TypedSingleConditionRenderSupport<SimpleFunction> implements PartRenderer {
private final StringBuilder part = new StringBuilder();
private boolean needsComma = false;
private String functionName;
SimpleFunctionVisitor(RenderContext context) {
super(context);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveNested(org.springframework.data.relational.core.sql.Visitable)
*/
@Override
Delegation leaveNested(Visitable segment) {
if (hasDelegatedRendering()) {
if (needsComma) {
part.append(", ");
}
if (part.length() == 0) {
part.append(functionName).append("(");
}
part.append(consumeRenderedPart());
needsComma = true;
}
return super.leaveNested(segment);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#enterMatched(org.springframework.data.relational.core.sql.Visitable)
*/
@Override
Delegation enterMatched(SimpleFunction segment) {
functionName = segment.getFunctionName();
return super.enterMatched(segment);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.render.TypedSubtreeVisitor#leaveMatched(org.springframework.data.relational.core.sql.Visitable)
*/
@Override
Delegation leaveMatched(SimpleFunction segment) {
part.append(")");
return super.leaveMatched(segment);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.sql.render.PartRenderer#getRenderedPart()
*/
@Override
public CharSequence getRenderedPart() {
return part;
}
}

2
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSingleConditionRenderSupport.java

@ -28,7 +28,7 @@ import org.springframework.util.Assert; @@ -28,7 +28,7 @@ import org.springframework.util.Assert;
* @author Mark Paluch
* @since 1.1
*/
abstract class TypedSingleConditionRenderSupport<T extends Visitable & Condition> extends TypedSubtreeVisitor<T> {
abstract class TypedSingleConditionRenderSupport<T extends Visitable> extends TypedSubtreeVisitor<T> {
private final RenderContext context;
private @Nullable PartRenderer current;

3
spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/CriteriaFactory.java

@ -66,8 +66,7 @@ class CriteriaFactory { @@ -66,8 +66,7 @@ class CriteriaFactory {
case BETWEEN: {
ParameterMetadata geParamMetadata = parameterMetadataProvider.next(part);
ParameterMetadata leParamMetadata = parameterMetadataProvider.next(part);
return criteriaStep.greaterThanOrEquals(geParamMetadata.getValue()).and(propertyName)
.lessThanOrEquals(leParamMetadata.getValue());
return criteriaStep.between(geParamMetadata.getValue(), leParamMetadata.getValue());
}
case AFTER:
case GREATER_THAN: {

57
spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/ParameterMetadataProvider.java

@ -20,6 +20,7 @@ import java.util.Iterator; @@ -20,6 +20,7 @@ import java.util.Iterator;
import java.util.List;
import org.springframework.data.relational.core.dialect.Escaper;
import org.springframework.data.relational.core.query.ValueFunction;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.parser.Part;
@ -28,40 +29,26 @@ import org.springframework.util.Assert; @@ -28,40 +29,26 @@ import org.springframework.util.Assert;
/**
* Helper class to allow easy creation of {@link ParameterMetadata}s.
* <p>
* This class is an adapted version of {@code org.springframework.data.jpa.repository.query.ParameterMetadataProvider}
* from Spring Data JPA project.
*
* @author Roman Chigvintsev
* @author Mark Paluch
* @since 2.0
*/
public class ParameterMetadataProvider implements Iterable<ParameterMetadata> {
class ParameterMetadataProvider implements Iterable<ParameterMetadata> {
private static final Object VALUE_PLACEHOLDER = new Object();
private final Iterator<? extends Parameter> bindableParameterIterator;
private final Iterator<Object> bindableParameterValueIterator;
private final List<ParameterMetadata> parameterMetadata = new ArrayList<>();
private final Escaper escaper;
/**
* Creates new instance of this class with the given {@link RelationalParameterAccessor} and {@link Escaper}.
*
* @param accessor relational parameter accessor (must not be {@literal null}).
* @param escaper escaper for LIKE operator parameters (must not be {@literal null})
*/
public ParameterMetadataProvider(RelationalParameterAccessor accessor, Escaper escaper) {
this(accessor.getBindableParameters(), accessor.iterator(), escaper);
}
/**
* Creates new instance of this class with the given {@link Parameters} and {@link Escaper}.
*
* @param parameters method parameters (must not be {@literal null})
* @param escaper escaper for LIKE operator parameters (must not be {@literal null})
*/
public ParameterMetadataProvider(Parameters<?, ?> parameters, Escaper escaper) {
this(parameters, null, escaper);
public ParameterMetadataProvider(RelationalParameterAccessor accessor) {
this(accessor.getBindableParameters(), accessor.iterator());
}
/**
@ -70,16 +57,14 @@ public class ParameterMetadataProvider implements Iterable<ParameterMetadata> { @@ -70,16 +57,14 @@ public class ParameterMetadataProvider implements Iterable<ParameterMetadata> {
*
* @param bindableParameterValueIterator iterator over bindable parameter values
* @param parameters method parameters (must not be {@literal null})
* @param escaper escaper for LIKE operator parameters (must not be {@literal null})
*/
private ParameterMetadataProvider(Parameters<?, ?> parameters,
@Nullable Iterator<Object> bindableParameterValueIterator, Escaper escaper) {
@Nullable Iterator<Object> bindableParameterValueIterator) {
Assert.notNull(parameters, "Parameters must not be null!");
Assert.notNull(escaper, "Like escaper must not be null!");
this.bindableParameterIterator = parameters.getBindableParameters().iterator();
this.bindableParameterValueIterator = bindableParameterValueIterator;
this.escaper = escaper;
}
@Override
@ -91,8 +76,10 @@ public class ParameterMetadataProvider implements Iterable<ParameterMetadata> { @@ -91,8 +76,10 @@ public class ParameterMetadataProvider implements Iterable<ParameterMetadata> {
* Creates new instance of {@link ParameterMetadata} for the given {@link Part} and next {@link Parameter}.
*/
public ParameterMetadata next(Part part) {
Assert.isTrue(bindableParameterIterator.hasNext(),
() -> String.format("No parameter available for part %s.", part));
Parameter parameter = bindableParameterIterator.next();
String parameterName = getParameterName(parameter, part.getProperty().getSegment());
Object parameterValue = getParameterValue();
@ -104,10 +91,12 @@ public class ParameterMetadataProvider implements Iterable<ParameterMetadata> { @@ -104,10 +91,12 @@ public class ParameterMetadataProvider implements Iterable<ParameterMetadata> {
ParameterMetadata metadata = new ParameterMetadata(parameterName, preparedParameterValue, parameterType);
parameterMetadata.add(metadata);
return metadata;
}
private String getParameterName(Parameter parameter, String defaultName) {
if (parameter.isExplicitlyNamed()) {
return parameter.getName().orElseThrow(() -> new IllegalArgumentException("Parameter needs to be named"));
}
@ -144,18 +133,20 @@ public class ParameterMetadataProvider implements Iterable<ParameterMetadata> { @@ -144,18 +133,20 @@ public class ParameterMetadataProvider implements Iterable<ParameterMetadata> {
@Nullable
protected Object prepareParameterValue(@Nullable Object value, Class<?> valueType, Part.Type partType) {
if (value != null && String.class == valueType) {
switch (partType) {
case STARTING_WITH:
return escaper.escape(value.toString()) + "%";
case ENDING_WITH:
return "%" + escaper.escape(value.toString());
case CONTAINING:
case NOT_CONTAINING:
return "%" + escaper.escape(value.toString()) + "%";
}
if (value == null || !CharSequence.class.isAssignableFrom(valueType)) {
return value;
}
return value;
switch (partType) {
case STARTING_WITH:
return (ValueFunction<String>) escaper -> escaper.escape(value.toString()) + "%";
case ENDING_WITH:
return (ValueFunction<String>) escaper -> "%" + escaper.escape(value.toString());
case CONTAINING:
case NOT_CONTAINING:
return (ValueFunction<String>) escaper -> "%" + escaper.escape(value.toString()) + "%";
default:
return value;
}
}
}

25
spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalQueryCreator.java

@ -26,9 +26,10 @@ import org.springframework.data.util.Streamable; @@ -26,9 +26,10 @@ import org.springframework.data.util.Streamable;
import org.springframework.util.Assert;
/**
* Implementation of {@link AbstractQueryCreator} that creates {@link PreparedOperation} from a {@link PartTree}.
* Implementation of {@link AbstractQueryCreator} that creates a query from a {@link PartTree}.
*
* @author Roman Chigvintsev
* @author Mark Paluch
* @since 2.0
*/
public abstract class RelationalQueryCreator<T> extends AbstractQueryCreator<T, Criteria> {
@ -39,20 +40,21 @@ public abstract class RelationalQueryCreator<T> extends AbstractQueryCreator<T, @@ -39,20 +40,21 @@ public abstract class RelationalQueryCreator<T> extends AbstractQueryCreator<T,
* Creates new instance of this class with the given {@link PartTree}, {@link RelationalEntityMetadata} and
* {@link ParameterMetadataProvider}.
*
* @param tree part tree (must not be {@literal null})
* @param parameterMetadataProvider parameter metadata provider (must not be {@literal null})
* @param tree part tree, must not be {@literal null}.
* @param accessor parameter metadata provider, must not be {@literal null}.
*/
public RelationalQueryCreator(PartTree tree, ParameterMetadataProvider parameterMetadataProvider) {
public RelationalQueryCreator(PartTree tree, RelationalParameterAccessor accessor) {
super(tree);
Assert.notNull(parameterMetadataProvider, "Parameter metadata provider must not be null");
this.criteriaFactory = new CriteriaFactory(parameterMetadataProvider);
Assert.notNull(accessor, "RelationalParameterAccessor must not be null");
this.criteriaFactory = new CriteriaFactory(new ParameterMetadataProvider(accessor));
}
/**
* Creates {@link Criteria} for the given method name part.
*
* @param part method name part (must not be {@literal null})
* @param part method name part, must not be {@literal null}.
* @param iterator iterator over query parameter values
* @return new instance of {@link Criteria}
*/
@ -64,8 +66,8 @@ public abstract class RelationalQueryCreator<T> extends AbstractQueryCreator<T, @@ -64,8 +66,8 @@ public abstract class RelationalQueryCreator<T> extends AbstractQueryCreator<T,
/**
* Combines the given {@link Criteria} with the new one created for the given method name part using {@code AND}.
*
* @param part method name part (must not be {@literal null})
* @param base {@link Criteria} to be combined (must not be {@literal null})
* @param part method name part, must not be {@literal null}.
* @param base {@link Criteria} to be combined, must not be {@literal null}.
* @param iterator iterator over query parameter values
* @return {@link Criteria} combination
*/
@ -77,8 +79,8 @@ public abstract class RelationalQueryCreator<T> extends AbstractQueryCreator<T, @@ -77,8 +79,8 @@ public abstract class RelationalQueryCreator<T> extends AbstractQueryCreator<T,
/**
* Combines two {@link Criteria}s using {@code OR}.
*
* @param base {@link Criteria} to be combined (must not be {@literal null})
* @param criteria another {@link Criteria} to be combined (must not be {@literal null})
* @param base {@link Criteria} to be combined, must not be {@literal null}.
* @param criteria another {@link Criteria} to be combined, must not be {@literal null}.
* @return {@link Criteria} combination
*/
@Override
@ -96,6 +98,7 @@ public abstract class RelationalQueryCreator<T> extends AbstractQueryCreator<T, @@ -96,6 +98,7 @@ public abstract class RelationalQueryCreator<T> extends AbstractQueryCreator<T,
public static void validate(PartTree tree, RelationalParameters parameters) {
int argCount = 0;
Iterable<Part> parts = () -> tree.stream().flatMap(Streamable::stream).iterator();
for (Part part : parts) {
int numberOfArguments = part.getNumberOfArguments();

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

@ -1,25 +0,0 @@ @@ -1,25 +0,0 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.query;
import static org.junit.Assert.*;
/**
* @author Mark Paluch
*/
public class CriteriaTests {
}

37
spring-data-relational/src/test/java/org/springframework/data/relational/core/sql/render/ConditionRendererUnitTests.java

@ -21,6 +21,7 @@ import org.junit.Test; @@ -21,6 +21,7 @@ import org.junit.Test;
import org.springframework.data.relational.core.sql.Column;
import org.springframework.data.relational.core.sql.Conditions;
import org.springframework.data.relational.core.sql.Functions;
import org.springframework.data.relational.core.sql.StatementBuilder;
import org.springframework.data.relational.core.sql.Table;
@ -34,6 +35,7 @@ public class ConditionRendererUnitTests { @@ -34,6 +35,7 @@ public class ConditionRendererUnitTests {
Table table = Table.create("my_table");
Column left = table.column("left");
Column right = table.column("right");
Column other = table.column("other");
@Test // DATAJDBC-309
public void shouldRenderEquals() {
@ -43,6 +45,15 @@ public class ConditionRendererUnitTests { @@ -43,6 +45,15 @@ public class ConditionRendererUnitTests {
assertThat(sql).endsWith("WHERE my_table.left = my_table.right");
}
@Test // DATAJDBC-514
public void shouldRenderEqualsCaseInsensitive() {
String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table)
.where(Conditions.isEqual(Functions.upper(left), Functions.upper(right))).build());
assertThat(sql).endsWith("WHERE UPPER(my_table.left) = UPPER(my_table.right)");
}
@Test // DATAJDBC-490
public void shouldRenderEqualsNested() {
@ -104,6 +115,24 @@ public class ConditionRendererUnitTests { @@ -104,6 +115,24 @@ public class ConditionRendererUnitTests {
assertThat(sql).endsWith("WHERE my_table.left < my_table.right");
}
@Test // DATAJDBC-513
public void shouldRenderBetween() {
String sql = SqlRenderer
.toString(StatementBuilder.select(left).from(table).where(left.between(right, other)).build());
assertThat(sql).endsWith("WHERE my_table.left BETWEEN my_table.right AND my_table.other");
}
@Test // DATAJDBC-513
public void shouldRenderNotBetween() {
String sql = SqlRenderer
.toString(StatementBuilder.select(left).from(table).where(left.notBetween(right, other)).build());
assertThat(sql).endsWith("WHERE my_table.left NOT BETWEEN my_table.right AND my_table.other");
}
@Test // DATAJDBC-309
public void shouldRenderIsLessOrEqualTo() {
@ -146,6 +175,14 @@ public class ConditionRendererUnitTests { @@ -146,6 +175,14 @@ public class ConditionRendererUnitTests {
assertThat(sql).endsWith("WHERE my_table.left LIKE my_table.right");
}
@Test // DATAJDBC-513
public void shouldRenderNotLike() {
String sql = SqlRenderer.toString(StatementBuilder.select(left).from(table).where(left.notLike(right)).build());
assertThat(sql).endsWith("WHERE my_table.left NOT LIKE my_table.right");
}
@Test // DATAJDBC-309
public void shouldRenderIsNull() {

1
spring-data-relational/src/test/java/org/springframework/data/relational/degraph/DependencyTests.java

@ -51,6 +51,7 @@ public class DependencyTests { @@ -51,6 +51,7 @@ public class DependencyTests {
// include only Spring Data related classes (for example no JDK code)
.including("org.springframework.data.**") //
.excluding("org.springframework.data.relational.core.sql.**") //
.excluding("org.springframework.data.repository.query.parser.**") //
.filterClasspath(new AbstractFunction1<String, Object>() {
@Override
public Object apply(String s) { //

101
spring-data-relational/src/test/java/org/springframework/data/relational/repository/query/ParameterMetadataProviderUnitTests.java

@ -0,0 +1,101 @@ @@ -0,0 +1,101 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.repository.query;
import static org.assertj.core.api.Assertions.*;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.relational.core.dialect.Escaper;
import org.springframework.data.relational.core.query.ValueFunction;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.parser.PartTree;
/**
* Unit tests for {@link ParameterMetadataProvider}.
*
* @author Mark Paluch
*/
public class ParameterMetadataProviderUnitTests {
@Test // DATAJDBC-514
public void shouldCreateValueFunctionForContains() throws Exception {
ParameterMetadata metadata = getParameterMetadata("findByNameContains", "hell%o");
assertThat(metadata.getValue()).isInstanceOf(ValueFunction.class);
ValueFunction<Object> function = (ValueFunction<Object>) metadata.getValue();
assertThat(function.apply(Escaper.DEFAULT)).isEqualTo("%hell\\%o%");
}
@Test // DATAJDBC-514
public void shouldCreateValueFunctionForStartingWith() throws Exception {
ParameterMetadata metadata = getParameterMetadata("findByNameStartingWith", "hell%o");
assertThat(metadata.getValue()).isInstanceOf(ValueFunction.class);
ValueFunction<Object> function = (ValueFunction<Object>) metadata.getValue();
assertThat(function.apply(Escaper.DEFAULT)).isEqualTo("hell\\%o%");
}
@Test // DATAJDBC-514
public void shouldCreateValue() throws Exception {
ParameterMetadata metadata = getParameterMetadata("findByName", "hell%o");
assertThat(metadata.getValue()).isEqualTo("hell%o");
}
private ParameterMetadata getParameterMetadata(String methodName, Object value) throws Exception {
Method method = UserRepository.class.getMethod(methodName, String.class);
ParameterMetadataProvider provider = new ParameterMetadataProvider(new RelationalParametersParameterAccessor(
new RelationalQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory()),
new Object[] { value }));
PartTree tree = new PartTree(methodName, User.class);
return provider.next(tree.getParts().iterator().next());
}
static class RelationalQueryMethod extends QueryMethod {
public RelationalQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory) {
super(method, metadata, factory);
}
}
interface UserRepository extends Repository<User, String> {
String findByNameStartingWith(String prefix);
String findByNameContains(String substring);
String findByName(String substring);
}
static class User {
String name;
}
}
Loading…
Cancel
Save