#282 - Polishing.

Move query derivation infrastructure to Spring Data Relational. Adapt to newly introduced ValueFunction for deferred value mapping. Use query derivation in integration tests.

Tweak javadoc, add since and author tags, reformat code.

Related ticket: https://jira.spring.io/browse/DATAJDBC-514
Original pull request: #295.
This commit is contained in:
Mark Paluch
2020-03-27 16:17:48 +01:00
parent a9a3919cf1
commit 4e5bf95504
20 changed files with 317 additions and 890 deletions

View File

@@ -34,19 +34,23 @@ import org.springframework.data.r2dbc.dialect.Bindings;
import org.springframework.data.r2dbc.dialect.MutableBindings;
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
import org.springframework.data.r2dbc.mapping.SettableValue;
import org.springframework.data.relational.core.dialect.Escaper;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.query.CriteriaDefinition;
import org.springframework.data.relational.core.query.CriteriaDefinition.Comparator;
import org.springframework.data.relational.core.query.ValueFunction;
import org.springframework.data.relational.core.sql.*;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Pair;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Maps {@link Criteria} and {@link Sort} objects considering mapping metadata and dialect-specific conversion.
* Maps {@link CriteriaDefinition} and {@link Sort} objects considering mapping metadata and dialect-specific
* conversion.
*
* @author Mark Paluch
* @author Roman Chigvintsev
@@ -344,7 +348,13 @@ public class QueryMapper {
mappedValue = convertValue(settableValue.getValue(), propertyField.getTypeHint());
typeHint = getTypeHint(mappedValue, actualType.getType(), settableValue);
} else if (criteria.getValue() instanceof ValueFunction) {
ValueFunction<Object> valueFunction = (ValueFunction<Object>) criteria.getValue();
Object value = valueFunction.apply(getEscaper(criteria.getComparator()));
mappedValue = convertValue(value, propertyField.getTypeHint());
typeHint = actualType.getType();
} else {
mappedValue = convertValue(criteria.getValue(), propertyField.getTypeHint());
@@ -354,6 +364,15 @@ public class QueryMapper {
return createCondition(column, mappedValue, typeHint, bindings, criteria.getComparator(), criteria.isIgnoreCase());
}
private Escaper getEscaper(Comparator comparator) {
if (comparator == Comparator.LIKE || comparator == Comparator.NOT_LIKE) {
return dialect.getLikeEscaper();
}
return Escaper.DEFAULT;
}
/**
* Potentially convert the {@link SettableValue}.
*
@@ -376,6 +395,21 @@ public class QueryMapper {
return null;
}
if (value instanceof Pair) {
Pair<Object, Object> pair = (Pair<Object, Object>) value;
Object first = convertValue(pair.getFirst(),
typeInformation.getActualType() != null ? typeInformation.getRequiredActualType()
: ClassTypeInformation.OBJECT);
Object second = convertValue(pair.getSecond(),
typeInformation.getActualType() != null ? typeInformation.getRequiredActualType()
: ClassTypeInformation.OBJECT);
return Pair.of(first, second);
}
if (value instanceof Iterable) {
List<Object> mapped = new ArrayList<>();
@@ -456,6 +490,19 @@ public class QueryMapper {
return condition;
}
if (comparator == Comparator.BETWEEN || comparator == Comparator.NOT_BETWEEN) {
Pair<Object, Object> pair = (Pair<Object, Object>) mappedValue;
Expression begin = bind(pair.getFirst(), valueType, bindings,
bindings.nextMarker(column.getName().getReference()), ignoreCase);
Expression end = bind(mappedValue, valueType, bindings, bindings.nextMarker(column.getName().getReference()),
ignoreCase);
return comparator == Comparator.BETWEEN ? Conditions.between(columnExpression, begin, end)
: Conditions.notBetween(columnExpression, begin, end);
}
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
switch (comparator) {
@@ -505,6 +552,10 @@ public class QueryMapper {
return entity == null ? new Field(key) : new MetadataBackedField(key, entity, mappingContext);
}
Class<?> getTypeHint(@Nullable Object mappedValue, Class<?> propertyType) {
return propertyType;
}
Class<?> getTypeHint(@Nullable Object mappedValue, Class<?> propertyType, SettableValue settableValue) {
if (mappedValue == null || propertyType.equals(Object.class)) {

View File

@@ -26,7 +26,9 @@ import org.springframework.data.r2dbc.dialect.Bindings;
import org.springframework.data.r2dbc.dialect.MutableBindings;
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
import org.springframework.data.r2dbc.mapping.SettableValue;
import org.springframework.data.relational.core.dialect.Escaper;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.query.ValueFunction;
import org.springframework.data.relational.core.sql.AssignValue;
import org.springframework.data.relational.core.sql.Assignment;
import org.springframework.data.relational.core.sql.Assignments;
@@ -134,6 +136,17 @@ public class UpdateMapper extends QueryMapper {
mappedValue = convertValue(settableValue.getValue(), propertyField.getTypeHint());
typeHint = getTypeHint(mappedValue, actualType.getType(), settableValue);
} else if (value instanceof ValueFunction) {
ValueFunction<Object> valueFunction = (ValueFunction<Object>) value;
mappedValue = convertValue(valueFunction.apply(Escaper.DEFAULT), propertyField.getTypeHint());
if (mappedValue == null) {
return Assignments.value(column, SQL.nullLiteral());
}
typeHint = actualType.getType();
} else {
mappedValue = convertValue(value, propertyField.getTypeHint());

View File

@@ -1,167 +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.r2dbc.repository.query;
import org.springframework.data.r2dbc.query.Criteria;
import org.springframework.data.relational.core.sql.Expression;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.util.Assert;
/**
* Simple factory to contain logic to create {@link Criteria}s from {@link Part}s.
*
* @author Roman Chigvintsev
*/
class CriteriaFactory {
private final ParameterMetadataProvider parameterMetadataProvider;
/**
* Creates new instance of this class with the given {@link ParameterMetadataProvider}.
*
* @param parameterMetadataProvider parameter metadata provider (must not be {@literal null})
*/
CriteriaFactory(ParameterMetadataProvider parameterMetadataProvider) {
Assert.notNull(parameterMetadataProvider, "Parameter metadata provider must not be null!");
this.parameterMetadataProvider = parameterMetadataProvider;
}
/**
* Creates {@link Criteria} for the given {@link Part}.
*
* @param part method name part (must not be {@literal null})
* @return {@link Criteria} instance
* @throws IllegalArgumentException if part type is not supported
*/
public Criteria createCriteria(Part part) {
Part.Type type = part.getType();
String propertyName = part.getProperty().getSegment();
Class<?> propertyType = part.getProperty().getType();
Criteria.CriteriaStep criteriaStep = Criteria.where(propertyName);
if (type == Part.Type.IS_NULL || type == Part.Type.IS_NOT_NULL) {
return part.getType() == Part.Type.IS_NULL ? criteriaStep.isNull() : criteriaStep.isNotNull();
}
if (type == Part.Type.TRUE || type == Part.Type.FALSE) {
return part.getType() == Part.Type.TRUE ? criteriaStep.isTrue() : criteriaStep.isFalse();
}
switch (type) {
case BETWEEN: {
ParameterMetadata geParamMetadata = parameterMetadataProvider.next(part);
ParameterMetadata leParamMetadata = parameterMetadataProvider.next(part);
return criteriaStep.greaterThanOrEquals(geParamMetadata.getValue())
.and(propertyName).lessThanOrEquals(leParamMetadata.getValue());
}
case AFTER:
case GREATER_THAN: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
return criteriaStep.greaterThan(paramMetadata.getValue());
}
case GREATER_THAN_EQUAL: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
return criteriaStep.greaterThanOrEquals(paramMetadata.getValue());
}
case BEFORE:
case LESS_THAN: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
return criteriaStep.lessThan(paramMetadata.getValue());
}
case LESS_THAN_EQUAL: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
return criteriaStep.lessThanOrEquals(paramMetadata.getValue());
}
case IN:
case NOT_IN: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
Criteria criteria = part.getType() == Part.Type.IN
? criteriaStep.in(paramMetadata.getValue())
: criteriaStep.notIn(paramMetadata.getValue());
return criteria.ignoreCase(shouldIgnoreCase(part)
&& checkCanUpperCase(part, part.getProperty().getType()));
}
case STARTING_WITH:
case ENDING_WITH:
case CONTAINING:
case NOT_CONTAINING:
case LIKE:
case NOT_LIKE: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
Criteria criteria = part.getType() == Part.Type.NOT_LIKE || part.getType() == Part.Type.NOT_CONTAINING
? criteriaStep.notLike(paramMetadata.getValue())
: criteriaStep.like(paramMetadata.getValue());
return criteria.ignoreCase(shouldIgnoreCase(part)
&& checkCanUpperCase(part, propertyType, paramMetadata.getType()));
}
case SIMPLE_PROPERTY: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
if (paramMetadata.getValue() == null) {
return criteriaStep.isNull();
}
return criteriaStep.is(paramMetadata.getValue()).ignoreCase(shouldIgnoreCase(part)
&& checkCanUpperCase(part, propertyType, paramMetadata.getType()));
}
case NEGATING_SIMPLE_PROPERTY: {
ParameterMetadata paramMetadata = parameterMetadataProvider.next(part);
return criteriaStep.not(paramMetadata.getValue()).ignoreCase(shouldIgnoreCase(part)
&& checkCanUpperCase(part, propertyType, paramMetadata.getType()));
}
default:
throw new IllegalArgumentException("Unsupported keyword " + type);
}
}
/**
* Checks whether comparison should be done in case-insensitive way.
*
* @param part method name part (must not be {@literal null})
* @return {@literal true} if comparison should be done in case-insensitive way
*/
private boolean shouldIgnoreCase(Part part) {
return part.shouldIgnoreCase() == Part.IgnoreCaseType.ALWAYS
|| part.shouldIgnoreCase() == Part.IgnoreCaseType.WHEN_POSSIBLE;
}
/**
* Checks whether "upper-case" conversion can be applied to the given {@link Expression}s in case the underlying
* {@link Part} requires ignoring case.
*
* @param part method name part (must not be {@literal null})
* @param expressionTypes types of the given expressions (must not be {@literal null} or empty)
* @throws IllegalStateException if {@link Part} requires ignoring case but "upper-case" conversion cannot be
* applied to at least one of the given {@link Expression}s
*/
private boolean checkCanUpperCase(Part part, Class<?>... expressionTypes) {
Assert.notEmpty(expressionTypes, "Expression types must not be null or empty");
boolean strict = part.shouldIgnoreCase() == Part.IgnoreCaseType.ALWAYS;
for (Class<?> expressionType : expressionTypes) {
if (!canUpperCase(expressionType)) {
if (strict) {
throw new IllegalStateException("Unable to ignore case of " + expressionType.getName()
+ " type, the property '" + part.getProperty().getSegment() + "' must reference a string");
}
return false;
}
}
return true;
}
private boolean canUpperCase(Class<?> expressionType) {
return expressionType == String.class;
}
}

