DATACASS-376 - Support ALLOW FILTERING using derived query methods.

We now support allow filtering using derived query methods by annotating query methods with AllowFiltering.

interface PersonRepository extends Repository<Person, String> {

    @AllowFiltering
    List<Person> findAllByFirstname(String firstname);
}
This commit is contained in:
Mark Paluch
2017-07-05 15:55:15 +02:00
committed by John Blum
parent 64a0380e86
commit add94d2ea6
7 changed files with 84 additions and 18 deletions

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2017 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;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to declare filtering for a derived query.
*
* @author Mark Paluch
* @since 2.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
@Query(allowFiltering = true)
public @interface AllowFiltering {
}

View File

@@ -39,8 +39,13 @@ public @interface Query {
/**
* A Cassandra CQL3 string to define the actual query to be executed. Placeholders {@code ?0}, {@code ?1}, etc are
* supported.
*
* @return
*/
String value() default "";
/**
* Specifies whether to allow filtering using query derivation without a {@link #value() string query}.
*
* @since 2.0
*/
boolean allowFiltering() default false;
}

View File

@@ -19,7 +19,6 @@ import java.lang.reflect.Method;
import java.util.Optional;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
@@ -50,6 +49,8 @@ public class CassandraQueryMethod extends QueryMethod {
private final MappingContext<? extends CassandraPersistentEntity<?>, ? extends CassandraPersistentProperty> mappingContext;
private final Optional<Query> query;
private CassandraEntityMetadata<?> entityMetadata;
/**
@@ -71,6 +72,7 @@ public class CassandraQueryMethod extends QueryMethod {
this.method = method;
this.mappingContext = mappingContext;
this.query = Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, Query.class));
}
/**
@@ -142,7 +144,7 @@ public class CassandraQueryMethod extends QueryMethod {
* Returns whether the method has an annotated query.
*/
public boolean hasAnnotatedQuery() {
return findAnnotatedQuery().isPresent();
return query.map(Query::value).filter(StringUtils::hasText).isPresent();
}
/**
@@ -152,16 +154,7 @@ public class CassandraQueryMethod extends QueryMethod {
* @return
*/
public String getAnnotatedQuery() {
return findAnnotatedQuery().orElse(null);
}
private Optional<String> findAnnotatedQuery() {
Optional<Query> queryAnnotation = Optional.ofNullable(getQueryAnnotation());
return queryAnnotation.map(AnnotationUtils::getValue) //
.map(it -> (String) it) //
.filter(StringUtils::hasText);
return query.map(Query::value).orElse(null);
}
/**
@@ -169,8 +162,8 @@ public class CassandraQueryMethod extends QueryMethod {
*
* @return
*/
Query getQueryAnnotation() {
return AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
Optional<Query> getQueryAnnotation() {
return query;
}
@Override

View File

@@ -102,8 +102,13 @@ public class PartTreeCassandraQuery extends AbstractCassandraQuery {
Query query = queryCreator.createQuery();
try {
if (getTree().isLimiting()) {
query.limit(getTree().getMaxResults());
query = query.limit(getTree().getMaxResults());
}
if (getQueryMethod().getQueryAnnotation().map(q -> q.allowFiltering()).orElse(false)) {
query = query.withAllowFiltering();
}
CassandraPersistentEntity<?> persistentEntity = getMappingContext()

View File

@@ -102,8 +102,13 @@ public class ReactivePartTreeCassandraQuery extends AbstractReactiveCassandraQue
Query query = queryCreator.createQuery();
try {
if (getTree().isLimiting()) {
query.limit(getTree().getMaxResults());
query = query.limit(getTree().getMaxResults());
}
if (getQueryMethod().getQueryAnnotation().map(q -> q.allowFiltering()).orElse(false)) {
query = query.withAllowFiltering();
}
CassandraPersistentEntity<?> persistentEntity = getMappingContext()

View File

@@ -38,6 +38,7 @@ import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
import org.springframework.data.cassandra.domain.AddressType;
import org.springframework.data.cassandra.domain.Group;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.repository.AllowFiltering;
import org.springframework.data.cassandra.repository.MapIdCassandraRepository;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cql.core.CqlIdentifier;
@@ -155,6 +156,14 @@ public class PartTreeCassandraQueryUnitTests {
assertThat(query.toString()).isEqualTo("SELECT * FROM group WHERE hash_prefix='foo';");
}
@Test // DATACASS-376
public void shouldAllowFiltering() {
Statement query = deriveQueryFromMethod(Repo.class, "findByFirstname", new Class[] { String.class }, "foo");
assertThat(query.toString()).isEqualTo("SELECT * FROM person WHERE firstname='foo' ALLOW FILTERING;");
}
private String deriveQueryFromMethod(String method, Object... args) {
Class<?>[] types = new Class<?>[args.length];
@@ -224,6 +233,9 @@ public class PartTreeCassandraQueryUnitTests {
Person findByFirstnameIn(Collection<String> firstname);
@AllowFiltering
Person findByFirstname(String firstname);
PersonProjection findPersonProjectedBy();
<T> T findDynamicallyProjectedBy(Class<T> type);

View File

@@ -87,6 +87,14 @@ public class ReactivePartTreeCassandraQueryUnitTests {
assertThat(query).isEqualTo("SELECT * FROM person WHERE firstname='foo' AND lastname='bar';");
}
@Test // DATACASS-376
public void shouldAllowFiltering() {
String query = deriveQueryFromMethod("findPersonByFirstname", "foo");
assertThat(query).isEqualTo("SELECT * FROM person WHERE firstname='foo' ALLOW FILTERING;");
}
@Test // DATACASS-335
public void usesDynamicProjection() {
String query = deriveQueryFromMethod("findDynamicallyProjectedBy", PersonProjection.class);
@@ -140,6 +148,9 @@ public class ReactivePartTreeCassandraQueryUnitTests {
Flux<Person> findPersonBy();
@Query(allowFiltering = true)
Flux<Person> findPersonByFirstname(String name);
Mono<PersonProjection> findPersonProjectedBy();
<T> Single<T> findDynamicallyProjectedBy(Class<T> type);