DATACASS-7 - Support query derivation in Cassandra repositories.

We now support query derivation in Cassandra repositories. Repositories may declare query methods and queries are created based on the repository declaration.

interface PersonRepository extends CassandraRepository<Person> {

	List<Person> findByLastname(@CassandraType(type = Name.VARCHAR) String lastname);
	List<Person> findByLastname(String lastname, Sort sort);
	List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
	Collection<PersonProjection> findPersonProjectedBy();

	interface PersonProjection {

		String getFirstname();
	}
}

@Table
@Data
public class Person {
	@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 0)
	private String lastname;

	@PrimaryKeyColumn(type = PrimaryKeyType.CLUSTERED, ordinal = 1)
	private String firstname;
}

Query derivation supports a basic set of where predicates:
* = (Equals/Simple property)
* >= (Greater or equal)
* > (Greater)
* < (Less)
* <= (Less or equal)
* IN, LIKE (Like, Starting with, Ending with), CONTAINING
* = true (Is true)
* = false (Is false)

Derived queries work with primary-key and non-primary key columns. Non-primary key columns require a secondary index otherwise these fields can't be queried.

Original pull request: #74.
This commit is contained in:
Mark Paluch
2016-07-01 18:02:13 +02:00
committed by John Blum
parent 6e176ba28d
commit 43fc7518b3
33 changed files with 2183 additions and 230 deletions

View File

@@ -62,6 +62,14 @@ public abstract class AbstractCassandraConverter implements CassandraConverter,
this.conversions = conversions;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.convert.CassandraConverter#getCustomConversions()
*/
@Override
public CustomConversions getCustomConversions() {
return conversions;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/

View File

@@ -46,7 +46,7 @@ public interface CassandraConverter
* <li>A the composite primary key for {@link org.springframework.data.cassandra.mapping.PrimaryKey} using a
* {@link org.springframework.data.cassandra.mapping.PrimaryKeyClass}</li>
* </ul>
*
*
* @param object must not be {@literal null}.
* @param entity must not be {@literal null}.
* @return
@@ -55,10 +55,17 @@ public interface CassandraConverter
/**
* Converts and writes a {@code source} object into a {@code sink} using the given {@link CassandraPersistentEntity}.
*
*
* @param source the source, may be {@literal null}.
* @param sink must not be {@literal null}.
* @param entity must not be {@literal null}.
*/
void write(Object source, Object sink, CassandraPersistentEntity<?> entity);
/**
* Returns the {@link CustomConversions} registered in the {@link CassandraConverter}.
*
* @return the {@link CustomConversions}.
*/
CustomConversions getCustomConversions();
}

View File