View File

@@ -1,71 +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.r2dbc.repository.query;
import java.util.Arrays;
import java.util.List;
import org.springframework.lang.Nullable;
/**
* Helper class encapsulating an escape character for LIKE queries and the actually usage of it in escaping
* {@link String}s.
* <p>
* This class is an adapted version of {@code org.springframework.data.jpa.repository.query.EscapeCharacter} from
* Spring Data JPA project.
*
* @author Roman Chigvintsev
*/
public class LikeEscaper {
public static final LikeEscaper DEFAULT = LikeEscaper.of('\\');
private final char escapeCharacter;
private final List<String> toReplace;
private LikeEscaper(char escapeCharacter) {
if (escapeCharacter == '_' || escapeCharacter == '%') {
throw new IllegalArgumentException("'_' and '%' are special characters and cannot be used as "
+ "escape character");
}
this.escapeCharacter = escapeCharacter;
this.toReplace = Arrays.asList(String.valueOf(escapeCharacter), "_", "%");
}
/**
* Creates new instance of this class with the given escape character.
*
* @param escapeCharacter escape character
* @return new instance of {@link LikeEscaper}
* @throws IllegalArgumentException if escape character is one of special characters ('_' and '%')
*/
public static LikeEscaper of(char escapeCharacter) {
return new LikeEscaper(escapeCharacter);
}
/**
* Escapes all special like characters ({@code _}, {@code %}) using the configured escape character.
*
* @param value value to be escaped
* @return escaped value
*/
@Nullable
public String escape(@Nullable String value) {
if (value == null) {
return null;
}
return toReplace.stream().reduce(value, (it, character) -> it.replace(character, escapeCharacter + character));
}
}

