DATACASS-117 - Support named and expression parameters in String-based repository query methods.

We now support named and expression parameters in String-based repository query methods. Name-based parameters are referenced with :parameter. Expression parameters can reference either parameter names (if provided) with :#{expression}/#{#fieldname} or index-based with ?#{[0]}.

String-based query creation now also serializes parameters using the configured CodecRegistry so escaping and serialization is handled by the driver itself which leads to correct queries.

public interface SampleRepository extends Repository<Person, String> {

  @Query("SELECT * FROM person WHERE lastname = ?0;")
  Person findByLastname(String lastname);

  @Query("SELECT * FROM person WHERE lastname = :lastname;")
  Person findByNamedParameter(@Param("lastname") String lastname);

  @Query("SELECT * FROM person WHERE lastname = ?#{[0]};")
  Person findByIndexExpressionParameter(String lastname);

  @Query("SELECT * FROM person WHERE lastnames IN (?0) AND age = ?1;")
  Person findByLastNamesAndAge(Collection<String> lastname, int age);

  @Query("SELECT * FROM person WHERE lastname = :#{#lastname == 'Matthews' ? 'Admin' : #lastname};")
  Person findByConditionalExpressionParameter(@Param("lastname") String lastname);
}

Related tickets: DATACASS-122, DATACASS-240
This commit is contained in:
Mark Paluch
2016-10-13 15:44:29 +02:00
committed by John Blum
parent fac4323372
commit 1bc460ca0a
11 changed files with 1062 additions and 119 deletions

View File

@@ -59,4 +59,12 @@ public interface CassandraParameterAccessor extends ParameterAccessor {
* @return the parameter type, never {@literal null}.
*/
Class<?> getParameterType(int index);
/**
* Returns the raw parameter values of the underlying query method.
*
* @return
* @since 1.5
*/
Object[] getValues();
}

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.data.cassandra.repository.query;
import java.util.Arrays;
import java.util.List;
import org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.repository.query.ParameterAccessor;
@@ -31,6 +34,8 @@ import com.datastax.driver.core.DataType;
public class CassandraParametersParameterAccessor extends ParametersParameterAccessor
implements CassandraParameterAccessor {
private final List<Object> values;
/**
* Creates a new {@link CassandraParametersParameterAccessor}.
*
@@ -38,7 +43,9 @@ public class CassandraParametersParameterAccessor extends ParametersParameterAcc
* @param values must not be {@literal null}.
*/
public CassandraParametersParameterAccessor(CassandraQueryMethod method, Object... values) {
super(method.getParameters(), values);
this.values = Arrays.asList(values);
}
/*
@@ -79,4 +86,13 @@ public class CassandraParametersParameterAccessor extends ParametersParameterAcc
public Class<?> getParameterType(int index) {
return getParameters().getParameter(index).getType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.CassandraParameterAccessor#getValues()
*/
@Override
public Object[] getValues() {
return values.toArray();
}
}

View File

