#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:
@@ -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)) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -130,7 +130,7 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
|
||||
|
||||
shouldInsertNewItems();
|
||||
|
||||
repository.findByNameContains("%F%") //
|
||||
repository.findByNameContains("F") //
|
||||
.map(LegoSet::getName) //
|
||||
.collectList() //
|
||||
.as(StepVerifier::create) //
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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("\\\\");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user