@@ -42,6 +42,10 @@ public class BasicCassandraPersistentEntityMetadataVerifier implements Cassandra
@Override
public void verify(CassandraPersistentEntity<?> entity) throws MappingException {
if(entity.getType().isInterface()){
return;
}
VerifierMappingExceptions exceptions = new VerifierMappingExceptions(entity,
String.format("Mapping Exceptions from BasicCassandraPersistentEntityMetadataVerifier for %s", entity.getName()));

View File

@@ -51,6 +51,7 @@ import com.datastax.driver.core.DataType;
* @author Alex Shvid
* @author Matthew T. Adams
* @author Antoine Toulme
* @author Mark Paluch
*/
public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentProperty<CassandraPersistentProperty>
implements CassandraPersistentProperty, ApplicationContextAware {
@@ -129,7 +130,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
List<CqlIdentifier> columnNames = getColumnNames();
if (columnNames.size() != 1) {
throw new IllegalStateException("property does not have a single column mapping");
throw new IllegalStateException(String.format("Property [%s] has no single column mapping", getName()));
}
return columnNames.get(0);

View File

@@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -28,6 +29,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.CollectionExecution;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultProcessingConverter;
@@ -38,6 +40,7 @@ import org.springframework.data.cassandra.repository.query.CassandraQueryExecuti
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -65,22 +68,29 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
*/
public AbstractCassandraQuery(CassandraQueryMethod method, CassandraOperations operations) {
Assert.notNull(operations);
Assert.notNull(method);
Assert.notNull(method, "CassandraQueryMethod must not be null");
Assert.notNull(operations, "CassandraOperations must not be null");
this.method = method;
this.template = operations;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
*/
@Override
public CassandraQueryMethod getQueryMethod() {
return method;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
*/
@Override
public Object execute(Object[] parameters) {
CassandraParameterAccessor accessor = new ConvertingParameterAccessor(template.getConverter(), new CassandraParametersParameterAccessor(method, parameters));
CassandraParameterAccessor accessor = new ConvertingParameterAccessor(template.getConverter(),
new CassandraParametersParameterAccessor(method, parameters));
String query = createQuery(accessor);
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
@@ -88,7 +98,13 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
CassandraQueryExecution cassandraQueryExecution = getExecution(query, accessor,
new ResultProcessingConverter(processor));
return cassandraQueryExecution.execute(query, processor.getReturnedType().getReturnedType());
CassandraReturnedType returnedType = new CassandraReturnedType(processor.getReturnedType(), template.getConverter().getCustomConversions());
if (returnedType.isProjecting()) {
return cassandraQueryExecution.execute(query, returnedType.getDomainType());
}
return cassandraQueryExecution.execute(query, returnedType.getReturnedType());
}
/**
@@ -167,6 +183,7 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
return object;
}
@Deprecated
protected void warnIfMoreResults(Iterator<Row> iterator) {
if (log.isWarnEnabled() && iterator.hasNext()) {
@@ -180,6 +197,7 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
}
}
@Deprecated
public ConversionService getConversionService() {
return template.getConverter().getConversionService();
}
@@ -200,4 +218,48 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
* @param accessor must not be {@literal null}.
*/
protected abstract String createQuery(CassandraParameterAccessor accessor);
private class CassandraReturnedType {
private final ReturnedType returnedType;
private final CustomConversions customConversions;
CassandraReturnedType(ReturnedType returnedType, CustomConversions customConversions) {
this.returnedType = returnedType;
this.customConversions = customConversions;
}
boolean isProjecting(){
if(!returnedType.isProjecting()){
return false;
}
// Spring Data Cassandra allows List<Map<String, Object> and Map<String, Object> declarations on query methods
// so we don't want to let projection kick in
if(ClassUtils.isAssignable(Map.class, returnedType.getReturnedType())){
return false;
}
// Type conversion using registered conversions is handled on template level
if(customConversions.hasCustomWriteTarget(returnedType.getReturnedType())){
return false;
}
// Don't apply projection on Cassandra simple types
if(customConversions.isSimpleType(returnedType.getReturnedType())){
return false;
}
return true;
}
Class<?> getReturnedType() {
return returnedType.getReturnedType();
}
Class<?> getDomainType() {
return returnedType.getDomainType();
}
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.cassandra.repository.query;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.repository.query.ParameterAccessor;
import com.datastax.driver.core.DataType;
@@ -32,7 +33,7 @@ public interface CassandraParameterAccessor extends ParameterAccessor {
* Returns the Cassandra {@link DataType} for the declared parameter if the type is a
* {@link org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder simple type}. Parameter types may be
* specified using {@link org.springframework.data.cassandra.mapping.CassandraType}.
*
*
* @param index the parameter index
* @return the Cassandra {@link DataType} or {@literal null} if the parameter type cannot be determined from
* {@link org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder}
@@ -41,6 +42,16 @@ public interface CassandraParameterAccessor extends ParameterAccessor {
*/
DataType getDataType(int index);
/**
* Returns the {@link CassandraType} for the declared method parameter.
*
* @param index the parameter index
* @return the Cassandra {@link CassandraType} or {@literal null}.
* @see org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder
* @see org.springframework.data.cassandra.mapping.CassandraType
*/
CassandraType findCassandraType(int index);
/**
* The actual parameter type (after unwrapping).
*

View File

@@ -71,7 +71,7 @@ public class CassandraParameters extends Parameters<CassandraParameters, Cassand
*/
class CassandraParameter extends Parameter {
private final DataType dataType;
private final CassandraType cassandraType;
protected CassandraParameter(MethodParameter parameter) {
@@ -87,22 +87,19 @@ public class CassandraParameters extends Parameters<CassandraParameters, Cassand
CassandraType.class.getSimpleName()));
}
this.dataType = CassandraSimpleTypeHolder.getDataTypeFor(cassandraType.type());
this.cassandraType = cassandraType;
} else {
this.dataType = CassandraSimpleTypeHolder.getDataTypeFor(getType());
this.cassandraType = null;
}
}
/**
* Returns the Cassandra {@link DataType} for the declared parameter if the type is a
* {@link org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder simple type}. Parameter types may be
* specified using {@link org.springframework.data.cassandra.mapping.CassandraType}.
* Returns the {@link CassandraType} for the declared parameter if specified using {@link org.springframework.data.cassandra.mapping.CassandraType}.
*
* @return the Cassandra {@link DataType} or {@literal null} if the parameter type cannot be determined from
* {@link org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder}
* @return the {@link CassandraType} or {@literal null}.
*/
public DataType getCassandraType() {
return dataType;
public CassandraType getCassandraType() {
return cassandraType;
}
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.cassandra.repository.query;
import org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.ParametersParameterAccessor;
@@ -39,19 +41,28 @@ public class CassandraParametersParameterAccessor extends ParametersParameterAcc
super(method.getParameters(), values);
}
/**
* Returns the Cassandra {@link DataType} for the declared parameter if the type is a
* {@link org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder simple type}. Parameter types may be
* specified using {@link org.springframework.data.cassandra.mapping.CassandraType}.
*
* @param index parameter index
* @return the Cassandra {@link DataType} or {@literal null} if the parameter type cannot be determined from
* {@link org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.CassandraParameterAccessor#findCassandraType(int)
*/
public DataType getDataType(int index) {
public CassandraType findCassandraType(int index) {
return getParameters().getParameter(index).getCassandraType();
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.CassandraParameterAccessor#getDataType(int)
*/
@Override
public DataType getDataType(int index) {
CassandraType cassandraType = findCassandraType(index);
if (cassandraType != null) {
return CassandraSimpleTypeHolder.getDataTypeFor(cassandraType.type());
}
return CassandraSimpleTypeHolder.getDataTypeFor(getParameterType(index));
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.CassandraParameterAccessor#getParameterType(int)
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2013 the original author or authors.
* Copyright 2010-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.
@@ -15,24 +15,31 @@
*/
package org.springframework.data.cassandra.repository.query;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.repository.core.EntityMetadata;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import com.datastax.driver.core.querybuilder.Clause;
import com.datastax.driver.core.querybuilder.QueryBuilder;
@@ -40,174 +47,172 @@ import com.datastax.driver.core.querybuilder.Select;
/**
* Custom query creator to create Cassandra criteria.
*
* @author Matthew Adams
* @author Mark Paluch
*/
class CassandraQueryCreator extends AbstractQueryCreator<Select, Clause> {
private static final Logger LOG = LoggerFactory.getLogger(CassandraQueryCreator.class);
private final CassandraParameterAccessor accessor;
private static final Pattern PUNCTATION_PATTERN = Pattern.compile("\\p{Punct}");
private static final Logger LOG = LoggerFactory.getLogger(CassandraQueryCreator.class);
private final CassandraMappingContext context;
private final WhereBuilder whereBuilder = new WhereBuilder();
private final CassandraPersistentEntity<?> entity;
/**
* Creates a new {@link CassandraQueryCreator} from the given {@link PartTree}, {@link ConvertingParameterAccessor}
* and {@link MappingContext}.
*
* @param tree
* @param accessor
* @param context
* @param tree must not be {@literal null}.
* @param accessor must not be {@literal null}.
* @param context must not be {@literal null}.
* @param entityMetadata must not be {@literal null}.
*/
public CassandraQueryCreator(PartTree tree, CassandraParameterAccessor accessor, CassandraMappingContext context) {
public CassandraQueryCreator(PartTree tree, CassandraParameterAccessor accessor, CassandraMappingContext context,
EntityMetadata<?> entityMetadata) {
super(tree, accessor);
Assert.notNull(context);
Assert.notNull(context, "CassandraMappingContext must not be null");
Assert.notNull(entityMetadata, "EntityInformation must not be null");
this.accessor = accessor;
this.context = context;
this.entity = context.getPersistentEntity(entityMetadata.getJavaType());
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator)
*/
@Override
protected Clause create(Part part, Iterator<Object> iterator) {
PersistentPropertyPath<CassandraPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
CassandraPersistentProperty property = path.getLeafProperty();
Clause criteria = from(part, property,
null /* TODO where(path.toDotPath(CassandraPersistentProperty.PropertyToFieldNameConverter.INSTANCE))*/,
iterator);
return criteria;
return from(part, property, (PotentiallyConvertingIterator) iterator);
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator)
*/
@Override
protected Clause and(Part part, Clause base, Iterator<Object> iterator) {
if (base == null) {
return create(part, iterator);
return whereBuilder.and(create(part, iterator));
}
PersistentPropertyPath<CassandraPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
CassandraPersistentProperty property = path.getLeafProperty();
return from(part, property,
null /* TODO base.and(path.toDotPath(CassandraPersistentProperty.PropertyToFieldNameConverter.INSTANCE))*/,
iterator);
whereBuilder.and(base);
return create(part, iterator);
}
/*
* Cassandra does not support OR queries.
*
* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object)
*/
@Override
protected Clause or(Clause base, Clause criteria) {
throw new InvalidDataAccessApiUsageException(String.format("Cassandra does not support an OR operator!"));
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#complete(java.lang.Object, org.springframework.data.domain.Sort)
*/
@Override
protected Select complete(Clause criteria, Sort sort) {
if (criteria == null) {
return null;
if (criteria != null) {
whereBuilder.and(criteria);
}
Select select = QueryBuilder.select().all().from("TODO");
select.where(criteria);
Select select = StatementBuilder.select(entity, whereBuilder, sort);
if (LOG.isDebugEnabled()) {
LOG.debug("Created query " + select.getQueryString());
LOG.debug("Created query {}", select);
}
return select;
}
private Clause from(Part part, CassandraPersistentProperty property, Clause criteria, Iterator<Object> parameters) {
private Clause from(Part part, CassandraPersistentProperty property, PotentiallyConvertingIterator parameters) {
Type type = part.getType();
switch (type) {
// TODO
// case AFTER:
// case GREATER_THAN:
// return criteria.gt(parameters.nextConverted(property));
// case GREATER_THAN_EQUAL:
// return criteria.gte(parameters.nextConverted(property));
// case BEFORE:
// case LESS_THAN:
// return criteria.lt(parameters.nextConverted(property));
// case LESS_THAN_EQUAL:
// return criteria.lte(parameters.nextConverted(property));
// case BETWEEN:
// return criteria.gt(parameters.nextConverted(property)).lt(parameters.nextConverted(property));
// case IS_NOT_NULL:
// return criteria.ne(null);
// case IS_NULL:
// return criteria.is(null);
// case NOT_IN:
// return criteria.nin(nextAsArray(parameters, property));
// case IN:
// return criteria.in(nextAsArray(parameters, property));
// case LIKE:
// case STARTING_WITH:
// case ENDING_WITH:
// case CONTAINING:
// return addAppropriateLikeRegexTo(criteria, part, parameters.next().toString());
// case REGEX:
// return criteria.regex(parameters.next().toString());
// case EXISTS:
// return criteria.exists((Boolean) parameters.next());
// case TRUE:
// return criteria.is(true);
// case FALSE:
// return criteria.is(false);
// case WITHIN:
//
// Object parameter = parameters.next();
// return criteria.within((Shape) parameter);
// case SIMPLE_PROPERTY:
//
// return isSimpleComparisionPossible(part) ? criteria.is(parameters.nextConverted(property))
// : createLikeRegexCriteriaOrThrow(part, property, criteria, parameters, false);
//
// case NEGATING_SIMPLE_PROPERTY:
//
// return isSimpleComparisionPossible(part) ? criteria.ne(parameters.nextConverted(property))
// : createLikeRegexCriteriaOrThrow(part, property, criteria, parameters, true);
case AFTER:
case GREATER_THAN:
return QueryBuilder.gt(columnName(property), parameters.nextConverted(property));
case GREATER_THAN_EQUAL:
return QueryBuilder.gte(columnName(property), parameters.nextConverted(property));
case BEFORE:
case LESS_THAN:
return QueryBuilder.lt(columnName(property), parameters.nextConverted(property));
case LESS_THAN_EQUAL:
return QueryBuilder.lte(columnName(property), parameters.nextConverted(property));
case IN:
return QueryBuilder.in(columnName(property), nextAsArray(property, parameters));
case LIKE:
case STARTING_WITH:
case ENDING_WITH:
return QueryBuilder.like(columnName(property), like(type, parameters.nextConverted(property)));
case CONTAINING:
return containing(property, parameters.nextConverted(property));
case TRUE:
return QueryBuilder.eq(columnName(property), true);
case FALSE:
return QueryBuilder.eq(columnName(property), false);
case SIMPLE_PROPERTY:
return QueryBuilder.eq(columnName(property), parameters.nextConverted(property));
default:
throw new UnsupportedCassandraQueryOperatorException(String.format(""));
throw new InvalidDataAccessApiUsageException(
String.format("Unsupported Keyword: [%s] in part [%s]", type, part));
}
}
private boolean isSimpleComparisionPossible(Part part) {
private Clause containing(CassandraPersistentProperty property, Object bindableValue) {
switch (part.shouldIgnoreCase()) {
case NEVER:
return true;
case WHEN_POSSIBLE:
return part.getProperty().getType() != String.class;
case ALWAYS:
return false;
default:
return true;
}
}
/**
* Returns the next element from the given {@link Iterator} expecting it to be of a certain type.
*
* @param <T>
* @param iterator
* @param type
* @throws IllegalArgumentException in case the next element in the iterator is not of the given type.
* @return
*/
@SuppressWarnings("unchecked")
private <T> T nextAs(Iterator<Object> iterator, Class<T> type) {
Object parameter = iterator.next();
if (parameter.getClass().isAssignableFrom(type)) {
return (T) parameter;
if (property.isCollectionLike() || ClassUtils.isAssignable(Map.class, property.getType())) {
return QueryBuilder.contains(columnName(property), bindableValue);
}
throw new IllegalArgumentException(String.format("Expected parameter type of %s but got %s!", type,
parameter.getClass()));
return QueryBuilder.like(columnName(property), like(Type.CONTAINING, bindableValue));
}
private Object[] nextAsArray(Iterator<Object> iterator, CassandraPersistentProperty property) {
Object next = iterator.next(); // TODO nextConverted(property);
private Object like(Type type, Object value) {
if (value == null) {
return null;
}
if (type == Type.LIKE) {
return value;
}
if (type == Type.CONTAINING) {
return "%" + value + "%";
}
if (type == Type.STARTING_WITH) {
return value + "%";
}
if (type == Type.ENDING_WITH) {
return "%" + value;
}
throw new IllegalArgumentException(String.format("Part Type [%s] not supported with like queries", type));
}
private static String columnName(CassandraPersistentProperty property) {
return property.getColumnName().toCql();
}
private Object[] nextAsArray(CassandraPersistentProperty property, PotentiallyConvertingIterator iterator) {
Object next = iterator.nextConverted(property);
if (next instanceof Collection) {
return ((Collection<?>) next).toArray();
@@ -217,4 +222,91 @@ class CassandraQueryCreator extends AbstractQueryCreator<Select, Clause> {
return new Object[] { next };
}
/**
* Where clause builder. Collects {@link Clause clauses} and builds the where-clause depending on the WHERE type.
*
* @author Mark Paluch
*/
static class WhereBuilder {
private List<Clause> clauses = new ArrayList<Clause>();
Clause and(Clause clause) {
clauses.add(clause);
return clause;
}
Select.Where build(Select.Where where) {
for (Clause clause : clauses) {
where = where.and(clause);
}
return where;
}
}
/**
* @author Mark Paluch
*/
static class StatementBuilder {
/**
* Build a {@link Select} statement from the given {@link WhereBuilder} and {@link Sort}. Resolves property names
* for {@link Sort} using the {@link CassandraPersistentEntity}.
*
* @param whereBuilder
* @param entity
* @param sort
* @return
*/
static Select select(CassandraPersistentEntity<?> entity, WhereBuilder whereBuilder, Sort sort) {
Select select = QueryBuilder.select().from(entity.getTableName().toCql());
whereBuilder.build(select.where());
if (sort != null) {
for (Order order : sort) {
String dotPath = order.getProperty();
CassandraPersistentProperty property = getPersistentProperty(entity, dotPath);
if (order.isAscending()) {
select.orderBy(QueryBuilder.asc(columnName(property)));
} else {
select.orderBy(QueryBuilder.desc(columnName(property)));
}
}
}
return select;
}
private static CassandraPersistentProperty getPersistentProperty(CassandraPersistentEntity<?> entity,
String dotPath) {
String[] segments = PUNCTATION_PATTERN.split(dotPath);
CassandraPersistentProperty property = null;
CassandraPersistentEntity<?> currentEntity = entity;
for (String segment : segments) {
property = currentEntity.getPersistentProperty(segment);
if (property != null && property.isCompositePrimaryKey()) {
currentEntity = property.getCompositePrimaryKeyEntity();
}
}
if (property != null) {
return property;
}
throw new IllegalArgumentException(
String.format("Cannot resolve path [%s] to a property of [%s]", dotPath, entity.getName()));
}
}
}

View File

@@ -21,6 +21,7 @@ import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -28,6 +29,7 @@ import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import com.datastax.driver.core.ResultSet;
@@ -41,16 +43,9 @@ import com.datastax.driver.core.ResultSet;
*/
public class CassandraQueryMethod extends QueryMethod {
private boolean queryCached = false;
@SuppressWarnings("all")
private final CassandraMappingContext mappingContext;
private final Method method;
private Query query;
private String queryString;
private final CassandraMappingContext mappingContext;
private CassandraEntityMetadata<?> metadata;
/**
* Creates a new {@link CassandraQueryMethod} from the given {@link Method}.
@@ -85,6 +80,36 @@ public class CassandraQueryMethod extends QueryMethod {
}
}
@Override
public CassandraEntityMetadata<?> getEntityInformation() {
if (metadata == null) {
Class<?> returnedObjectType = getReturnedObjectType();
Class<?> domainClass = getDomainClass();
if (ClassUtils.isPrimitiveOrWrapper(returnedObjectType)) {
this.metadata = new SimpleCassandraEntityMetadata<Object>((Class<Object>) domainClass,
mappingContext.getPersistentEntity(domainClass));
} else {
CassandraPersistentEntity<?> returnedEntity = mappingContext.getPersistentEntity(returnedObjectType);
CassandraPersistentEntity<?> managedEntity = mappingContext.getPersistentEntity(domainClass);
returnedEntity = returnedEntity == null || returnedEntity.getType().isInterface() ? managedEntity
: returnedEntity;
CassandraPersistentEntity<?> collectionEntity = domainClass.isAssignableFrom(returnedObjectType)
? returnedEntity : managedEntity;
this.metadata = new SimpleCassandraEntityMetadata<Object>((Class<Object>) returnedEntity.getType(),
collectionEntity);
}
}
return this.metadata;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryMethod#createParameters(java.lang.reflect.Method)
*/
@@ -93,18 +118,6 @@ public class CassandraQueryMethod extends QueryMethod {
return new CassandraParameters(method);
}
/**
* Returns the {@link Query} annotation that is applied to the method or {@code null} if none available.
*/
Query getQueryAnnotation() {
if (query == null) {
query = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
queryCached = true;
}
return query;
}
/**
* Returns whether the method has an annotated query.
*/
@@ -115,15 +128,22 @@ public class CassandraQueryMethod extends QueryMethod {
/**
* Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation found
* nor the attribute was specified.
*
* @return
*/
public String getAnnotatedQuery() {
if (!queryCached) {
queryString = (String) AnnotationUtils.getValue(getQueryAnnotation());
queryString = (StringUtils.hasText(queryString) ? queryString : null);
}
String query = (String) AnnotationUtils.getValue(getQueryAnnotation());
return StringUtils.hasText(query) ? query : null;
}
return queryString;
/**
* Returns the {@link Query} annotation that is applied to the method or {@code null} if none available.
*
* @return
*/
Query getQueryAnnotation() {
return AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
}
/**

View File

@@ -15,14 +15,26 @@
*/
package org.springframework.data.cassandra.repository.query;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.DataType.CollectionType;
import com.datastax.driver.core.TypeCodec;
/**
@@ -34,10 +46,12 @@ import com.datastax.driver.core.TypeCodec;
*/
class ConvertingParameterAccessor implements CassandraParameterAccessor {
private final static TypeInformation<Set> SET = ClassTypeInformation.from(Set.class);
private final CassandraConverter cassandraConverter;
private final CassandraParameterAccessor delegate;
public ConvertingParameterAccessor(CassandraConverter cassandraConverter, CassandraParameterAccessor delegate) {
ConvertingParameterAccessor(CassandraConverter cassandraConverter, CassandraParameterAccessor delegate) {
this.cassandraConverter = cassandraConverter;
this.delegate = delegate;
@@ -72,7 +86,12 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
*/
@Override
public Object getBindableValue(int index) {
return potentiallyConvert(index, delegate.getBindableValue(index));
return potentiallyConvert(index, delegate.getBindableValue(index), null);
}
@Override
public CassandraType findCassandraType(int index) {
return delegate.findCassandraType(index);
}
/* (non-Javadoc)
@@ -80,7 +99,14 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
*/
@Override
public DataType getDataType(int index) {
return delegate.getDataType(index);
DataType dataType = delegate.getDataType(index);
if (dataType != null) {
return dataType;
}
return cassandraConverter.getMappingContext().getDataType(getParameterType(index));
}
/* (non-Javadoc)
@@ -99,11 +125,6 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
return delegate.hasBindableNullValue();
}
/*
* (non-Javadoc)
*
* @see java.lang.Iterable#iterator()
*/
/* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#iterator()
*/
@@ -111,20 +132,37 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
return new ConvertingIterator(delegate.iterator());
}
private Object potentiallyConvert(int index, Object bindableValue) {
@SuppressWarnings("unchecked")
private Object potentiallyConvert(int index, Object bindableValue, CassandraPersistentProperty property) {
if (bindableValue == null) {
return null;
}
DataType parameterType = getDataType(index);
if (parameterType == null) {
parameterType = cassandraConverter.getMappingContext().getDataType(getParameterType(index));
if (bindableValue.getClass().isArray()) {
return bindableValue;
}
DataType parameterType = getDataType(index, property);
TypeCodec<?> cassandraType = CodecRegistry.DEFAULT_INSTANCE.codecFor(parameterType);
if (property != null && getCustomConversions().hasCustomWriteTarget(property.getActualType())
&& property.isCollectionLike()) {
Class<?> customWriteTarget = getCustomConversions().getCustomWriteTarget(property.getActualType());
if (Collection.class.isAssignableFrom(property.getType()) && bindableValue instanceof Collection) {
Collection<Object> original = (Collection<Object>) bindableValue;
Collection<Object> converted = CollectionFactory.createCollection(property.getType(), original.size());
for (Object o : original) {
converted.add(getConversionService().convert(o, customWriteTarget));
}
return converted;
}
}
if (cassandraType.getJavaType().getRawType().isAssignableFrom(bindableValue.getClass())) {
return bindableValue;
}
@@ -132,12 +170,79 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
return cassandraConverter.getConversionService().convert(bindableValue, cassandraType.getJavaType().getRawType());
}
private CustomConversions getCustomConversions() {
return cassandraConverter.getCustomConversions();
}
private ConversionService getConversionService() {
return cassandraConverter.getConversionService();
}
/**
* Return the {@link DataType} based on annotated parameters with {@link CassandraType}, the
* {@link CassandraPersistentProperty} type or the declared parameter type.
*
* @param index
* @param cassandraPersistentProperty
* @return the {@link DataType}
*/
DataType getDataType(int index, CassandraPersistentProperty cassandraPersistentProperty) {
CassandraType cassandraType = delegate.findCassandraType(index);
if (cassandraType != null) {
return CassandraSimpleTypeHolder.getDataTypeFor(cassandraType.type());
}
CassandraMappingContext mappingContext = cassandraConverter.getMappingContext();
TypeInformation<?> typeInformation = ClassTypeInformation.from(getParameterType(index));
if (cassandraPersistentProperty == null) {
return mappingContext.getDataType(typeInformation.getType());
}
DataType dataType = mappingContext.getDataType(cassandraPersistentProperty);
if (cassandraPersistentProperty.isCollectionLike() && !typeInformation.isCollectionLike()) {
if (dataType instanceof CollectionType) {
CollectionType collectionType = (CollectionType) dataType;
if (collectionType.getTypeArguments().size() == 1) {
return collectionType.getTypeArguments().get(0);
}
}
}
if (!cassandraPersistentProperty.isCollectionLike() && typeInformation.isCollectionLike()) {
if (typeInformation.isAssignableFrom(SET)) {
return DataType.set(dataType);
}
return DataType.list(dataType);
}
if (cassandraPersistentProperty.isMap()) {
if (dataType instanceof CollectionType) {
CollectionType collectionType = (CollectionType) dataType;
if (collectionType.getTypeArguments().size() == 2) {
return collectionType.getTypeArguments().get(0);
}
}
}
return mappingContext.getDataType(cassandraPersistentProperty);
}
/**
* Custom {@link Iterator} to convert items before returning them.
*
* @author Mark Paluch
*/
private class ConvertingIterator implements Iterator<Object> {
private class ConvertingIterator implements PotentiallyConvertingIterator {
private final Iterator<Object> delegate;
private int index = 0;
@@ -145,9 +250,9 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
/**
* Creates a new {@link ConvertingIterator} for the given delegate.
*
* @param delegate
* @param delegate must not be {@literal null}.
*/
public ConvertingIterator(Iterator<Object> delegate) {
ConvertingIterator(Iterator<Object> delegate) {
this.delegate = delegate;
}
@@ -164,7 +269,7 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
* @see java.util.Iterator#next()
*/
public Object next() {
return potentiallyConvert(index++, next());
return potentiallyConvert(index++, delegate.next(), null);
}
/*
@@ -174,5 +279,25 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
public void remove() {
delegate.remove();
}
@Override
public Object nextConverted(CassandraPersistentProperty property) {
return potentiallyConvert(index++, delegate.next(), property);
}
}
/**
* Custom {@link Iterator} that adds a method to access elements in a converted manner.
*
* @author Mark Paluch
*/
interface PotentiallyConvertingIterator extends Iterator<Object> {
/**
* Returns the next element and pass in type information for potential conversion.
*
* @return the converted object, may be {@literal null}.
*/
Object nextConverted(CassandraPersistentProperty property);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2014-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.
@@ -24,6 +24,9 @@ import org.springframework.data.repository.query.parser.PartTree;
/**
* {@link RepositoryQuery} implementation for Cassandra.
*
* @author Matthew Adams
* @author Mark Paluch
*/
public class PartTreeCassandraQuery extends AbstractCassandraQuery {
@@ -34,13 +37,14 @@ public class PartTreeCassandraQuery extends AbstractCassandraQuery {
* Creates a new {@link PartTreeCassandraQuery} from the given {@link QueryMethod} and {@link CassandraTemplate}.
*
* @param method must not be {@literal null}.
* @param template must not be {@literal null}.
* @param operations must not be {@literal null}.
*/
public PartTreeCassandraQuery(CassandraQueryMethod method, CassandraOperations cassandraOperations) {
public PartTreeCassandraQuery(CassandraQueryMethod method, CassandraOperations operations) {
super(method, operations);
super(method, cassandraOperations);
this.tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType());
this.context = cassandraOperations.getConverter().getMappingContext();
this.context = operations.getConverter().getMappingContext();
}
/**
@@ -52,10 +56,15 @@ public class PartTreeCassandraQuery extends AbstractCassandraQuery {
return tree;
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.AbstractCassandraQuery#createQuery(org.springframework.data.cassandra.repository.query.CassandraParameterAccessor, boolean)
*/
@Override
protected String createQuery(CassandraParameterAccessor accessor) {
CassandraQueryCreator creator = new CassandraQueryCreator(tree, accessor, context);
return creator.createQuery().getQueryString();
CassandraQueryCreator creator = new CassandraQueryCreator(tree, accessor, context,
getQueryMethod().getEntityInformation());
return creator.createQuery().toString();
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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 org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.util.Assert;
/**
* Implementation of {@link CassandraEntityMetadata} based on the type and {@link CassandraPersistentEntity}.
*
* @author Mark Paluch
* @since 1.5
*/
class SimpleCassandraEntityMetadata<T> implements CassandraEntityMetadata<T> {
private final Class<T> type;
private final CassandraPersistentEntity<?> tableEntity;
/**
* Creates a new {@link SimpleCassandraEntityMetadata} using the given type and {@link CassandraPersistentEntity} to
* use for table lookups.
*
* @param type must not be {@literal null}.
* @param tableEntity must not be {@literal null} or empty.
*/
public SimpleCassandraEntityMetadata(Class<T> type, CassandraPersistentEntity<?> tableEntity) {
Assert.notNull(type, "Type must not be null!");
Assert.notNull(tableEntity, "Collection entity must not be null or empty!");
this.type = type;
this.tableEntity = tableEntity;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.CassandraEntityMetadata#getTableName()
*/
@Override
public CqlIdentifier getTableName() {
return tableEntity.getTableName();
}
/* (non-Javadoc)
* @see org.springframework.data.repository.core.EntityMetadata#getJavaType()
*/
@Override
public Class<T> getJavaType() {
return type;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors
* Copyright 2013-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.
@@ -18,13 +18,13 @@ package org.springframework.data.cassandra.repository.support;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.repository.TypedIdCassandraRepository;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
import org.springframework.data.cassandra.repository.query.CassandraQueryMethod;
import org.springframework.data.cassandra.repository.query.PartTreeCassandraQuery;
import org.springframework.data.cassandra.repository.query.StringBasedCassandraQuery;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.projection.ProjectionFactory;
@@ -43,8 +43,8 @@ import org.springframework.util.Assert;
* @author Alex Shvid
* @author Matthew T. Adams
* @author Thomas Darimont
* @author Mark Paluch
*/
public class CassandraRepositoryFactory extends RepositoryFactorySupport {
private final CassandraOperations cassandraOperations;
@@ -61,16 +61,19 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
this.cassandraOperations = cassandraOperations;
this.mappingContext = cassandraOperations.getConverter().getMappingContext();
// TODO: remove when supporting declarative query methods
setQueryLookupStrategyKey(QueryLookupStrategy.Key.USE_DECLARED_QUERY);
}
/* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
*/
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return SimpleCassandraRepository.class;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryInformation)
*/
@Override
protected Object getTargetRepository(RepositoryInformation information) {
@@ -78,6 +81,9 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
return getTargetRepositoryViaReflection(information, entityInformation, cassandraOperations);
}
/* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class)
*/
@Override
@SuppressWarnings("unchecked")
public <T, ID extends Serializable> CassandraEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
@@ -85,14 +91,17 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
CassandraPersistentEntity<?> entity = mappingContext.getPersistentEntity(domainClass);
if (entity == null) {
throw new MappingException(String.format("Could not lookup mapping metadata for domain class %s!",
domainClass.getName()));
throw new MappingException(
String.format("Could not lookup mapping metadata for domain class %s!", domainClass.getName()));
}
return new MappingCassandraEntityInformation<T, ID>((CassandraPersistentEntity<T>) entity,
cassandraOperations.getConverter());
}
/* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key)
*/
@Override
protected QueryLookupStrategy getQueryLookupStrategy(Key key) {
return new CassandraQueryLookupStrategy();
@@ -117,7 +126,7 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
} else if (queryMethod.hasAnnotatedQuery()) {
return new StringBasedCassandraQuery(queryMethod, cassandraOperations);
} else {
throw new InvalidDataAccessApiUsageException("declarative query methods are a todo");
return new PartTreeCassandraQuery(queryMethod, cassandraOperations);
}
}
}

View File

@@ -29,4 +29,5 @@ public class Person {
@Id String id;
String firstname;
String lastname;
}

View File

@@ -44,12 +44,12 @@ public class CassandraParametersUnitTests {
* @see DATACASS-296
*/
@Test
public void shouldReturnDataTypeForSimpleType() throws Exception {
public void shouldUnknownDataTypeForSimpleType() throws Exception {
Method method = PersonRepository.class.getMethod("findByFirstname", String.class);
CassandraParameters cassandraParameters = new CassandraParameters(method);
assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(DataType.varchar()));
assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(nullValue()));
}
/**
@@ -61,7 +61,8 @@ public class CassandraParametersUnitTests {
Method method = PersonRepository.class.getMethod("findByFirstTime", String.class);
CassandraParameters cassandraParameters = new CassandraParameters(method);
assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(DataType.time()));
assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(notNullValue()));
assertThat(cassandraParameters.getParameter(0).getCassandraType().type(), is(Name.TIME));
}
/**
@@ -85,7 +86,8 @@ public class CassandraParametersUnitTests {
Method method = PersonRepository.class.getMethod("findByAnnotatedObject", Object.class);
CassandraParameters cassandraParameters = new CassandraParameters(method);
assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(DataType.time()));
assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(notNullValue()));
assertThat(cassandraParameters.getParameter(0).getCassandraType().type(), is(Name.TIME));
}
interface PersonRepository {

View File

@@ -0,0 +1,408 @@
/*
* 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.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.cassandra.repository.query.StubParameterAccessor.*;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.Column;
import org.springframework.data.cassandra.mapping.PrimaryKey;
import org.springframework.data.cassandra.mapping.PrimaryKeyClass;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
import org.springframework.data.repository.core.EntityMetadata;
import org.springframework.data.repository.query.parser.PartTree;
/**
* Unit tests for {@link CassandraQueryCreator}.
*
* @author Mark Paluch
* @soundtrack Odyssey - Everybody Move 9Club Mix
*/
public class CassandraQueryCreatorUnitTests {
CassandraMappingContext context;
CassandraConverter converter;
@Rule public ExpectedException expection = ExpectedException.none();
@Before
public void setUp() throws SecurityException, NoSuchMethodException {
context = new BasicCassandraMappingContext();
converter = new MappingCassandraConverter(context);
}
/**
* @see DATACASS-7
*/
@Test
public void createsQueryCorrectly() {
String query = createQuery("findByFirstname", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname='Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsQueryWithSortCorrectly() {
String query = createQuery("findByFirstnameOrderByLastname", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname='Walter' ORDER BY lastname ASC;")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsAndQueryCorrectly() {
String query = createQuery("findByFirstnameAndLastname", Person.class, "Walter", "White");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname='Walter' AND lastname='White';")));
}
/**
* @see DATACASS-7
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
public void rejectsNegatingQueryQuery() {
createQuery("findByFirstnameNot", Person.class, "Walter");
}
/**
* @see DATACASS-7
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
public void rejectsOrQuery() {
createQuery("findByFirstnameOrLastname", Person.class, "Walter", "White");
}
/**
* @see DATACASS-7
*/
@Test
public void createsGreaterThanQueryCorrectly() {
String query = createQuery("findByFirstnameGreaterThan", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname>'Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsGreaterThanEqualQueryCorrectly() {
String query = createQuery("findByFirstnameGreaterThanEqual", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname>='Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsLessThanQueryCorrectly() {
String query = createQuery("findByFirstnameLessThan", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname<'Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsLessThanEqualQueryCorrectly() {
String query = createQuery("findByFirstnameLessThanEqual", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname<='Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsInQueryCorrectly() {
String query = createQuery("findByFirstnameIn", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname IN ('Walter');")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsInQueryWithListCorrectly() {
String query = createQuery("findByFirstnameIn", Person.class, Arrays.asList("Walter", "Gus"));
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname IN ('Walter','Gus');")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsInQueryWithArrayCorrectly() {
String query = createQuery("findByFirstnameInAndLastname", Person.class, new String[] { "Walter", "Gus" }, "Fring");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname IN ('Walter','Gus') AND lastname='Fring';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsLikeQueryCorrectly() {
assertThat(createQuery("findByFirstnameLike", Person.class, "Wal%ter"),
is(equalTo("SELECT * FROM person WHERE firstname LIKE 'Wal%ter';")));
assertThat(createQuery("findByFirstnameLike", Person.class, "Walter"),
is(equalTo("SELECT * FROM person WHERE firstname LIKE 'Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsStartsWithQueryCorrectly() {
String query = createQuery("findByFirstnameStartsWith", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname LIKE 'Walter%';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsEndsWithQueryCorrectly() {
String query = createQuery("findByFirstnameEndsWith", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname LIKE '%Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsContainsQueryOnSimplePropertyCorrectly() {
String query = createQuery("findByFirstnameContains", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname LIKE '%Walter%';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsContainsQueryOnSetPropertyCorrectly() {
String query = createQuery("findByMysetContains", TypeWithSet.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM typewithset WHERE myset CONTAINS 'Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsContainsQueryOnListPropertyCorrectly() {
String query = createQuery("findByMylistContains", TypeWithList.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM typewithlist WHERE mylist CONTAINS 'Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsContainsQueryOnMapPropertyCorrectly() {
String query = createQuery("findByMymapContains", TypeWithMap.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM typewithmap WHERE mymap CONTAINS 'Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsIsTrueQueryCorrectly() {
String query = createQuery("findByFirstnameIsTrue", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname=true;")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsIsFalseQueryCorrectly() {
String query = createQuery("findByFirstnameIsFalse", Person.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname=false;")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsQueryUsingQuotingCorrectly() {
String query = createQuery("findByIdAndSet", QuotedType.class, "Walter", "White");
assertThat(query, is(equalTo("SELECT * FROM \"myTable\" WHERE \"my_id\"='Walter' AND \"set\"='White';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsFindByPrimaryKeyPartCorrectly() {
String query = createQuery("findByKeyFirstname", TypeWithCompositeId.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM typewithcompositeid WHERE firstname='Walter';")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsFindByPrimaryKeyPartWithSortCorrectly() {
String query = createQuery("findByKeyFirstnameOrderByKeyLastnameAsc", TypeWithCompositeId.class, "Walter");
assertThat(query, is(equalTo("SELECT * FROM typewithcompositeid WHERE firstname='Walter' ORDER BY lastname ASC;")));
}
/**
* @see DATACASS-7
*/
@Test
public void createsFindByPrimaryKeyPartOfPrimaryKeyClassCorrectly() {
String query = createQuery("findByFirstname", Key.class, "Walter");
// ⊙_ʘ rly? ヾ( •́д•̀ ;)ノ
assertThat(query, is(equalTo("SELECT * FROM key WHERE firstname='Walter';")));
}
/**
* @see DATACASS-7
*/
@Test(expected = IllegalStateException.class)
public void createsFindByPrimaryKey2PartCorrectly() {
createQuery("findByKey", TypeWithCompositeId.class, new Key());
}
private String createQuery(String source, Class<?> entityClass, Object... values) {
PartTree tree = new PartTree(source, entityClass);
CassandraQueryCreator creator = new CassandraQueryCreator(tree, getAccessor(converter, values), context,
getEntityInformation(entityClass));
return creator.createQuery().toString();
}
private <T> EntityMetadata<T> getEntityInformation(final Class<T> entityClass) {
return new EntityMetadata<T>() {
@Override
public Class<T> getJavaType() {
return entityClass;
}
};
}
@Table
private static class TypeWithSet {
@Id String id;
Set<String> myset;
}
@Table
private static class TypeWithList {
@Id String id;
List<String> mylist;
}
@Table
private static class TypeWithMap {
@Id String id;
Map<String, String> mymap;
}
@Table(value = "myTable", forceQuote = true)
private static class QuotedType {
@PrimaryKey(value = "my_id", forceQuote = true) String id;
@Column(value = "set") Set<String> set;
}
@PrimaryKeyClass
private static class Key implements Serializable {
@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 1) String firstname;
@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 1) String lastname;
}
@Table
private static class TypeWithCompositeId {
@PrimaryKey Key key;
String city;
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.Matchers.*;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
/**
* Unit tests for {@link CassandraQueryMethod}.
*
* @author Mark Paluch
*/
public class CassandraQueryMethodUnitTests {
CassandraMappingContext context;
@Before
public void setUp() {
context = new BasicCassandraMappingContext();
}
/**
* @see DATACASS-7
*/
@Test
public void detectsCollectionFromRepoTypeIfReturnTypeNotAssignable() throws Exception {
CassandraQueryMethod queryMethod = queryMethod(SampleRepository.class, "method");
CassandraEntityMetadata<?> metadata = queryMethod.getEntityInformation();
assertThat(metadata.getJavaType(), is(typeCompatibleWith(Person.class)));
assertThat(metadata.getTableName().toCql(), is("person"));
}
/**
* @see DATACASS-7
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsNullMappingContext() throws Exception {
Method method = SampleRepository.class.getMethod("method");
new CassandraQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
new SpelAwareProxyProjectionFactory(), null);
}
/**
* @see DATACASS-7
*/
@Test
public void considersMethodAsCollectionQuery() throws Exception {
CassandraQueryMethod queryMethod = queryMethod(SampleRepository.class, "method");
assertThat(queryMethod.isCollectionQuery(), is(true));
}
private CassandraQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters) throws Exception {
Method method = repository.getMethod(name, parameters);
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
return new CassandraQueryMethod(method, new DefaultRepositoryMetadata(repository), factory, context);
}
interface SampleRepository extends Repository<Person, Long> {
List<Person> method();
}
}

View File

@@ -20,6 +20,10 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
@@ -28,6 +32,9 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.cassandra.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator;
import com.datastax.driver.core.DataType;
@@ -41,14 +48,16 @@ import com.datastax.driver.core.DataType;
public class ConvertingParameterAccessorUnitTests {
@Mock CassandraParameterAccessor delegateMock;
@Mock CassandraPersistentProperty propertyMock;
MappingCassandraConverter converter;
ConvertingParameterAccessor accessor;
@Before
public void setUp() {
this.converter = new MappingCassandraConverter(new BasicCassandraMappingContext());
this.converter.afterPropertiesSet();
this.accessor = new ConvertingParameterAccessor(converter, delegateMock);
}
/**
@@ -56,9 +65,6 @@ public class ConvertingParameterAccessorUnitTests {
*/
@Test
public void shouldReturnNullBindableValue() {
ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter, delegateMock);
assertThat(accessor.getBindableValue(0), is(nullValue()));
}
@@ -66,12 +72,12 @@ public class ConvertingParameterAccessorUnitTests {
* @see DATACASS-296
*/
@Test
@SuppressWarnings({"rawtypes", "unchecked"})
public void shouldReturnNativeBindableValue() {
ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter, delegateMock);
when(delegateMock.getBindableValue(0)).thenReturn("hello");
when(delegateMock.getDataType(0)).thenReturn(DataType.varchar());
when(delegateMock.getParameterType(0)).thenReturn((Class) String.class);
assertThat(accessor.getBindableValue(0), is(equalTo((Object) "hello")));
}
@@ -80,11 +86,9 @@ public class ConvertingParameterAccessorUnitTests {
* @see DATACASS-296
*/
@Test
@SuppressWarnings("rawtypes")
@SuppressWarnings({"rawtypes", "unchecked"})
public void shouldReturnConvertedBindableValue() {
ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter, delegateMock);
LocalDate localDate = LocalDate.of(2010, 7, 4);
when(delegateMock.getBindableValue(0)).thenReturn(localDate);
@@ -93,4 +97,70 @@ public class ConvertingParameterAccessorUnitTests {
assertThat(accessor.getBindableValue(0),
is(equalTo((Object) com.datastax.driver.core.LocalDate.fromYearMonthDay(2010, 7, 4))));
}
/**
* @see DATACASS-296
* @see DATACASS-7
*/
@Test
public void shouldReturnDataTypeProvidedByDelegate() {
when(delegateMock.getDataType(0)).thenReturn(DataType.varchar());
assertThat(accessor.getDataType(0), is(equalTo(DataType.varchar())));
}
/**
* @see DATACASS-296
* @see DATACASS-7
*/
@Test
@SuppressWarnings({"rawtypes", "unchecked"})
public void shouldConvertCollections() {
LocalDate localDate = LocalDate.of(2010, 7, 4);
when(delegateMock.iterator()).thenReturn((Iterator) Arrays.asList(Collections.singletonList(localDate)).iterator());
when(delegateMock.getDataType(0)).thenReturn(DataType.list(DataType.date()));
when(delegateMock.getParameterType(0)).thenReturn((Class) List.class);
when(propertyMock.getType()).thenReturn((Class) List.class);
when(propertyMock.getActualType()).thenReturn((Class) LocalDate.class);
when(propertyMock.isCollectionLike()).thenReturn(true);
PotentiallyConvertingIterator iterator = (PotentiallyConvertingIterator) accessor.iterator();
Object converted = iterator.nextConverted(propertyMock);
assertThat(converted, is(instanceOf(List.class)));
List<?> list = (List<?>) converted;
assertThat(list.get(0), is(instanceOf(com.datastax.driver.core.LocalDate.class)));
}
/**
* @see DATACASS-7
*/
@Test
@SuppressWarnings({"rawtypes", "unchecked"})
public void shouldProvideTypeBasedOnValue() {
when(delegateMock.getDataType(0)).thenReturn(null);
when(delegateMock.getParameterType(0)).thenReturn((Class) LocalDate.class);
assertThat(accessor.getDataType(0), is(equalTo(DataType.date())));
}
/**
* @see DATACASS-7
*/
@Test
@SuppressWarnings("rawtypes")
public void shouldProvideTypeBasedOnPropertyType() {
when(propertyMock.getDataType()).thenReturn(DataType.varchar());
when(propertyMock.findAnnotation(CassandraType.class)).thenReturn(mock(CassandraType.class));
when(delegateMock.getParameterType(0)).thenReturn((Class) String.class);
when(delegateMock.getDataType(0)).thenReturn(null);
assertThat(accessor.getDataType(0, propertyMock), is(equalTo(DataType.varchar())));
}
}

View File

@@ -0,0 +1,168 @@
/*
* 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.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
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.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.domain.Person;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
/**
* Unit tests for {@link PartTreeCassandraQuery}.
*
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class PartTreeCassandraQueryUnitTests {
public @Rule ExpectedException exception = ExpectedException.none();
@Mock CassandraOperations cassandraOperationsMock;
CassandraMappingContext mappingContext;
CassandraConverter converter;
@Before
public void setUp() {
mappingContext = new BasicCassandraMappingContext();
converter = new MappingCassandraConverter(mappingContext);
when(cassandraOperationsMock.getConverter()).thenReturn(converter);
}
/**
* @see DATACASS-7
*/
@Test
public void shouldDeriveSimpleQuery() {
String query = deriveQueryFromMethod("findByLastname", "foo");
assertThat(query, is(equalTo("SELECT * FROM person WHERE lastname='foo';")));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldDeriveSimpleQueryWithoutNames() {
String query = deriveQueryFromMethod("findPersonBy");
assertThat(query, is(equalTo("SELECT * FROM person;")));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldDeriveAndQuery() {
String query = deriveQueryFromMethod("findByFirstnameAndLastname", "foo", "bar" );
assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname='foo' AND lastname='bar';")));
}
/**
* @see DATACASS-7
*/
@Test
public void usesDynamicProjection() {
String query = deriveQueryFromMethod("findDynamicallyProjectedBy", PersonProjection.class);
assertThat(query, is(equalTo("SELECT * FROM person;")));
}
private String deriveQueryFromMethod(String method, Object... args) {
Class<?>[] types = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
types[i] = args[i].getClass();
}
PartTreeCassandraQuery partTreeQuery = createQueryForMethod(method, types);
CassandraParameterAccessor accessor = new CassandraParametersParameterAccessor(partTreeQuery.getQueryMethod(), args);
return partTreeQuery.createQuery(new ConvertingParameterAccessor(cassandraOperationsMock.getConverter(), accessor));
}
private PartTreeCassandraQuery createQueryForMethod(String methodName, Class<?>... paramTypes) {
try {
Method method = Repo.class.getMethod(methodName, paramTypes);
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
CassandraQueryMethod queryMethod = new CassandraQueryMethod(method, new DefaultRepositoryMetadata(Repo.class), factory,
mappingContext);
return new PartTreeCassandraQuery(queryMethod, cassandraOperationsMock);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(e.getMessage(), e);
} catch (SecurityException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
@SuppressWarnings("unused")
interface Repo extends CassandraRepository<Person> {
@Query()
Person findByLastname(String lastname);
Person findByFirstnameAndLastname(String firstname, String lastname);
Person findPersonByFirstnameAndLastname(String firstname, String lastname);
Person findByAge(Integer age);
Person findPersonBy();
PersonProjection findPersonProjectedBy();
<T> T findDynamicallyProjectedBy(Class<T> type);
}
interface PersonProjection {
String getFirstname();
String getLastname();
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.Arrays;
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;
import com.datastax.driver.core.DataType;
/**
* Simple {@link ParameterAccessor} that returns the given parameters unfiltered.
*
* @author Mark Paluch
*/
class StubParameterAccessor implements CassandraParameterAccessor {
private final Object[] values;
/**
* Creates a new {@link ConvertingParameterAccessor} backed by a {@link StubParameterAccessor} simply returning the
* given parameters converted but unfiltered.
*
* @param converter
* @param parameters
* @return
*/
public static ConvertingParameterAccessor getAccessor(CassandraConverter converter, Object... parameters) {
return new ConvertingParameterAccessor(converter, new StubParameterAccessor(parameters));
}
@SuppressWarnings("unchecked")
public StubParameterAccessor(Object... values) {
this.values = values;
}
@Override
public DataType getDataType(int index) {
return CodecRegistry.DEFAULT_INSTANCE.codecFor(values[index]).getCqlType();
}
@Override
public Class<?> getParameterType(int index) {
return values[index].getClass();
}
@Override
public Pageable getPageable() {
return null;
}
@Override
public Sort getSort() {
return null;
}
@Override
public Class<?> getDynamicProjection() {
return null;
}
@Override
public Object getBindableValue(int index) {
return values[index];
}
@Override
public boolean hasBindableNullValue() {
return false;
}
@Override
public Iterator<Object> iterator() {
return Arrays.asList(values).iterator();
}
@Override
public CassandraType findCassandraType(int index) {
return null;
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.support;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
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.core.CassandraTemplate;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
import org.springframework.data.repository.Repository;
/**
* Unit tests for {@link CassandraRepositoryFactory}.
*
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
@SuppressWarnings({ "rawtypes", "unchecked" })
public class CassandraRepositoryFactoryUnitTests {
@Mock CassandraTemplate template;
@Mock CassandraConverter converter;
@Mock CassandraMappingContext mappingContext;
@Mock CassandraPersistentEntity entity;
@Before
public void setUp() {
when(template.getConverter()).thenReturn(converter);
when(converter.getMappingContext()).thenReturn(mappingContext);
}
/**
* @see DATACASS-7
*/
@Test
public void usesMappingCassandraEntityInformationIfMappingContextSet() {
when(mappingContext.getPersistentEntity(Person.class)).thenReturn(entity);
when(entity.getType()).thenReturn(Person.class);
CassandraRepositoryFactory factory = new CassandraRepositoryFactory(template);
CassandraEntityInformation<Person, Serializable> entityInformation = factory.getEntityInformation(Person.class);
assertTrue(entityInformation instanceof MappingCassandraEntityInformation);
}
/**
* @see DATACASS-7
*/
@Test
public void createsRepositoryWithIdTypeLong() {
when(mappingContext.getPersistentEntity(Person.class)).thenReturn(entity);
when(entity.getType()).thenReturn(Person.class);
CassandraRepositoryFactory factory = new CassandraRepositoryFactory(template);
MyPersonRepository repository = factory.getRepository(MyPersonRepository.class);
assertThat(repository, is(notNullValue()));
}
interface MyPersonRepository extends Repository<Person, Long> {
}
}

View File

@@ -246,7 +246,7 @@ public class AsynchronousCassandraTemplateIntegrationTests extends AbstractSprin
@AllArgsConstructor
@NoArgsConstructor
@SuppressWarnings("unused")
public static class Person {
static class Person {
@PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) String id;
@Column String firstname;

View File

@@ -734,7 +734,11 @@ public class CassandraOperationsIntegrationTests extends AbstractSpringDataEmbed
* @see <a href="https://jira.spring.io/browse/DATACASS-182">DATACASS-182</a>
*/
@Test
public void stream() {
public void stream() throws InterruptedException {
while(template.select("SELECT * FROM book", Book.class).size() != 0){
Thread.sleep(10);
}
template.insert(getBookList(20));

View File

@@ -0,0 +1,32 @@
/*
* 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.test.integration.repository.querymethods.conversion;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Mark Paluch
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class Address {
String city;
String country;
}

View File

@@ -0,0 +1,42 @@
/*
* 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.test.integration.repository.querymethods.conversion;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.mapping.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Mark Paluch
*/
@Table
@Data
@NoArgsConstructor
class Contact {
@Id String id;
Address address;
List<Address> addresses;
public Contact(String id) {
this.id = id;
}
}

View File

@@ -0,0 +1,178 @@
/*
* 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.test.integration.repository.querymethods.conversion;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.base.PersonRepository;
import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
/**
* Integration tests for query derivation through {@link PersonRepository}.
*
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ParameterConversionIntegrationTests extends AbstractSpringDataEmbeddedCassandraIntegrationTest {
@Configuration
@EnableCassandraRepositories(considerNestedRepositories = true)
public static class Config extends IntegrationTestConfig {
@Override
public String[] getEntityBasePackages() {
return new String[] { Contact.class.getPackage().getName() };
}
@Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE_DROP_UNUSED;
}
@Override
public CustomConversions customConversions() {
return new CustomConversions(Arrays.asList(AddressReadConverter.INSTANCE, AddressWriteConverter.INSTANCE));
}
}
@Autowired CassandraOperations template;
@Autowired ContactRepository contactRepository;
Contact walter, flynn;
@Before
public void before() {
deleteAllEntities();
template.execute("CREATE INDEX IF NOT EXISTS contact_address ON contact (address);");
template.execute("CREATE INDEX IF NOT EXISTS contact_addresses ON contact (addresses);");
walter = new Contact("Walter");
walter.setAddress(new Address("Albuquerque", "USA"));
walter.setAddresses(Arrays.asList(new Address("Albuquerque", "USA"), new Address("New Hampshire", "USA"),
new Address("Grocery Store", "Mexico")));
flynn = new Contact("Flynn");
flynn.setAddress(new Address("Albuquerque", "USA"));
flynn.setAddresses(Collections.singletonList(new Address("Albuquerque", "USA")));
walter = contactRepository.save(walter);
flynn = contactRepository.save(flynn);
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByConvertedParameter() {
List<Contact> contacts = contactRepository.findByAddress(walter.getAddress());
assertThat(contacts, hasItems(walter, flynn));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByStringParameter() {
String parameter = AddressWriteConverter.INSTANCE.convert(walter.getAddress());
List<Contact> contacts = contactRepository.findByAddress(parameter);
assertThat(contacts, hasItems(walter, flynn));
}
/**
* @see DATACASS-7
*/
@Test
public void findByAddressesIn() {
assertThat(contactRepository.findByAddressesContains(flynn.address), containsInAnyOrder(flynn, walter));
assertThat(contactRepository.findByAddressesContains(walter.addresses.get(1)), contains(walter));
}
interface ContactRepository extends CassandraRepository<Contact> {
List<Contact> findByAddress(Address address);
List<Contact> findByAddress(String address);
List<Contact> findByAddressesContains(Address address);
}
/**
* @author Mark Paluch
*/
static enum AddressReadConverter implements Converter<String, Address> {
INSTANCE;
public Address convert(String source) {
if (StringUtils.hasText(source)) {
try {
return new ObjectMapper().readValue(source, Address.class);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
return null;
}
}
/**
* @author Mark Paluch
*/
static enum AddressWriteConverter implements Converter<Address, String> {
INSTANCE;
public String convert(Address source) {
try {
return new ObjectMapper().writeValueAsString(source);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
}

View File

@@ -20,17 +20,18 @@ import java.time.ZoneId;
import java.util.Date;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.data.cassandra.mapping.Indexed;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Sample domain class.
*/
@Table
@Data
@NoArgsConstructor
public class Person {
@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 0) private String lastname;
@@ -44,4 +45,10 @@ public class Person {
private LocalDate createdDate;
private ZoneId zoneId;
public Person(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.test.integration.repository.querymethods.derived;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
import org.springframework.data.domain.Sort;
/**
* @author Mark Paluch
*/
interface PersonRepository extends CassandraRepository<Person> {
List<Person> findByLastname(String lastname);
List<Person> findByLastname(String lastname, Sort sort);
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
Person findByFirstnameAndLastname(String firstname, String lastname);
Person findByCreatedDate(LocalDate createdDate);
Person findByNicknameStartsWith(String prefix);
Person findByNicknameContains(String contains);
Person findByNumberOfChildren(NumberOfChildren numberOfChildren);
Collection<PersonProjection> findPersonProjectedBy();
@Query("select * from person where firstname = ?0 and lastname = 'White'")
List<Person> findByFirstname(String firstname);
enum NumberOfChildren {
ZERO, ONE, TWO,
}
interface PersonProjection {
String getFirstname();
String getLastname();
}
}

View File

@@ -0,0 +1,237 @@
/*
* 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.test.integration.repository.querymethods.derived;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.SpringVersion;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
import org.springframework.data.cassandra.test.integration.repository.querymethods.derived.PersonRepository.NumberOfChildren;
import org.springframework.data.cassandra.test.integration.repository.querymethods.derived.PersonRepository.PersonProjection;
import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
import org.springframework.data.cassandra.test.integration.support.CassandraVersion;
import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig;
import org.springframework.data.domain.Sort;
import org.springframework.data.util.Version;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration tests for query derivation through {@link PersonRepository}.
*
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedCassandraIntegrationTest {
@Configuration
@EnableCassandraRepositories
public static class Config extends IntegrationTestConfig {
@Override
public String[] getEntityBasePackages() {
return new String[] { Person.class.getPackage().getName() };
}
@Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE_DROP_UNUSED;
}
}
@Autowired CassandraOperations template;
@Autowired PersonRepository personRepository;
Person walter, skyler, flynn;
@Before
public void before() {
deleteAllEntities();
Person person = new Person("Walter", "White");
person.setNumberOfChildren(2);
walter = personRepository.save(person);
skyler = personRepository.save(new Person("Skyler", "White"));
flynn = personRepository.save(new Person("Flynn (Walter Jr.)", "White"));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByLastname() {
List<Person> result = personRepository.findByLastname("White");
assertThat(result, hasItems(walter, skyler, flynn));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByLastnameAndDynamicSort() {
List<Person> result = personRepository.findByLastname("White", new Sort("firstname"));
assertThat(result, contains(flynn, skyler, walter));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByLastnameWithOrdering() {
List<Person> result = personRepository.findByLastnameOrderByFirstnameAsc("White");
assertThat(result, contains(flynn, skyler, walter));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByFirstnameAndLastname() {
Person result = personRepository.findByFirstnameAndLastname("Walter", "White");
assertThat(result, is(walter));
}
/**
* @see DATACASS-7
*/
@Test
public void executesCollectionQueryWithProjectionCorrectly() {
Collection<PersonProjection> collection = personRepository.findPersonProjectedBy();
assertThat(collection, hasSize(3));
for (PersonProjection personProjection : collection) {
assertThat(personProjection.getLastname(), is(equalTo("White")));
}
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByNumberOfChildren() throws Exception {
assumeThat(SpringVersion.getVersion(), startsWith("4.3"));
template.execute("CREATE INDEX IF NOT EXISTS person_number_of_children ON person (numberofchildren);");
// Give Cassandra some time to build the index
Thread.sleep(500);
Person result = personRepository.findByNumberOfChildren(NumberOfChildren.TWO);
assertThat(result, is(walter));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldFindByLocalDate() throws InterruptedException {
template.execute("CREATE INDEX IF NOT EXISTS person_created_date ON person (createddate);");
// Give Cassandra some time to build the index
Thread.sleep(500);
walter.setCreatedDate(LocalDate.now());
personRepository.save(walter);
Person result = personRepository.findByCreatedDate(walter.getCreatedDate());
assertThat(result, is(walter));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldUseQueryOverride() {
Person otherWalter = new Person("Walter", "Black");
personRepository.save(otherWalter);
List<Person> result = personRepository.findByFirstname("Walter");
assertThat(result, hasSize(1));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldUseStartsWithQuery() throws InterruptedException {
Version version = CassandraVersion.get(template.getSession());
assumeTrue(version.isGreaterThanOrEqualTo(Version.parse("3.4")));
template.execute(
"CREATE CUSTOM INDEX IF NOT EXISTS fn_starts_with ON person (nickname) USING 'org.apache.cassandra.index.sasi.SASIIndex';");
// Give Cassandra some time to build the index
Thread.sleep(500);
walter.setNickname("Heisenberg");
personRepository.save(walter);
assertThat(personRepository.findByNicknameStartsWith("Heis"), is(walter));
}
/**
* @see DATACASS-7
*/
@Test
public void shouldUseContainsQuery() throws InterruptedException {
Version version = CassandraVersion.get(template.getSession());
assumeTrue(version.isGreaterThanOrEqualTo(Version.parse("3.4")));
template.execute(
"CREATE CUSTOM INDEX IF NOT EXISTS fn_contains ON person (nickname) USING 'org.apache.cassandra.index.sasi.SASIIndex'\n"
+ "WITH OPTIONS = { 'mode': 'CONTAINS' };");
// Give Cassandra some time to build the index
Thread.sleep(500);
walter.setNickname("Heisenberg");
personRepository.save(walter);
assertThat(personRepository.findByNicknameContains("eisenber"), is(walter));
}
}

View File

@@ -39,6 +39,11 @@ public abstract class AbstractSpringDataEmbeddedCassandraIntegrationTest
*/
public void deleteAllEntities() {
for (CassandraPersistentEntity<?> entity : template.getConverter().getMappingContext().getPersistentEntities()) {
if(entity.getType().isInterface()){
continue;
}
template.truncate(entity.getTableName());
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.test.integration.support;
import lombok.experimental.UtilityClass;
import org.springframework.data.util.Version;
import org.springframework.util.Assert;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
/**
* Utility to retrieve the Cassandra release version.
*
* @author Mark Paluch
*/
@UtilityClass
public class CassandraVersion {
/**
* Retrieve the Cassandra release version.
*
* @param session must not be {@literal null}.
* @return the release {@link Version}.
*/
public static Version get(Session session) {
Assert.notNull(session, "Session must not be null");
ResultSet resultSet = session.execute("SELECT release_version FROM system.local;");
Row row = resultSet.one();
return Version.parse(row.getString(0));
}
}

View File

@@ -1,34 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/data/cassandra"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
">
<import resource="classpath:/config/spring-data-cassandra-basic.xml" />
<cass:mapping
entity-base-packages="org.springframework.data.cassandra.test.integration.repository">
entity-base-packages="org.springframework.data.cassandra.test.integration.repository.simple">
<cass:entity
class="org.springframework.data.cassandra.test.integration.repository.simple.User">
<cass:table name="users" />