View File

@@ -1,48 +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.r2dbc.repository.query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Helper class for holding information about query parameter.
*/
class ParameterMetadata {
private final String name;
@Nullable private final Object value;
private final Class<?> type;
public ParameterMetadata(String name, @Nullable Object value, Class<?> type) {
Assert.notNull(type, "Parameter type must not be null");
this.name = name;
this.value = value;
this.type = type;
}
public String getName() {
return name;
}
@Nullable
public Object getValue() {
return value;
}
public Class<?> getType() {
return type;
}
}

View File

@@ -1,158 +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.r2dbc.repository.query;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.lang.Nullable;
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
*/
class ParameterMetadataProvider implements Iterable<ParameterMetadata> {
private static final Object VALUE_PLACEHOLDER = new Object();
private final Iterator<? extends Parameter> bindableParameterIterator;
@Nullable private final Iterator<Object> bindableParameterValueIterator;
private final List<ParameterMetadata> parameterMetadata = new ArrayList<>();
private final LikeEscaper likeEscaper;
/**
* Creates new instance of this class with the given {@link RelationalParameterAccessor} and {@link LikeEscaper}.
*
* @param accessor relational parameter accessor (must not be {@literal null}).
* @param likeEscaper escaper for LIKE operator parameters (must not be {@literal null})
*/
ParameterMetadataProvider(RelationalParameterAccessor accessor, LikeEscaper likeEscaper) {
this(accessor.getBindableParameters(), accessor.iterator(), likeEscaper);
}
/**
* Creates new instance of this class with the given {@link Parameters} and {@link LikeEscaper}.
*
* @param parameters method parameters (must not be {@literal null})
* @param likeEscaper escaper for LIKE operator parameters (must not be {@literal null})
*/
ParameterMetadataProvider(Parameters<?, ?> parameters, LikeEscaper likeEscaper) {
this(parameters, null, likeEscaper);
}
/**
* Creates new instance of this class with the given {@link Parameters}, {@link Iterator} over all bindable
* parameter values and {@link LikeEscaper}.
*
* @param bindableParameterValueIterator iterator over bindable parameter values
* @param parameters method parameters (must not be {@literal null})
* @param likeEscaper escaper for LIKE operator parameters (must not be {@literal null})
*/
private ParameterMetadataProvider(Parameters<?, ?> parameters,
@Nullable Iterator<Object> bindableParameterValueIterator, LikeEscaper likeEscaper) {
Assert.notNull(parameters, "Parameters must not be null!");
Assert.notNull(likeEscaper, "Like escaper must not be null!");
this.bindableParameterIterator = parameters.getBindableParameters().iterator();
this.bindableParameterValueIterator = bindableParameterValueIterator;
this.likeEscaper = likeEscaper;
}
@NotNull
@Override
public Iterator<ParameterMetadata> iterator() {
return parameterMetadata.iterator();
}
/**
* 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();
Part.Type partType = part.getType();
checkNullIsAllowed(parameterName, parameterValue, partType);
Class<?> parameterType = parameter.getType();
Object preparedParameterValue = prepareParameterValue(parameterValue, parameterType, partType);
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"));
}
return defaultName;
}
@Nullable
private Object getParameterValue() {
return bindableParameterValueIterator == null ? VALUE_PLACEHOLDER : bindableParameterValueIterator.next();
}
/**
* Checks whether {@literal null} is allowed as parameter value.
*
* @param parameterName parameter name
* @param parameterValue parameter value
* @param partType method name part type (must not be {@literal null})
* @throws IllegalArgumentException if {@literal null} is not allowed as parameter value
*/
private void checkNullIsAllowed(String parameterName, @Nullable Object parameterValue, Part.Type partType) {
if (parameterValue == null && !Part.Type.SIMPLE_PROPERTY.equals(partType)) {
String message = String.format("Value of parameter with name %s must not be null!", parameterName);
throw new IllegalArgumentException(message);
}
}
/**
* Prepares parameter value before it's actually bound to the query.
*
* @param value must not be {@literal null}
* @return prepared query parameter value
*/
@Nullable
protected Object prepareParameterValue(@Nullable Object value, Class<?> valueType, Part.Type partType) {
if (value != null && String.class == valueType) {
switch (partType) {
case STARTING_WITH:
return likeEscaper.escape(value.toString()) + "%";
case ENDING_WITH:
return "%" + likeEscaper.escape(value.toString());
case CONTAINING:
case NOT_CONTAINING:
return "%" + likeEscaper.escape(value.toString()) + "%";
}
}
return value;
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.data.r2dbc.repository.query;
import java.util.Collection;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.r2dbc.core.DatabaseClient;
@@ -25,33 +23,28 @@ import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalParameters;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.util.Streamable;
/**
* An {@link AbstractR2dbcQuery} implementation based on a {@link PartTree}.
* <p>
* This class is an adapted version of {@code org.springframework.data.jpa.repository.query.PartTreeJpaQuery} from
* Spring Data JPA project.
*
* @author Roman Chigvintsev
* @since 1.1
*/
public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
private final ReactiveDataAccessStrategy dataAccessStrategy;
private final RelationalParameters parameters;
private final PartTree tree;
private LikeEscaper likeEscaper = LikeEscaper.DEFAULT;
/**
* Creates new instance of this class with the given {@link R2dbcQueryMethod}, {@link DatabaseClient},
* {@link R2dbcConverter} and {@link ReactiveDataAccessStrategy}.
*
* @param method query method (must not be {@literal null})
* @param databaseClient database client (must not be {@literal null})
* @param converter converter (must not be {@literal null})
* @param dataAccessStrategy data access strategy (must not be {@literal null})
* @param method query method, must not be {@literal null}.
* @param databaseClient database client, must not be {@literal null}.
* @param converter converter, must not be {@literal null}.
* @param dataAccessStrategy data access strategy, must not be {@literal null}.
*/
public PartTreeR2dbcQuery(R2dbcQueryMethod method, DatabaseClient databaseClient, R2dbcConverter converter,
ReactiveDataAccessStrategy dataAccessStrategy) {
@@ -61,86 +54,24 @@ public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
try {
this.tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType());
validate(this.tree, this.parameters, method.getName());
} catch (Exception e) {
String message = String.format("Failed to create query for method %s! %s", method, e.getMessage());
throw new IllegalArgumentException(message, e);
R2dbcQueryCreator.validate(this.tree, this.parameters);
} catch (RuntimeException e) {
throw new IllegalArgumentException(
String.format("Failed to create query for method %s! %s", method, e.getMessage()), e);
}
}
public void setLikeEscaper(LikeEscaper likeEscaper) {
this.likeEscaper = likeEscaper;
}
/**
* Creates new {@link BindableQuery} for the given {@link RelationalParameterAccessor}.
*
* @param accessor query parameter accessor (must not be {@literal null})
* @return new instance of {@link BindableQuery}
*/
@Override
protected BindableQuery createQuery(RelationalParameterAccessor accessor) {
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
ParameterMetadataProvider parameterMetadataProvider = new ParameterMetadataProvider(accessor, likeEscaper);
R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata,
parameterMetadataProvider);
R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor);
PreparedOperation<?> preparedQuery = queryCreator.createQuery(getDynamicSort(accessor));
return new PreparedOperationBindableQuery(preparedQuery);
}
private Sort getDynamicSort(RelationalParameterAccessor accessor) {
return parameters.potentiallySortsDynamically() ? accessor.getSort() : Sort.unsorted();
}
private static void validate(PartTree tree, RelationalParameters parameters, String methodName) {
int argCount = 0;
Iterable<Part> parts = () -> tree.stream().flatMap(Streamable::stream).iterator();
for (Part part : parts) {
int numberOfArguments = part.getNumberOfArguments();
for (int i = 0; i < numberOfArguments; i++) {
throwExceptionOnArgumentMismatch(methodName, part, parameters, argCount);
argCount++;
}
}
}
private static void throwExceptionOnArgumentMismatch(String methodName, Part part, RelationalParameters parameters,
int index) {
Part.Type type = part.getType();
String property = part.getProperty().toDotPath();
if (!parameters.getBindableParameters().hasParameterAt(index)) {
String msgTemplate = "Method %s expects at least %d arguments but only found %d. "
+ "This leaves an operator of type %s for property %s unbound.";
String formattedMsg = String.format(msgTemplate, methodName, index + 1, index, type.name(), property);
throw new IllegalStateException(formattedMsg);
}
RelationalParameters.RelationalParameter parameter = parameters.getBindableParameter(index);
if (expectsCollection(type) && !parameterIsCollectionLike(parameter)) {
String message = wrongParameterTypeMessage(methodName, property, type, "Collection", parameter);
throw new IllegalStateException(message);
} else if (!expectsCollection(type) && !parameterIsScalarLike(parameter)) {
String message = wrongParameterTypeMessage(methodName, property, type, "scalar", parameter);
throw new IllegalStateException(message);
}
}
private static boolean expectsCollection(Part.Type type) {
return type == Part.Type.IN || type == Part.Type.NOT_IN;
}
private static boolean parameterIsCollectionLike(RelationalParameters.RelationalParameter parameter) {
return parameter.getType().isArray() || Collection.class.isAssignableFrom(parameter.getType());
}
private static boolean parameterIsScalarLike(RelationalParameters.RelationalParameter parameter) {
return !Collection.class.isAssignableFrom(parameter.getType());
}
private static String wrongParameterTypeMessage(String methodName, String property, Part.Type operatorType,
String expectedArgumentType, RelationalParameters.RelationalParameter parameter) {
return String.format("Operator %s on %s requires a %s argument, found %s in method %s.", operatorType.name(),
property, expectedArgumentType, parameter.getType(), methodName);
}
}