@@ -114,6 +114,15 @@ public class CassandraQueryMethod extends QueryMethod {
return this.entityMetadata;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryMethod#getParameters()
*/
@Override
public CassandraParameters getParameters() {
return (CassandraParameters) super.getParameters();
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryMethod#createParameters(java.lang.reflect.Method)
*/

View File

@@ -48,11 +48,13 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
private final static TypeInformation<Set> SET = ClassTypeInformation.from(Set.class);
private final CassandraConverter cassandraConverter;
private final CassandraConverter converter;
private final CassandraParameterAccessor delegate;
ConvertingParameterAccessor(CassandraConverter cassandraConverter, CassandraParameterAccessor delegate) {
this.cassandraConverter = cassandraConverter;
ConvertingParameterAccessor(CassandraConverter converter,
CassandraParameterAccessor delegate) {
this.converter = converter;
this.delegate = delegate;
}
@@ -101,8 +103,7 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
DataType dataType = delegate.getDataType(index);
return (dataType != null ? dataType
: cassandraConverter.getMappingContext().getDataType(getParameterType(index)));
return (dataType != null ? dataType : converter.getMappingContext().getDataType(getParameterType(index)));
}
/* (non-Javadoc)
@@ -128,6 +129,14 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
return new ConvertingIterator(delegate.iterator());
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.CassandraParameterAccessor#getValues()
*/
@Override
public Object[] getValues() {
return delegate.getValues();
}
@SuppressWarnings("unchecked")
private Object potentiallyConvert(int index, Object bindableValue, CassandraPersistentProperty property) {
@@ -164,15 +173,15 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
return bindableValue;
}
return cassandraConverter.getConversionService().convert(bindableValue, cassandraType.getJavaType().getRawType());
return converter.getConversionService().convert(bindableValue, cassandraType.getJavaType().getRawType());
}
private CustomConversions getCustomConversions() {
return cassandraConverter.getCustomConversions();
return converter.getCustomConversions();
}
private ConversionService getConversionService() {
return cassandraConverter.getConversionService();
return converter.getConversionService();
}
/**
@@ -191,7 +200,7 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
return CassandraSimpleTypeHolder.getDataTypeFor(cassandraType.type());
}
CassandraMappingContext mappingContext = cassandraConverter.getMappingContext();
CassandraMappingContext mappingContext = converter.getMappingContext();
TypeInformation<?> typeInformation = ClassTypeInformation.from(getParameterType(index));
if (property == null) {

View File

@@ -0,0 +1,185 @@
/*
* Copyright 2016 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
*
* http://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.cassandra.repository.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.data.cassandra.repository.query.StringBasedCassandraQuery.ParameterBinding;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a
* {@link String}.
*
* @author Mark Paluch
* @since 1.5
*/
class ExpressionEvaluatingParameterBinder {
private final SpelExpressionParser expressionParser;
private final EvaluationContextProvider evaluationContextProvider;
/**
* Creates new {@link ExpressionEvaluatingParameterBinder}
*
* @param expressionParser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
*/
public ExpressionEvaluatingParameterBinder(SpelExpressionParser expressionParser,
EvaluationContextProvider evaluationContextProvider) {
Assert.notNull(expressionParser, "ExpressionParser must not be null!");
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
this.expressionParser = expressionParser;
this.evaluationContextProvider = evaluationContextProvider;
}
/**
* Bind values provided by {@link CassandraParameterAccessor} to placeholders in {@link BindingContext} while
* considering potential conversions and parameter types.
*
* @param raw can be {@literal null} or empty.
* @param accessor must not be {@literal null}.
* @param bindingContext must not be {@literal null}.
* @return {@literal null} if given {@code raw} value is empty.
*/
public List<Object> bind(CassandraParameterAccessor accessor, BindingContext bindingContext) {
if (!bindingContext.hasBindings()) {
return Collections.emptyList();
}
List<Object> parameters = new ArrayList<Object>(bindingContext.getBindings().size());
for (ParameterBinding binding : bindingContext.getBindings()) {
parameters.add(getParameterValueForBinding(accessor, bindingContext.getParameters(), binding));
}
return parameters;
}
/**
* Returns the value to be used for the given {@link ParameterBinding}.
*
* @param accessor must not be {@literal null}.
* @param parameters must not be {@literal null}.
* @param binding must not be {@literal null}.
* @return
*/
private Object getParameterValueForBinding(CassandraParameterAccessor accessor, CassandraParameters parameters,
ParameterBinding binding) {
if (binding.isExpression()) {
return evaluateExpression(binding.getExpression(), parameters, accessor.getValues());
}
return binding.isNamed() ? accessor.getBindableValue(getParameterIndex(parameters, binding.getParameterName()))
: accessor.getBindableValue(binding.getParameterIndex());
}
private int getParameterIndex(CassandraParameters parameters, String parameterName) {
for (CassandraParameters.CassandraParameter parameter : parameters) {
if (parameterName.equals(parameter.getName())) {
return parameter.getIndex();
}
}
throw new IllegalArgumentException(
String.format("Invalid parameter name! Cannot resolve parameter [%s]", parameterName));
}
/**
* Evaluates the given {@code expressionString}.
*
* @param expressionString must not be {@literal null} or empty.
* @param parameters must not be {@literal null}.
* @param parameterValues must not be {@literal null}.
* @return
*/
private Object evaluateExpression(String expressionString, CassandraParameters parameters, Object[] parameterValues) {
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(parameters, parameterValues);
Expression expression = expressionParser.parseExpression(expressionString);
return expression.getValue(evaluationContext, Object.class);
}
/**
* @author Mark Paluch
* @since 1.5
*/
static class BindingContext {
final CassandraQueryMethod queryMethod;
final List<ParameterBinding> bindings;
/**
* Creates new {@link BindingContext}.
*
* @param queryMethod
* @param bindings
*/
public BindingContext(CassandraQueryMethod queryMethod, List<ParameterBinding> bindings) {
this.queryMethod = queryMethod;
this.bindings = bindings;
}
/**
* @return {@literal true} when list of bindings is not empty.
*/
boolean hasBindings() {
return !CollectionUtils.isEmpty(bindings);
}
/**
* Get unmodifiable list of {@link ParameterBinding}s.
*
* @return never {@literal null}.
*/
public List<ParameterBinding> getBindings() {
return Collections.unmodifiableList(bindings);
}
/**
* Get the associated {@link CassandraParameters}.
*
* @return
*/
public CassandraParameters getParameters() {
return queryMethod.getParameters();
}
/**
* Get the {@link CassandraQueryMethod}.
*
* @return
*/
public CassandraQueryMethod getQueryMethod() {
return queryMethod;
}
}
}

View File

@@ -15,44 +15,85 @@
*/
package org.springframework.data.cassandra.repository.query;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.cassandra.core.cql.CqlStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.util.ClassUtils;
import org.springframework.data.cassandra.repository.query.ExpressionEvaluatingParameterBinder.BindingContext;
import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.datastax.driver.core.LocalDate;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.core.querybuilder.BindMarker;
/**
* Query to use a plain String to create the {@link Query} to actually execute.
* String-based {@link AbstractCassandraQuery} implementation.
* <p>
* A {@link StringBasedCassandraQuery} expects a query method to be annotated with
* {@link org.springframework.data.cassandra.repository.Query} with a CQL query. String-based queries support named,
* index-based and expression parameters that are resolved during query execution.
*
* @author Matthew Adams
* @author Mark Paluch
* @see org.springframework.data.cassandra.repository.Query
*/
public class StringBasedCassandraQuery extends AbstractCassandraQuery {
@SuppressWarnings("unchecked")
private static final Set<Class<?>> STRING_LIKE_PARAMETER_TYPES = new HashSet<Class<?>>(
Arrays.asList(CharSequence.class, char.class, Character.class, char[].class));
private static final Logger LOG = LoggerFactory.getLogger(StringBasedCassandraQuery.class);
private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE;
private static final Pattern PLACEHOLDER = Pattern.compile("\\?(\\d+)");
private final String query;
private final List<ParameterBinding> queryParameterBindings;
private final ExpressionEvaluatingParameterBinder parameterBinder;
private final CodecRegistry codecRegistry;
protected final String query;
/**
* Creates a new {@link StringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
* {@link CassandraOperations}, {@link SpelExpressionParser}, and {@link EvaluationContextProvider}.
*
* @param queryMethod
* @param operations
* @param expressionParser
* @param evaluationContextProvider
*/
public StringBasedCassandraQuery(CassandraQueryMethod queryMethod, CassandraOperations operations,
SpelExpressionParser expressionParser, EvaluationContextProvider evaluationContextProvider) {
public StringBasedCassandraQuery(String query, CassandraQueryMethod queryMethod, CassandraOperations operations) {
this(queryMethod.getAnnotatedQuery(), queryMethod, operations, expressionParser, evaluationContextProvider);
}
/**
* Creates a new {@link StringBasedCassandraQuery} for the given {@code query}, {@link CassandraQueryMethod},
* {@link CassandraOperations}, {@link SpelExpressionParser}, and {@link EvaluationContextProvider}.
*
* @param query
* @param queryMethod
* @param operations
* @param expressionParser
* @param evaluationContextProvider
*/
public StringBasedCassandraQuery(String query, CassandraQueryMethod queryMethod, CassandraOperations operations,
SpelExpressionParser expressionParser, EvaluationContextProvider evaluationContextProvider) {
super(queryMethod, operations);
this.query = query;
}
public StringBasedCassandraQuery(CassandraQueryMethod queryMethod, CassandraOperations operations) {
this(queryMethod.getAnnotatedQuery(), queryMethod, operations);
this.queryParameterBindings = new ArrayList<ParameterBinding>();
this.query = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings(query,
this.queryParameterBindings);
this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
this.codecRegistry = operations.getSession().getCluster().getConfiguration().getCodecRegistry();
}
/* (non-Javadoc)
@@ -60,59 +101,344 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
*/
@Override
public String createQuery(CassandraParameterAccessor accessor) {
return replacePlaceholders(query, accessor);
}
private String replacePlaceholders(String input, CassandraParameterAccessor accessor) {
try {
List<Object> arguments = this.parameterBinder.bind(accessor,
new BindingContext(getQueryMethod(), queryParameterBindings));
Matcher matcher = PLACEHOLDER.matcher(input);
String result = input;
String boundQuery = bind(query, arguments);
while (matcher.find()) {
String group = matcher.group();
int index = Integer.parseInt(matcher.group(1));
Object value = getParameterWithIndex(accessor, index);
String stringValue;
if (isStringLike(value)) {
stringValue = String.format("'%s'", CqlStringUtils.escapeSingle(value));
} else if (isTimestampParameter(value)) {
stringValue = String.format("%d", ((Date) value).getTime());
} else if (isDateParameter(value)) {
stringValue = String.format("'%s'", value);
} else {
stringValue = value.toString();
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Created query [%s].", boundQuery));
}
result = result.replace(group, stringValue);
return boundQuery;
} catch (RuntimeException e) {
throw QueryCreationException.create(getQueryMethod(), e);
}
}
private String bind(String query, List<Object> arguments) {
return ParameterBinder.INSTANCE.bind(query, codecRegistry, arguments);
}
/**
* A parser that extracts the parameter bindings from a given query string.
*
* @author Mark Paluch
*/
enum ParameterBinder {
INSTANCE;
private static final String ARGUMENT_PLACEHOLDER = "?_param_?";
private static final Pattern ARGUMENT_PLACEHOLDER_PATTERN = Pattern.compile(Pattern.quote(ARGUMENT_PLACEHOLDER));
public String bind(String input, CodecRegistry codecRegistry, List<Object> parameters) {
if (parameters.isEmpty()) {
return input;
}
StringBuilder result = new StringBuilder();
int startIndex = 0;
int currentPos = 0;
int parameterIndex = 0;
Matcher matcher = ARGUMENT_PLACEHOLDER_PATTERN.matcher(input);
while (currentPos < input.length()) {
if (!matcher.find()) {
break;
}
int exprStart = matcher.start();
result.append(input.subSequence(startIndex, exprStart));
result = appendValue(parameters.get(parameterIndex++), codecRegistry, result);
currentPos = matcher.end();
startIndex = currentPos;
}
return result.append(input.subSequence(currentPos, input.length())).toString();
}
return result;
static StringBuilder appendValue(Object value, CodecRegistry codecRegistry, StringBuilder sb) {
if (value == null) {
sb.append("null");
} else if (value instanceof BindMarker) {
sb.append(value);
} else if (value instanceof List && isSerializable(value)) {
// bind variables are not supported inside collection literals
appendList((List<?>) value, codecRegistry, sb);
} else if (value instanceof Set && isSerializable(value)) {
// bind variables are not supported inside collection literals
appendSet((Set<?>) value, codecRegistry, sb);
} else if (value instanceof Map && isSerializable(value)) {
// bind variables are not supported inside collection literals
appendMap((Map<?, ?>) value, codecRegistry, sb);
} else if (isSerializable(value)) {
TypeCodec<Object> codec = codecRegistry.codecFor(value);
sb.append(codec.format(value));
} else {
throw new IllegalArgumentException(String.format("Argument value [%s] is not serializable", value.toString()));
}
return sb;
}
private static StringBuilder appendList(List<?> l, CodecRegistry codecRegistry, StringBuilder sb) {
for (int i = 0; i < l.size(); i++) {
if (i > 0)
sb.append(',');
appendValue(l.get(i), codecRegistry, sb);
}
return sb;
}
private static StringBuilder appendSet(Set<?> s, CodecRegistry codecRegistry, StringBuilder sb) {
boolean first = true;
for (Object elt : s) {
if (first)
first = false;
else
sb.append(',');
appendValue(elt, codecRegistry, sb);
}
return sb;
}
private static StringBuilder appendMap(Map<?, ?> m, CodecRegistry codecRegistry, StringBuilder sb) {
sb.append('{');
boolean first = true;
for (Map.Entry<?, ?> entry : m.entrySet()) {
if (first)
first = false;
else
sb.append(',');
appendValue(entry.getKey(), codecRegistry, sb);
sb.append(':');
appendValue(entry.getValue(), codecRegistry, sb);
}
sb.append('}');
return sb;
}
/**
* Return true if the given value is likely to find a suitable codec to be serialized as a query parameter. If the
* value is not serializable, it must be included in the query string. Non serializable values include special
* values such as function calls, column names and bind markers, and collections thereof. We also don't serialize
* fixed size number types. The reason is that if we do it, we will force a particular size (4 bytes for ints, ...)
* and for the query builder, we don't want users to have to bother with that.
*
* @param value the value to inspect.
* @return true if the value is serializable, false otherwise.
*/
static boolean isSerializable(Object value) {
if (containsSpecialValue(value))
return false;
if (value instanceof Collection)
for (Object elt : (Collection) value)
if (!isSerializable(elt))
return false;
if (value instanceof Map)
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
if (!isSerializable(entry.getKey()) || !isSerializable(entry.getValue()))
return false;
return true;
}
static boolean containsSpecialValue(Object value) {
if (value instanceof BindMarker)
return true;
if (value instanceof Collection)
for (Object elt : (Collection) value)
if (containsSpecialValue(elt))
return true;
if (value instanceof Map)
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
if (containsSpecialValue(entry.getKey()) || containsSpecialValue(entry.getValue()))
return true;
return false;
}
}
private boolean isTimestampParameter(Object value) {
return value instanceof Date;
}
/**
* A parser that extracts the parameter bindings from a given query string.
*
* @author Mark Paluch
*/
enum ParameterBindingParser {
private boolean isDateParameter(Object value) {
return value instanceof LocalDate;
}
INSTANCE;
private boolean isStringLike(Object value) {
private static final char CURRLY_BRACE_OPEN = '{';
private static final char CURRLY_BRACE_CLOSE = '}';
private static final Pattern INDEX_PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)");
private static final Pattern NAMED_PARAMETER_BINDING_PATTERN = Pattern.compile("\\:(\\w+)");
if (value != null) {
for (Class<?> type : STRING_LIKE_PARAMETER_TYPES) {
private static final Pattern INDEX_BASED_EXPRESSION_PATTERN = Pattern.compile("\\?\\#\\{");
private static final Pattern NAME_BASED_EXPRESSION_PATTERN = Pattern.compile("\\:\\#\\{");
private static final String ARGUMENT_PLACEHOLDER = "?_param_?";
if (ClassUtils.isAssignableValue(type, value)) {
return true;
/**
* Returns a list of {@link ParameterBinding}s found in the given {@code input}.
*
* @param input can be {@literal null} or empty.
* @param bindings must not be {@literal null}.
* @return
*/
public String parseAndCollectParameterBindingsFromQueryIntoBindings(String input, List<ParameterBinding> bindings) {
if (!StringUtils.hasText(input)) {
return input;
}
Assert.notNull(bindings, "Parameter bindings must not be null!");
return transformQueryAndCollectExpressionParametersIntoBindings(input, bindings);
}
private static String transformQueryAndCollectExpressionParametersIntoBindings(String input,
List<ParameterBinding> bindings) {
StringBuilder result = new StringBuilder();
int startIndex = 0;
int currentPos = 0;
while (currentPos < input.length()) {
Matcher matcher = findNextBindingOrExpression(input, currentPos);
// no expression parameter found
if (matcher == null) {
break;
}
int exprStart = matcher.start();
currentPos = exprStart;
if (matcher.pattern() == NAME_BASED_EXPRESSION_PATTERN || matcher.pattern() == INDEX_BASED_EXPRESSION_PATTERN) {
// eat parameter expression
int curlyBraceOpenCnt = 1;
currentPos += 3;
while (curlyBraceOpenCnt > 0 && currentPos < input.length()) {
switch (input.charAt(currentPos++)) {
case CURRLY_BRACE_OPEN:
curlyBraceOpenCnt++;
break;
case CURRLY_BRACE_CLOSE:
curlyBraceOpenCnt--;
break;
default:
}
}
result.append(input.subSequence(startIndex, exprStart));
} else {
result.append(input.subSequence(startIndex, exprStart));
}
result.append(ARGUMENT_PLACEHOLDER);
if (matcher.pattern() == NAME_BASED_EXPRESSION_PATTERN || matcher.pattern() == INDEX_BASED_EXPRESSION_PATTERN) {
bindings.add(ParameterBinding.expression(input.substring(exprStart + 3, currentPos - 1), true));
} else {
if (matcher.pattern() == INDEX_PARAMETER_BINDING_PATTERN) {
bindings.add(ParameterBinding.indexed(Integer.parseInt(matcher.group(1))));
} else {
bindings.add(ParameterBinding.named(matcher.group(1)));
}
currentPos = matcher.end();
}
startIndex = currentPos;
}
return result.append(input.subSequence(currentPos, input.length())).toString();
}
private static Matcher findNextBindingOrExpression(String input, int position) {
List<Matcher> matchers = new ArrayList<Matcher>();
matchers.add(INDEX_PARAMETER_BINDING_PATTERN.matcher(input));
matchers.add(NAMED_PARAMETER_BINDING_PATTERN.matcher(input));
matchers.add(INDEX_BASED_EXPRESSION_PATTERN.matcher(input));
matchers.add(NAME_BASED_EXPRESSION_PATTERN.matcher(input));
TreeMap<Integer, Matcher> matcherMap = new TreeMap<Integer, Matcher>();
for (Matcher matcher : matchers) {
if (matcher.find(position)) {
matcherMap.put(matcher.start(), matcher);
}
}
if (matcherMap.isEmpty()) {
return null;
}
return matcherMap.values().iterator().next();
}
}
/**
* A generic parameter binding with name or position information.
*
* @author Mark Paluch
*/
static class ParameterBinding {
private final int parameterIndex;
private final boolean quoted;
private final String expression;
private final String parameterName;
private ParameterBinding(int parameterIndex, boolean quoted, String expression, String parameterName) {
this.parameterIndex = parameterIndex;
this.quoted = quoted;
this.expression = expression;
this.parameterName = parameterName;
}
return false;
}
public static ParameterBinding expression(String expression, boolean quoted) {
return new ParameterBinding(-1, quoted, expression, null);
}
private Object getParameterWithIndex(CassandraParameterAccessor accessor, int index) {
return accessor.getBindableValue(index);
public static ParameterBinding indexed(int parameterIndex) {
return new ParameterBinding(parameterIndex, false, null, null);
}
public static ParameterBinding named(String name) {
return new ParameterBinding(-1, false, null, name);
}
public boolean isNamed() {
return parameterName != null;
}
public int getParameterIndex() {
return parameterIndex;
}
public String getParameter() {
return "?" + (isExpression() ? "expr" : "") + parameterIndex;
}
public String getExpression() {
return expression;
}
public boolean isExpression() {
return this.expression != null;
}
public String getParameterName() {
return parameterName;
}
}
}

View File

@@ -36,6 +36,7 @@ import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
/**
@@ -49,20 +50,22 @@ import org.springframework.util.Assert;
*/
public class CassandraRepositoryFactory extends RepositoryFactorySupport {
private final CassandraOperations cassandraOperations;
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private final CassandraOperations operations;
private final CassandraMappingContext mappingContext;
/**
* Creates a new {@link CassandraRepositoryFactory} with the given {@link CassandraOperations}.
*
* @param cassandraOperations must not be {@literal null}
* @param operations must not be {@literal null}
*/
public CassandraRepositoryFactory(CassandraOperations cassandraOperations) {
public CassandraRepositoryFactory(CassandraOperations operations) {
Assert.notNull(cassandraOperations);
Assert.notNull(operations);
this.cassandraOperations = cassandraOperations;
this.mappingContext = cassandraOperations.getConverter().getMappingContext();
this.operations = operations;
this.mappingContext = operations.getConverter().getMappingContext();
}
/*
@@ -82,7 +85,7 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
protected Object getTargetRepository(RepositoryInformation information) {
CassandraEntityInformation<?, Serializable> entityInformation = getEntityInformation(information.getDomainType());
return getTargetRepositoryViaReflection(information, entityInformation, cassandraOperations);
return getTargetRepositoryViaReflection(information, entityInformation, operations);
}
/*
@@ -101,7 +104,7 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
}
return new MappingCassandraEntityInformation<T, ID>((CassandraPersistentEntity<T>) entity,
cassandraOperations.getConverter());
operations.getConverter());
}
/*
@@ -119,11 +122,22 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
*/
@Override
protected QueryLookupStrategy getQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider) {
return new CassandraQueryLookupStrategy();
return new CassandraQueryLookupStrategy(operations, evaluationContextProvider, mappingContext);
}
private class CassandraQueryLookupStrategy implements QueryLookupStrategy {
private final CassandraOperations operations;
private final EvaluationContextProvider evaluationContextProvider;
private final CassandraMappingContext mappingContext;
public CassandraQueryLookupStrategy(CassandraOperations operations,
EvaluationContextProvider evaluationContextProvider, CassandraMappingContext mappingContext) {
this.operations = operations;
this.evaluationContextProvider = evaluationContextProvider;
this.mappingContext = mappingContext;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries)
@@ -137,11 +151,12 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
if (namedQueries.hasQuery(namedQueryName)) {
String namedQuery = namedQueries.getQuery(namedQueryName);
return new StringBasedCassandraQuery(namedQuery, queryMethod, cassandraOperations);
return new StringBasedCassandraQuery(namedQuery, queryMethod, operations, EXPRESSION_PARSER,
evaluationContextProvider);
} else if (queryMethod.hasAnnotatedQuery()) {
return new StringBasedCassandraQuery(queryMethod, cassandraOperations);
return new StringBasedCassandraQuery(queryMethod, operations, EXPRESSION_PARSER, evaluationContextProvider);
} else {
return new PartTreeCassandraQuery(queryMethod, cassandraOperations);
return new PartTreeCassandraQuery(queryMethod, operations);
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2016 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
*
* http://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.cassandra.repository.query;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.springframework.data.cassandra.repository.query.StringBasedCassandraQuery.ParameterBindingParser.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.data.cassandra.repository.query.StringBasedCassandraQuery.ParameterBinding;
/**
* Unit tests for
* {@link org.springframework.data.cassandra.repository.query.StringBasedCassandraQuery.ParameterBindingParser}.
*
* @author Mark Paluch
*/
public class ParameterBindingParserUnitTests {
/**
* @see DATACASS-117
*/
@Test
public void parseWithoutParameters() {
String query = "SELECT * FROM hello_world";
List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
String transformed = INSTANCE.parseAndCollectParameterBindingsFromQueryIntoBindings(query, bindings);
assertThat(transformed, is(equalTo(query)));
assertThat(bindings, is(empty()));
}
/**
* @see DATACASS-117
*/
@Test
public void parseWithStaticParameters() {
String query = "SELECT * FROM hello_world WHERE a = 1 AND b = {'list'} AND c = {'key':'value'}";
List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
String transformed = INSTANCE.parseAndCollectParameterBindingsFromQueryIntoBindings(query, bindings);
assertThat(transformed, is(equalTo(query)));
assertThat(bindings, is(empty()));
}
/**
* @see DATACASS-117
*/
@Test
public void parseWithPositionalParameters() {
String query = "SELECT * FROM hello_world WHERE a = ?0 and b = ?13";
List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
String transformed = INSTANCE.parseAndCollectParameterBindingsFromQueryIntoBindings(query, bindings);
assertThat(transformed, is(equalTo("SELECT * FROM hello_world WHERE a = ?_param_? and b = ?_param_?")));
assertThat(bindings, hasSize(2));
assertThat(bindings.get(0).getParameterIndex(), is(equalTo(0)));
assertThat(bindings.get(1).getParameterIndex(), is(equalTo(13)));
}
/**
* @see DATACASS-117
*/
@Test
public void parseWithNamedParameters() {
String query = "SELECT * FROM hello_world WHERE a = :hello and b = :world";
List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
String transformed = INSTANCE.parseAndCollectParameterBindingsFromQueryIntoBindings(query, bindings);
assertThat(transformed, is(equalTo("SELECT * FROM hello_world WHERE a = ?_param_? and b = ?_param_?")));
assertThat(bindings, hasSize(2));
}
/**
* @see DATACASS-117
*/
@Test
public void parseWithIndexExpressionParameters() {
String query = "SELECT * FROM hello_world WHERE a = ?#{[0]} and b = ?#{[2]}";
List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
String transformed = INSTANCE.parseAndCollectParameterBindingsFromQueryIntoBindings(query, bindings);
assertThat(transformed, is(equalTo("SELECT * FROM hello_world WHERE a = ?_param_? and b = ?_param_?")));
assertThat(bindings, hasSize(2));
}
/**
* @see DATACASS-117
*/
@Test
public void parseWithNameExpressionParameters() {
String query = "SELECT * FROM hello_world WHERE a = :#{#a} and b = :#{#b}";
List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
String transformed = INSTANCE.parseAndCollectParameterBindingsFromQueryIntoBindings(query, bindings);
assertThat(transformed, is(equalTo("SELECT * FROM hello_world WHERE a = ?_param_? and b = ?_param_?")));
assertThat(bindings, hasSize(2));
}
/**
* @see DATACASS-117
*/
@Test
public void parseWithMixedParameters() {
String query = "SELECT * FROM hello_world WHERE (a = ?1 and b = :name) and c = (:#{#a}) and (d = ?#{[1]})";
List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
String transformed = INSTANCE.parseAndCollectParameterBindingsFromQueryIntoBindings(query, bindings);
assertThat(transformed, is(equalTo(
"SELECT * FROM hello_world WHERE (a = ?_param_? and b = ?_param_?) and c = (?_param_?) and (d = ?_param_?)")));
assertThat(bindings, hasSize(4));
}
}

View File

@@ -20,14 +20,17 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
@@ -38,8 +41,16 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
import org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider;
import org.springframework.data.repository.query.Param;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.ReflectionUtils;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.Configuration;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
@@ -53,7 +64,12 @@ import com.datastax.driver.core.querybuilder.Select;
@RunWith(MockitoJUnitRunner.class)
public class StringBasedCassandraQueryIntegrationUnitTests {
SpelExpressionParser PARSER = new SpelExpressionParser();
@Mock CassandraOperations operations;
@Mock Session session;
@Mock Cluster cluster;
@Mock Configuration configuration;
RepositoryMetadata metadata;
MappingCassandraConverter converter;
@@ -63,6 +79,10 @@ public class StringBasedCassandraQueryIntegrationUnitTests {
public void setUp() {
when(operations.getConverter()).thenReturn(converter);
when(operations.getSession()).thenReturn(session);
when(session.getCluster()).thenReturn(cluster);
when(cluster.getConfiguration()).thenReturn(configuration);
when(configuration.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT_INSTANCE);
this.metadata = AbstractRepositoryMetadata.getMetadata(SampleRepository.class);
this.converter = new MappingCassandraConverter(new BasicCassandraMappingContext());
@@ -71,76 +91,283 @@ public class StringBasedCassandraQueryIntegrationUnitTests {
this.converter.afterPropertiesSet();
}
/**
* @see DATACASS-117
*/
@Test
public void bindsSimplePropertyCorrectly() throws Exception {
public void bindsIndexParameterCorrectly() {
Method method = SampleRepository.class.getMethod("findByLastname", String.class);
CassandraQueryMethod queryMethod = new CassandraQueryMethod(method, metadata, factory,
converter.getMappingContext());
StringBasedCassandraQuery cassandraQuery = new StringBasedCassandraQuery(queryMethod, operations);
CassandraParametersParameterAccessor accesor = new CassandraParametersParameterAccessor(queryMethod, "Matthews");
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByLastname", String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), "Matthews");
String stringQuery = cassandraQuery.createQuery(accesor);
SimpleStatement actual = new SimpleStatement(stringQuery);
String actual = cassandraQuery.createQuery(accessor);
String table = Person.class.getSimpleName().toLowerCase();
Select expected = QueryBuilder.select().all().from(table);
expected.setForceNoValues(true);
expected.where(QueryBuilder.eq("lastname", "Matthews"));
assertThat(actual.getQueryString(), is(expected.getQueryString()));
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname = 'Matthews';")));
}
/**
* @see DATACASS-117
*/
@Test
public void bindsMultipleParametersCorrectly() throws Exception {
public void bindsAndEscapesIndexParameterCorrectly() {
Method method = SampleRepository.class.getMethod("findByLastnameAndFirstname", String.class, String.class);
CassandraQueryMethod queryMethod = new CassandraQueryMethod(method, metadata, factory,
converter.getMappingContext());
StringBasedCassandraQuery cassandraQuery = new StringBasedCassandraQuery(queryMethod, operations);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(queryMethod, "Matthews",
"John");
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByLastname", String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), "Mat\th'ew\"s");
String stringQuery = cassandraQuery.createQuery(accessor);
SimpleStatement actual = new SimpleStatement(stringQuery);
String actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname = 'Mat\th''ew\"s';")));
}
/**
* @see DATACASS-117
*/
@Test
public void bindsAndEscapesBytesIndexParameterCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByLastname", String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), ByteBuffer.wrap(new byte[] { 1, 2, 3, 4 }));
String actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname = 0x01020304;")));
}
/**
* @see DATACASS-117
*/
@Test
public void bindsIndexParameterInListCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByLastNameIn", Collection.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), Arrays.asList("White", "Heisenberg"));
String actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname IN ('White','Heisenberg');")));
}
/**
* @see DATACASS-117
*/
@Test
public void bindsIndexParameterIsListCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByLastNamesAndAge", Collection.class, int.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), Arrays.asList("White", "Heisenberg"), 42);
String actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastnames = ['White','Heisenberg'] AND age = 42;")));
}
/**
* @see DATACASS-117
*/
@Test(expected = QueryCreationException.class)
public void referencingUnknownIndexedParameterShouldFail() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByOutOfBoundsLastNameShouldFail", String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), "Hello");
cassandraQuery.createQuery(accessor);
}
/**
* @see DATACASS-117
*/
@Test(expected = QueryCreationException.class)
public void referencingUnknownNamedParameterShouldFail() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByUnknownParameterLastNameShouldFail", String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), "Hello");
cassandraQuery.createQuery(accessor);
}
/**
* @see DATACASS-117
*/
@Test
public void bindsIndexParameterInSetCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByLastNameIn", Collection.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), new HashSet<String>(Arrays.asList("White", "Heisenberg")));
String actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname IN ('White','Heisenberg');")));
}
/**
* @see DATACASS-117
*/
@Test
public void bindsNamedParameterCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByNamedParameter", String.class, String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), "Walter", "Matthews");
String actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname = 'Matthews';")));
}
/**
* @see DATACASS-117
*/
@Test
public void bindsIndexExpressionParameterCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByIndexExpressionParameter", String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), "Matthews");
String actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname = 'Matthews';")));
}
/**
* @see DATACASS-117
*/
@Test
public void bindsExpressionParameterCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByExpressionParameter", String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), "Matthews");
String actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname = 'Matthews';")));
}
/**
* @see DATACASS-117
*/
@Test
public void bindsConditionalExpressionParameterCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByConditionalExpressionParameter", String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), "Matthews");
String actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname = 'Woohoo';")));
accessor = new CassandraParametersParameterAccessor(cassandraQuery.getQueryMethod(), "Walter");
actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname = 'Walter';")));
}
/**
* @see DATACASS-117
*/
@Test
public void bindsReusedParametersCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByLastnameUsedTwice", String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), "Matthews");
String actual = cassandraQuery.createQuery(accessor);
assertThat(actual, is(equalTo("SELECT * FROM person WHERE lastname='Matthews' or firstname = 'Matthews';")));
}
/**
* @see DATACASS-117
*/
@Test
public void bindsMultipleParametersCorrectly() {
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByLastnameAndFirstname", String.class, String.class);
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod(), "Matthews", "John");
String actual = cassandraQuery.createQuery(accessor);
String table = Person.class.getSimpleName().toLowerCase();
Select expected = QueryBuilder.select().all().from(table);
expected.setForceNoValues(true);
expected.where(QueryBuilder.eq("lastname", "Matthews")).and(QueryBuilder.eq("firstname", "John"));
assertThat(actual.getQueryString(), is(expected.getQueryString()));
assertThat(actual, is(expected.toString()));
}
/**
* @see DATACASS-296
*/
@Test
public void bindsConvertedPropertyCorrectly() throws Exception {
public void bindsConvertedParameterCorrectly() {
Method method = SampleRepository.class.getMethod("findByCreatedDate", LocalDate.class);
CassandraQueryMethod queryMethod = new CassandraQueryMethod(method, metadata, factory,
converter.getMappingContext());
StringBasedCassandraQuery cassandraQuery = new StringBasedCassandraQuery(queryMethod, operations);
CassandraParameterAccessor accessor = new ConvertingParameterAccessor(converter, new CassandraParametersParameterAccessor(queryMethod,
LocalDate.of(2010, 7, 4)));
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByCreatedDate", LocalDate.class);
CassandraParameterAccessor accessor = new ConvertingParameterAccessor(converter,
new CassandraParametersParameterAccessor(cassandraQuery.getQueryMethod(), LocalDate.of(2010, 7, 4)));
String stringQuery = cassandraQuery.createQuery(accessor);
SimpleStatement actual = new SimpleStatement(stringQuery);
String actual = cassandraQuery.createQuery(accessor);
String table = Person.class.getSimpleName().toLowerCase();
Select expected = QueryBuilder.select().all().from(table);
expected.setForceNoValues(true);
expected.where(QueryBuilder.eq("createdDate", com.datastax.driver.core.LocalDate.fromYearMonthDay(2010, 7, 4)));
assertThat(actual.getQueryString(), is(expected.getQueryString()));
assertThat(actual, is(equalTo("SELECT * FROM person WHERE createdDate='2010-07-04';")));
}
private StringBasedCassandraQuery getQueryMethod(String name, Class<?>... args) {
Method method = ReflectionUtils.findMethod(SampleRepository.class, name, args);
CassandraQueryMethod queryMethod = new CassandraQueryMethod(method, metadata, factory,
converter.getMappingContext());
return new StringBasedCassandraQuery(queryMethod, operations, PARSER,
new ExtensionAwareEvaluationContextProvider());
}
private interface SampleRepository extends Repository<Person, String> {
@Query("SELECT * FROM person WHERE lastname=?0;")
@Query("SELECT * FROM person WHERE lastname = ?0;")
Person findByLastname(String lastname);
@Query("SELECT * FROM person WHERE lastname=?0 or firstname = ?0;")
Person findByLastnameUsedTwice(String lastname);
@Query("SELECT * FROM person WHERE lastname = :lastname;")
Person findByNamedParameter(@Param("another") String another, @Param("lastname") String lastname);
@Query("SELECT * FROM person WHERE lastname = :#{[0]};")
Person findByIndexExpressionParameter(String lastname);
@Query("SELECT * FROM person WHERE lastnames = [?0] AND age = ?1;")
Person findByLastNamesAndAge(Collection<String> lastname, int age);
@Query("SELECT * FROM person WHERE lastname = ?0 AND age = ?2;")
Person findByOutOfBoundsLastNameShouldFail(String lastname);
@Query("SELECT * FROM person WHERE lastname = :unknown;")
Person findByUnknownParameterLastNameShouldFail(String lastname);
@Query("SELECT * FROM person WHERE lastname IN (?0);")
Person findByLastNameIn(Collection<String> lastNames);
@Query("SELECT * FROM person WHERE lastname = :#{#lastname};")
Person findByExpressionParameter(@Param("lastname") String lastname);
@Query("SELECT * FROM person WHERE lastname = :#{#lastname == 'Matthews' ? 'Woohoo' : #lastname};")
Person findByConditionalExpressionParameter(@Param("lastname") String lastname);
@Query("SELECT * FROM person WHERE lastname=?0 AND firstname=?1;")
Person findByLastnameAndFirstname(String lastname, String firstname);

View File

@@ -21,9 +21,7 @@ import java.util.Iterator;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Range;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Distance;
import org.springframework.data.repository.query.ParameterAccessor;
import com.datastax.driver.core.CodecRegistry;
@@ -85,6 +83,11 @@ class StubParameterAccessor implements CassandraParameterAccessor {
return values[index];
}
@Override
public Object[] getValues() {
return new Object[0];
}
@Override
public boolean hasBindableNullValue() {
return false;

View File

@@ -29,6 +29,7 @@ import org.springframework.data.cassandra.test.integration.repository.querymetho
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.named.PersonRepositoryWithNamedQueries;
import com.datastax.driver.core.ResultSet;
import org.springframework.data.repository.query.Param;
/**
* we extend {@link PersonRepositoryWithNamedQueries} here just to keep the test codebase in sync.
@@ -50,16 +51,16 @@ public interface PersonRepositoryWithQueryAnnotations extends PersonRepository {
Person[] findFolksWithLastnameAsArray(String lastname);
@Override
@Query("select * from person where lastname = ?0 and firstname = ?1")
@Query("select * from person where lastname = ?#{[0]} and firstname = ?1")
Person findSingle(String last, String first);
@Override
@Query("select * from person where lastname = ?0")
List<Map<String, Object>> findFolksWithLastnameAsListOfMapOfStringToObject(String last);
@Query("select * from person where lastname = :last")
List<Map<String, Object>> findFolksWithLastnameAsListOfMapOfStringToObject(@Param("last") String last);
@Override
@Query("select nickname from person where lastname = ?0 and firstname = ?1")
String findSingleNickname(String last, String first);
@Query("select nickname from person where lastname = :#{#last} and firstname = ?1")
String findSingleNickname(@Param("last") String last, @Param("first") String first);
@Override
@Query("select birthdate from person where lastname = ?0 and firstname = ?1")