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.
This commit is contained in:
@@ -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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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()
|
||||
|
||||
@@ -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 {
|
||||
this.ignoreCase = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Static factory method to create an empty Criteria.
|
||||
*
|
||||
@@ -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 {
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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 {
|
||||
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}.
|
||||
*
|
||||
|
||||
@@ -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 {
|
||||
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() {}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
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();
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
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 {
|
||||
|
||||
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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
*
|
||||
* @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> {
|
||||
* 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> {
|
||||
|
||||
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> {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
* 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,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* 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,
|
||||
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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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() {
|
||||
|
||||
|
||||
@@ -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) { //
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user