Skip to content

Commit

Permalink
DATACASS-7 - Support query derivation in Cassandra repositories.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mp911de authored and jxblum committed Jul 25, 2016
1 parent 6e176ba commit 43fc751
Show file tree
Hide file tree
Showing 33 changed files with 2,180 additions and 227 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ public void setCustomConversions(CustomConversions conversions) {
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()
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
* @author Alex Shvid
* @author Matthew T. Adams
* @author Antoine Toulme
* @author Mark Paluch
*/
public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentProperty<CassandraPersistentProperty>
implements CassandraPersistentProperty, ApplicationContextAware {
Expand Down Expand Up @@ -129,7 +130,7 @@ public CqlIdentifier getColumnName() {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand All @@ -28,6 +29,7 @@
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;
Expand All @@ -38,6 +40,7 @@
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;

Expand Down Expand Up @@ -65,30 +68,43 @@ 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);

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());
}

/**
Expand Down Expand Up @@ -167,6 +183,7 @@ public Object getSingleEntity(ResultSet resultSet, Class<?> type) {
return object;
}

@Deprecated
protected void warnIfMoreResults(Iterator<Row> iterator) {
if (log.isWarnEnabled() && iterator.hasNext()) {

Expand All @@ -180,6 +197,7 @@ protected void warnIfMoreResults(Iterator<Row> iterator) {
}
}

@Deprecated
public ConversionService getConversionService() {
return template.getConverter().getConversionService();
}
Expand All @@ -200,4 +218,48 @@ public void setConversionService(ConversionService conversionService) {
* @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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}
Expand All @@ -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).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ protected CassandraParameters createFrom(List<CassandraParameter> parameters) {
*/
class CassandraParameter extends Parameter {

private final DataType dataType;
private final CassandraType cassandraType;

protected CassandraParameter(MethodParameter parameter) {

Expand All @@ -87,22 +87,19 @@ protected CassandraParameter(MethodParameter parameter) {
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -39,19 +41,28 @@ public CassandraParametersParameterAccessor(CassandraQueryMethod method, Object.
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)
*/
Expand Down
Loading

0 comments on commit 43fc751

Please sign in to comment.