View File

@@ -26,15 +26,18 @@ import org.springframework.util.Assert;
* @author Roman Chigvintsev
*/
class PreparedOperationBindableQuery implements BindableQuery {
private final PreparedOperation<?> preparedQuery;
/**
* Creates new instance of this class with the given {@link PreparedOperation}.
*
* @param preparedQuery prepared SQL query (must not be {@literal null})
* @param preparedQuery prepared SQL query, must not be {@literal null}.
*/
PreparedOperationBindableQuery(PreparedOperation<?> preparedQuery) {
Assert.notNull(preparedQuery, "Prepared query must not be null!");
this.preparedQuery = preparedQuery;
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.data.r2dbc.repository.query;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
@@ -23,13 +22,14 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.core.PreparedOperation;
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.core.StatementMapper;
import org.springframework.data.r2dbc.query.Criteria;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalQueryCreator;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.Assert;
@@ -37,82 +37,46 @@ import org.springframework.util.Assert;
* Implementation of {@link AbstractQueryCreator} that creates {@link PreparedOperation} from a {@link PartTree}.
*
* @author Roman Chigvintsev
* @author Mark Paluch
* @since 1.1
*/
public class R2dbcQueryCreator extends AbstractQueryCreator<PreparedOperation<?>, Criteria> {
public class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<?>> {
private final PartTree tree;
private final ReactiveDataAccessStrategy dataAccessStrategy;
private final RelationalEntityMetadata<?> entityMetadata;
private final CriteriaFactory criteriaFactory;
/**
* Creates new instance of this class with the given {@link PartTree}, {@link ReactiveDataAccessStrategy},
* {@link RelationalEntityMetadata} and {@link ParameterMetadataProvider}.
* {@link RelationalEntityMetadata} and {@link RelationalParameterAccessor}.
*
* @param tree part tree (must not be {@literal null})
* @param dataAccessStrategy data access strategy (must not be {@literal null})
* @param entityMetadata relational entity metadata (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 dataAccessStrategy data access strategy, must not be {@literal null}.
* @param entityMetadata relational entity metadata, must not be {@literal null}.
* @param accessor parameter metadata provider, must not be {@literal null}.
*/
public R2dbcQueryCreator(PartTree tree, ReactiveDataAccessStrategy dataAccessStrategy,
RelationalEntityMetadata<?> entityMetadata, ParameterMetadataProvider parameterMetadataProvider) {
super(tree);
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor) {
super(tree, accessor);
this.tree = tree;
Assert.notNull(dataAccessStrategy, "Data access strategy must not be null");
Assert.notNull(entityMetadata, "Relational entity metadata must not be null");
Assert.notNull(parameterMetadataProvider, "Parameter metadata provider must not be null");
this.dataAccessStrategy = dataAccessStrategy;
this.entityMetadata = entityMetadata;
this.criteriaFactory = new CriteriaFactory(parameterMetadataProvider);
}
/**
* Creates {@link Criteria} for the given method name part.
*
* @param part method name part (must not be {@literal null})
* @param iterator iterator over query parameter values
* @return new instance of {@link Criteria}
*/
@Override
protected Criteria create(Part part, Iterator<Object> iterator) {
return criteriaFactory.createCriteria(part);
}
/**
* 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 iterator iterator over query parameter values
* @return {@link Criteria} combination
*/
@Override
protected Criteria and(Part part, Criteria base, Iterator<Object> iterator) {
return base.and(criteriaFactory.createCriteria(part));
}
/**
* 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})
* @return {@link Criteria} combination
*/
@Override
protected Criteria or(Criteria base, Criteria criteria) {
return base.or(criteria);
}
/**
* Creates {@link PreparedOperation} applying the given {@link Criteria} and {@link Sort} definition.
*
* @param criteria {@link Criteria} to be applied to query
* @param sort sort option to be applied to query (must not be {@literal null})
* @param sort sort option to be applied to query, must not be {@literal null}.
* @return instance of {@link PreparedOperation}
*/
@Override
protected PreparedOperation<?> complete(Criteria criteria, Sort sort) {
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityMetadata.getJavaType());
StatementMapper.SelectSpec selectSpec = statementMapper.createSelect(entityMetadata.getTableName())
.withProjection(getSelectProjection());
@@ -135,26 +99,27 @@ public class R2dbcQueryCreator extends AbstractQueryCreator<PreparedOperation<?>
}
private SqlIdentifier[] getSelectProjection() {
List<SqlIdentifier> columnNames;
if (tree.isExistsProjection()) {
columnNames = dataAccessStrategy.getIdentifierColumns(entityMetadata.getJavaType());
} else {
columnNames = dataAccessStrategy.getAllColumns(entityMetadata.getJavaType());
}
return columnNames.toArray(new SqlIdentifier[0]);
}
private Sort getSort(Sort sort) {
RelationalPersistentEntity<?> tableEntity = entityMetadata.getTableEntity();
List<Sort.Order> orders = sort.get().map(order -> {
RelationalPersistentProperty property = tableEntity.getRequiredPersistentProperty(order.getProperty());
String columnName = dataAccessStrategy.toSql(property.getColumnName());
String orderProperty = entityMetadata.getTableName() + "." + columnName;
// TODO: org.springframework.data.relational.core.sql.render.OrderByClauseVisitor from
// spring-data-relational does not prepend column name with table name. It makes sense to render
// column names uniformly.
return order.isAscending() ? Sort.Order.asc(orderProperty) : Sort.Order.desc(orderProperty);
return order.isAscending() ? Sort.Order.asc(property.getName()) : Sort.Order.desc(property.getName());
}).collect(Collectors.toList());
return Sort.by(orders);
}
}

View File

@@ -43,6 +43,7 @@ import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.annotation.Id;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;

View File

@@ -22,7 +22,7 @@ import java.util.Arrays;
import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
import org.springframework.data.r2dbc.query.Criteria.*;
import org.springframework.data.relational.core.sql.SqlIdentifier;
/**
@@ -72,7 +72,6 @@ public class CriteriaUnitTests {
assertThat(Criteria.from(empty, notEmpty).isEmpty()).isFalse();
assertThat(Criteria.from(notEmpty, empty).isEmpty()).isFalse();
});
}
@@ -272,16 +271,18 @@ public class CriteriaUnitTests {
assertThat(criteria.getComparator()).isEqualTo(Comparator.IS_NOT_NULL);
}
@Test
@Test // gh-282
public void shouldBuildIsTrueCriteria() {
Criteria criteria = where("foo").isTrue();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));
assertThat(criteria.getComparator()).isEqualTo(Comparator.IS_TRUE);
}
@Test
@Test // gh-282
public void shouldBuildIsFalseCriteria() {
Criteria criteria = where("foo").isFalse();
assertThat(criteria.getColumn()).isEqualTo(SqlIdentifier.unquoted("foo"));

View File

@@ -130,7 +130,7 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
shouldInsertNewItems();
repository.findByNameContains("%F%") //
repository.findByNameContains("F") //
.map(LegoSet::getName) //
.collectList() //
.as(StepVerifier::create) //

View File

@@ -114,10 +114,6 @@ public class H2R2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositoryIn
interface H2LegoSetRepository extends LegoSetRepository {
@Override
@Query("SELECT * FROM legoset WHERE name like $1")
Flux<LegoSet> findByNameContains(String name);
@Override
@Query("SELECT name FROM legoset")
Flux<Named> findAsProjection();

View File

@@ -82,10 +82,6 @@ public class JasyncMySqlR2dbcRepositoryIntegrationTests extends AbstractR2dbcRep
interface MySqlLegoSetRepository extends LegoSetRepository {
@Override
@Query("SELECT * FROM legoset WHERE name like ?")
Flux<LegoSet> findByNameContains(String name);
@Override
@Query("SELECT name FROM legoset")
Flux<Named> findAsProjection();

View File

@@ -81,10 +81,6 @@ public class MariaDbR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposit
interface MySqlLegoSetRepository extends LegoSetRepository {
@Override
@Query("SELECT * FROM legoset WHERE name like ?")
Flux<LegoSet> findByNameContains(String name);
@Override
@Query("SELECT name FROM legoset")
Flux<Named> findAsProjection();

View File

@@ -81,10 +81,6 @@ public class MySqlR2dbcRepositoryIntegrationTests extends AbstractR2dbcRepositor
interface MySqlLegoSetRepository extends LegoSetRepository {
@Override
@Query("SELECT * FROM legoset WHERE name like ?")
Flux<LegoSet> findByNameContains(String name);
@Override
@Query("SELECT name FROM legoset")
Flux<Named> findAsProjection();

View File

@@ -81,10 +81,6 @@ public class PostgresR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposi
interface PostgresLegoSetRepository extends LegoSetRepository {
@Override
@Query("SELECT * FROM legoset WHERE name like $1")
Flux<LegoSet> findByNameContains(String name);
@Override
@Query("SELECT name FROM legoset")
Flux<Named> findAsProjection();

View File

@@ -1,71 +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.r2dbc.repository.query;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.Test;
/**
* @author Roman Chigvintsev
*/
public class LikeEscaperUnitTests {
@Test
public void ignoresNulls() {
assertNull(LikeEscaper.DEFAULT.escape(null));
}
@Test
public void ignoresEmptyString() {
assertThat(LikeEscaper.DEFAULT.escape("")).isEqualTo("");
}
@Test
public void ignoresBlankString() {
assertThat(LikeEscaper.DEFAULT.escape(" ")).isEqualTo(" ");
}
@Test(expected = IllegalArgumentException.class)
public void throwsExceptionWhenEscapeCharacterIsUnderscore() {
LikeEscaper.of('_');
}
@Test(expected = IllegalArgumentException.class)
public void throwsExceptionWhenEscapeCharacterIsPercent() {
LikeEscaper.of('%');
}
@Test
public void escapesUnderscoresUsingDefaultEscapeCharacter() {
assertThat(LikeEscaper.DEFAULT.escape("_test_")).isEqualTo("\\_test\\_");
}
@Test
public void escapesPercentsUsingDefaultEscapeCharacter() {
assertThat(LikeEscaper.DEFAULT.escape("%test%")).isEqualTo("\\%test\\%");
}
@Test
public void escapesSpecialCharactersUsingCustomEscapeCharacter() {
assertThat(LikeEscaper.of('$').escape("_%")).isEqualTo("$_$%");
}
@Test
public void doublesEscapeCharacter() {
assertThat(LikeEscaper.DEFAULT.escape("\\")).isEqualTo("\\\\");
}
}

View File

@@ -20,6 +20,7 @@ import static org.mockito.Mockito.*;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryMetadata;
import lombok.Data;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -29,12 +30,11 @@ import java.util.Collections;
import java.util.Date;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
@@ -51,29 +51,28 @@ import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
/**
* Unit tests for {@link PartTreeR2dbcQuery}.
*
* @author Roman Chigvintsev
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class PartTreeR2dbcQueryIntegrationTests {
public class PartTreeR2dbcQueryUnitTests {
private static final String TABLE = "users";
private static final String ALL_FIELDS = TABLE + ".id, "
+ TABLE + ".first_name, "
+ TABLE + ".last_name, "
+ TABLE + ".date_of_birth, "
+ TABLE + ".age, "
+ TABLE + ".active";
private static final String ALL_FIELDS = TABLE + ".id, " + TABLE + ".first_name, " + TABLE + ".last_name, " + TABLE
+ ".date_of_birth, " + TABLE + ".age, " + TABLE + ".active";
@Mock private ConnectionFactory connectionFactory;
@Mock private R2dbcConverter r2dbcConverter;
@Mock ConnectionFactory connectionFactory;
@Mock R2dbcConverter r2dbcConverter;
@Rule public ExpectedException thrown = ExpectedException.none();
private RelationalMappingContext mappingContext;
private ReactiveDataAccessStrategy dataAccessStrategy;
private DatabaseClient databaseClient;
RelationalMappingContext mappingContext;
ReactiveDataAccessStrategy dataAccessStrategy;
DatabaseClient databaseClient;
@Before
public void setUp() {
ConnectionFactoryMetadata metadataMock = mock(ConnectionFactoryMetadata.class);
when(metadataMock.getName()).thenReturn("PostgreSQL");
when(connectionFactory.getMetadata()).thenReturn(metadataMock);
@@ -90,195 +89,225 @@ public class PartTreeR2dbcQueryIntegrationTests {
.dataAccessStrategy(dataAccessStrategy).build();
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByStringAttribute() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" }));
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
}
@Test
@Test // gh-282
public void createsQueryWithIsNullCondition() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = r2dbcQuery.createQuery((getAccessor(queryMethod, new Object[] { null })));
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name IS NULL";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name IS NULL");
}
@Test
@Test // gh-282
public void createsQueryWithLimitForExistsProjection() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("existsByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
BindableQuery query = r2dbcQuery.createQuery((getAccessor(queryMethod, new Object[] { "John" })));
String expectedSql = "SELECT " + TABLE + ".id FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 1";
assertThat(query.get()).isEqualTo(expectedSql);
assertThat(query.get())
.isEqualTo("SELECT " + TABLE + ".id FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 1");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByTwoStringAttributes() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameAndFirstName", String.class, String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "Doe", "John" }));
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE
+ " WHERE " + TABLE + ".last_name = $1 AND (" + TABLE + ".first_name = $2)";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE
+ ".last_name = $1 AND (" + TABLE + ".first_name = $2)");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByOneOfTwoStringAttributes() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameOrFirstName", String.class, String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "Doe", "John" }));
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE
+ " WHERE " + TABLE + ".last_name = $1 OR (" + TABLE + ".first_name = $2)";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE
+ ".last_name = $1 OR (" + TABLE + ".first_name = $2)");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByDateAttributeBetween() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthBetween", Date.class, Date.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod,
new Object[] { new Date(), new Date() });
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date(), new Date() });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE
+ " WHERE " + TABLE + ".date_of_birth >= $1 AND " + TABLE + ".date_of_birth <= $2";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth BETWEEN $1 AND $2");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByIntegerAttributeLessThan() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeLessThan", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age < $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age < $1");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByIntegerAttributeLessThanEqual() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeLessThanEqual", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age <= $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age <= $1");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByIntegerAttributeGreaterThan() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeGreaterThan", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age > $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age > $1");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByIntegerAttributeGreaterThanEqual() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeGreaterThanEqual", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age >= $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age >= $1");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByDateAttributeAfter() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthAfter", Date.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth > $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth > $1");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByDateAttributeBefore() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByDateOfBirthBefore", Date.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth < $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth < $1");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByIntegerAttributeIsNull() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIsNull");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IS NULL";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IS NULL");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByIntegerAttributeIsNotNull() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIsNotNull");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IS NOT NULL";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IS NOT NULL");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByStringAttributeLike() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameLike", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByStringAttributeNotLike() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotLike", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name NOT LIKE $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name NOT LIKE $1");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByStringAttributeStartingWith() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameStartingWith", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
@Test // gh-282
public void appendsLikeOperatorParameterWithPercentSymbolForStartingWithQuery() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameStartingWith", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
@@ -286,23 +315,27 @@ public class PartTreeR2dbcQueryIntegrationTests {
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
bindableQuery.bind(bindSpecMock);
verify(bindSpecMock, times(1)).bind(0, "Jo%");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByStringAttributeEndingWith() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameEndingWith", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
@Test // gh-282
public void prependsLikeOperatorParameterWithPercentSymbolForEndingWithQuery() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameEndingWith", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
@@ -310,23 +343,27 @@ public class PartTreeR2dbcQueryIntegrationTests {
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
bindableQuery.bind(bindSpecMock);
verify(bindSpecMock, times(1)).bind(0, "%hn");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByStringAttributeContaining() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameContaining", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name LIKE $1");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
@Test // gh-282
public void wrapsLikeOperatorParameterWithPercentSymbolsForContainingQuery() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameContaining", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
@@ -334,24 +371,27 @@ public class PartTreeR2dbcQueryIntegrationTests {
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
bindableQuery.bind(bindSpecMock);
verify(bindSpecMock, times(1)).bind(0, "%oh%");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByStringAttributeNotContaining() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotContaining", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE
+ " WHERE " + TABLE + ".first_name NOT LIKE $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name NOT LIKE $1");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
@Test // gh-282
public void wrapsLikeOperatorParameterWithPercentSymbolsForNotContainingQuery() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameNotContaining", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
@@ -359,10 +399,11 @@ public class PartTreeR2dbcQueryIntegrationTests {
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
bindableQuery.bind(bindSpecMock);
verify(bindSpecMock, times(1)).bind(0, "%oh%");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByIntegerAttributeWithDescendingOrderingByStringAttribute()
throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeOrderByLastNameDesc", Integer.class);
@@ -370,48 +411,50 @@ public class PartTreeR2dbcQueryIntegrationTests {
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE
+ " WHERE " + TABLE + ".age = $1 ORDER BY users.last_name DESC";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age = $1 ORDER BY last_name DESC");
}
@Test
public void createsQueryToFindAllEntitiesByIntegerAttributeWithAscendingOrderingByStringAttribute()
throws Exception {
@Test // gh-282
public void createsQueryToFindAllEntitiesByIntegerAttributeWithAscendingOrderingByStringAttribute() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeOrderByLastNameAsc", Integer.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE
+ " WHERE " + TABLE + ".age = $1 ORDER BY users.last_name ASC";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age = $1 ORDER BY last_name ASC");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByStringAttributeNot() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByLastNameNot", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Doe" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".last_name != $1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".last_name != $1");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByIntegerAttributeIn() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeIn", Collection.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod,
new Object[] { Collections.singleton(25) });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IN ($1)";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age IN ($1)");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByIntegerAttributeNotIn() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByAgeNotIn", Collection.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
@@ -419,118 +462,120 @@ public class PartTreeR2dbcQueryIntegrationTests {
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod,
new Object[] { Collections.singleton(25) });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age NOT IN ($1)";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".age NOT IN ($1)");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByBooleanAttributeTrue() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByActiveTrue");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".active = TRUE";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".active = TRUE");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByBooleanAttributeFalse() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByActiveFalse");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]);
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".active = FALSE";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".active = FALSE");
}
@Test
@Test // gh-282
public void createsQueryToFindAllEntitiesByStringAttributeIgnoringCase() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstNameIgnoreCase", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE
+ " WHERE UPPER(" + TABLE + ".first_name) = UPPER($1)";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE UPPER(" + TABLE + ".first_name) = UPPER($1)");
}
@Test
@Test // gh-282
public void throwsExceptionWhenIgnoringCaseIsImpossible() throws Exception {
thrown.expect(IllegalStateException.class);
thrown.expectMessage("Unable to ignore case of java.lang.Long type, "
+ "the property 'id' must reference a string");
R2dbcQueryMethod queryMethod = getQueryMethod("findByIdIgnoringCase", Long.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { 1L }));
assertThatIllegalStateException()
.isThrownBy(() -> r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { 1L })));
}
@Test
@Test // gh-282
public void throwsExceptionWhenInPredicateHasNonIterableParameter() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Operator IN on id requires a Collection argument, "
+ "found class java.lang.Long in method findAllByIdIn.");
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByIdIn", Long.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { 1L }));
assertThatIllegalArgumentException()
.isThrownBy(() -> new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy));
}
@Test
@Test // gh-282
public void throwsExceptionWhenSimplePropertyPredicateHasIterableParameter() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Operator SIMPLE_PROPERTY on id requires a scalar argument, "
+ "found interface java.util.Collection in method findAllById.");
R2dbcQueryMethod queryMethod = getQueryMethod("findAllById", Collection.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { Collections.singleton(1L) }));
assertThatIllegalArgumentException()
.isThrownBy(() -> new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter, dataAccessStrategy));
}
@Test
@Test // gh-282
public void throwsExceptionWhenConditionKeywordIsUnsupported() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Unsupported keyword IS_EMPTY");
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByIdIsEmpty");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[0]));
assertThatIllegalArgumentException()
.isThrownBy(() -> r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[0])));
}
@Test
@Test // gh-282
public void throwsExceptionWhenInvalidNumberOfParameterIsGiven() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Invalid number of parameters given!");
R2dbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[0]));
assertThatIllegalArgumentException()
.isThrownBy(() -> r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[0])));
}
@Test
@Test // gh-282
public void createsQueryWithLimitToFindEntitiesByStringAttribute() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findTop3ByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE
+ " WHERE " + TABLE + ".first_name = $1 LIMIT 3";
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 3";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
}
@Test
@Test // gh-282
public void createsQueryToFindFirstEntityByStringAttribute() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findFirstByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" });
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE
+ " WHERE " + TABLE + ".first_name = $1 LIMIT 1";
String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1 LIMIT 1";
assertThat(bindableQuery.get()).isEqualTo(expectedSql);
}
@@ -544,7 +589,8 @@ public class PartTreeR2dbcQueryIntegrationTests {
return new RelationalParametersParameterAccessor(queryMethod, values);
}
private interface UserRepository extends Repository<User, Long> {
interface UserRepository extends Repository<User, Long> {
Flux<User> findAllByFirstName(String firstName);
Flux<User> findAllByLastNameAndFirstName(String lastName, String firstName);
@@ -613,60 +659,14 @@ public class PartTreeR2dbcQueryIntegrationTests {
}
@Table("users")
@Data
private static class User {
@Id private Long id;
private @Id Long id;
private String firstName;
private String lastName;
private Date dateOfBirth;
private Integer age;
private Boolean active;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
}
}

View File

@@ -32,16 +32,13 @@ import org.springframework.data.r2dbc.core.PreparedOperation;
@RunWith(MockitoJUnitRunner.class)
@Ignore
public class PreparedOperationBindableQueryUnitTests {
@Mock private PreparedOperation<?> preparedOperation;
@Test(expected = IllegalArgumentException.class)
public void throwsExceptionWhenPreparedOperationIsNull() {
new PreparedOperationBindableQuery(null);
}
@Mock PreparedOperation<?> preparedOperation;
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
@Test // gh-282
public void bindsQueryParameterValues() {
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
PreparedOperationBindableQuery query = new PreparedOperationBindableQuery(preparedOperation);
@@ -49,12 +46,12 @@ public class PreparedOperationBindableQueryUnitTests {
verify(preparedOperation, times(1)).bindTo(any());
}
@Test
@Test // gh-282
public void returnsSqlQuery() {
String sql = "SELECT * FROM test";
when(preparedOperation.get()).thenReturn(sql);
when(preparedOperation.get()).thenReturn("SELECT * FROM test");
PreparedOperationBindableQuery query = new PreparedOperationBindableQuery(preparedOperation);
assertThat(query.get()).isEqualTo(sql);
assertThat(query.get()).isEqualTo("SELECT * FROM test");
}
}