From add94d2ea6951a4947a08bda11b9428ddfc0ca36 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 5 Jul 2017 15:55:15 +0200 Subject: [PATCH] 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 { @AllowFiltering List findAllByFirstname(String firstname); } --- .../cassandra/repository/AllowFiltering.java | 35 +++++++++++++++++++ .../data/cassandra/repository/Query.java | 9 +++-- .../query/CassandraQueryMethod.java | 21 ++++------- .../query/PartTreeCassandraQuery.java | 7 +++- .../query/ReactivePartTreeCassandraQuery.java | 7 +++- .../PartTreeCassandraQueryUnitTests.java | 12 +++++++ ...activePartTreeCassandraQueryUnitTests.java | 11 ++++++ 7 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/AllowFiltering.java diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/AllowFiltering.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/AllowFiltering.java new file mode 100644 index 000000000..bf54840eb --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/AllowFiltering.java @@ -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 { +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/Query.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/Query.java index 699b38e52..efab170e9 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/Query.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/Query.java @@ -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; } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java index c880489a0..a9e3f318c 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java @@ -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 CassandraPersistentProperty> mappingContext; + private final Optional 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 findAnnotatedQuery() { - - Optional 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 getQueryAnnotation() { + return query; } @Override diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQuery.java index 0d5d58591..bf7814e6d 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQuery.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQuery.java @@ -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() diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQuery.java index c8f94dbc3..ef00ca5e2 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQuery.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQuery.java @@ -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() diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java index 66421f005..a92930fb4 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java @@ -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 firstname); + @AllowFiltering + Person findByFirstname(String firstname); + PersonProjection findPersonProjectedBy(); T findDynamicallyProjectedBy(Class type); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQueryUnitTests.java index 59f345984..87651a37c 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQueryUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQueryUnitTests.java @@ -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 findPersonBy(); + @Query(allowFiltering = true) + Flux findPersonByFirstname(String name); + Mono findPersonProjectedBy(); Single findDynamicallyProjectedBy(Class type);