diff --git a/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java b/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java index 2bc75eac..ef603cdb 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java @@ -18,6 +18,8 @@ package org.springframework.data.couchbase.repository; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.couchbase.client.java.Bucket; import com.couchbase.client.java.view.Stale; @@ -74,9 +76,14 @@ public class CouchbaseRepositoryViewTests { assertThat(value, is(100L)); } - @Test(expected = InvalidDataAccessResourceUsageException.class) + @Test public void shouldTrimOffFindOnCustomFinder() { - repository.findAllSomething(); + try { + repository.findAllSomething(); + fail("Expected InvalidDataAccessResourceException"); + } catch (InvalidDataAccessResourceUsageException e) { + assertTrue(e.getMessage(), e.getMessage().startsWith("View user/allSomething does not exist")); + } } } diff --git a/src/integration/java/org/springframework/data/couchbase/repository/CustomUserRepository.java b/src/integration/java/org/springframework/data/couchbase/repository/CustomUserRepository.java index aa0414ad..cad54dc4 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/CustomUserRepository.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/CustomUserRepository.java @@ -31,6 +31,7 @@ public interface CustomUserRepository extends CouchbaseRepository @View(designDocument = "userCustom", viewName = "customCountView") long count(); + @View Iterable findAllSomething(); } diff --git a/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java b/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java index 7349eebb..06a187b7 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java @@ -19,6 +19,7 @@ package org.springframework.data.couchbase.repository; import static org.junit.Assert.*; import java.util.Arrays; +import java.util.List; import com.couchbase.client.java.Bucket; import com.couchbase.client.java.view.Stale; @@ -146,4 +147,26 @@ public class SimpleCouchbaseRepositoryTests { assertEquals("uname-2", user.getUsername()); } + @Test + public void shouldFindContainsWithoutAnnotation() { + List users = repository.findByUsernameContains("-9"); + assertNotNull(users); + assertFalse(users.isEmpty()); + for (User user : users) { + assertTrue(user.getUsername().startsWith("uname-9")); + } + } + + @Test + public void shouldDefaultToN1qlQueryDerivation() { + try { + User u = repository.findByUsernameNear("london"); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + if (!e.getMessage().contains("N1QL")) { + fail(e.getMessage()); + } + } + } + } diff --git a/src/integration/java/org/springframework/data/couchbase/repository/UserRepository.java b/src/integration/java/org/springframework/data/couchbase/repository/UserRepository.java index 48f2703c..f8d46c0c 100644 --- a/src/integration/java/org/springframework/data/couchbase/repository/UserRepository.java +++ b/src/integration/java/org/springframework/data/couchbase/repository/UserRepository.java @@ -20,7 +20,7 @@ import java.util.List; import com.couchbase.client.java.view.ViewQuery; -import org.springframework.data.couchbase.core.view.N1QL; +import org.springframework.data.couchbase.core.view.Query; import org.springframework.data.couchbase.core.view.View; /** @@ -31,13 +31,16 @@ public interface UserRepository extends CouchbaseRepository { @View(designDocument = "user", viewName = "all") Iterable customViewQuery(ViewQuery query); - @N1QL("$SELECT_ENTITY$ WHERE username = $1") + @Query("$SELECT_ENTITY$ WHERE username = $1") User findByUsername(String username); - @N1QL("SELECT * FROM $BUCKET$ WHERE username = $1") + @Query("SELECT * FROM $BUCKET$ WHERE username = $1") User findByUsernameBadSelect(String username); - @N1QL + @Query User findByUsernameRegexAndUsernameIn(String regex, List sample); + List findByUsernameContains(String contains); + + User findByUsernameNear(String place);//this is to check that there's a N1QL derivation AND it fails } diff --git a/src/main/java/org/springframework/data/couchbase/core/view/N1QL.java b/src/main/java/org/springframework/data/couchbase/core/view/Query.java similarity index 62% rename from src/main/java/org/springframework/data/couchbase/core/view/N1QL.java rename to src/main/java/org/springframework/data/couchbase/core/view/Query.java index 840ab0a0..e37a96e7 100644 --- a/src/main/java/org/springframework/data/couchbase/core/view/N1QL.java +++ b/src/main/java/org/springframework/data/couchbase/core/view/Query.java @@ -24,17 +24,32 @@ import java.lang.annotation.Target; import org.springframework.data.annotation.QueryAnnotation; import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery; /** * Annotation to support the use of N1QL queries with Couchbase. - * + *

* Using it without parameter will resolve the query from the method name. Providing a value * (an inline N1QL statement) will execute that statement instead. - * + *

* In this case, one can use a placeholder notation of {@code ?0}, {@code ?1} and so on. - * - * Also, the placeholder {@code $BUCKET$} can be used to be replaced by the underlying {@link CouchbaseTemplate} - * associated bucket name. + *

+ * Also, the following placeholders can be used to be replaced by the underlying {@link CouchbaseTemplate} + * associated information: + *

    + *
  • + * {@value StringN1qlBasedQuery#PLACEHOLDER_SELECT_FROM} + * (see {@link StringN1qlBasedQuery#PLACEHOLDER_SELECT_FROM}) + *
  • + *
  • + * {@value StringN1qlBasedQuery#PLACEHOLDER_BUCKET} + * (see {@link StringN1qlBasedQuery#PLACEHOLDER_BUCKET}) + *
  • + *
  • + * {@value StringN1qlBasedQuery#PLACEHOLDER_ENTITY} + * (see {@link StringN1qlBasedQuery#PLACEHOLDER_ENTITY}) + *
  • + *
* * @author Simon Baslé. */ @@ -42,12 +57,12 @@ import org.springframework.data.couchbase.core.CouchbaseTemplate; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @QueryAnnotation -public @interface N1QL { +public @interface Query { - /** - * Takes a N1QL statement string to define the actual query to be executed. This one will take precedence over the - * method name then. - */ - String value() default ""; + /** + * Takes a N1QL statement string to define the actual query to be executed. This one will take precedence over the + * method name then. + */ + String value() default ""; } diff --git a/src/main/java/org/springframework/data/couchbase/core/view/View.java b/src/main/java/org/springframework/data/couchbase/core/view/View.java index d11fdb08..1767a523 100644 --- a/src/main/java/org/springframework/data/couchbase/core/view/View.java +++ b/src/main/java/org/springframework/data/couchbase/core/view/View.java @@ -35,19 +35,19 @@ public @interface View { /** * The name of the Design Document to use. *

- * This field is mandatory. + * If the {@link #viewName()} field is set, this field is mandatory. * * @return name of the Design Document. */ - String designDocument(); + String designDocument() default ""; /** * The name of the View to use. *

- * This field is mandatory. + * If the {@link #designDocument()} field is set, his field is mandatory. * * @return name of the View */ - String viewName(); + String viewName() default ""; } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java index ffb06d5f..cb645e99 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java @@ -21,7 +21,7 @@ import java.lang.reflect.Method; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; -import org.springframework.data.couchbase.core.view.N1QL; +import org.springframework.data.couchbase.core.view.Query; import org.springframework.data.couchbase.core.view.View; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.core.RepositoryMetadata; @@ -57,6 +57,19 @@ public class CouchbaseQueryMethod extends QueryMethod { return getViewAnnotation() != null; } + /** + * If the method has a @View annotation with the designDocument and viewName specified. + * + * @return true if it has the annotation and full view specified. + */ + public boolean hasViewSpecification() { + View annotation = getViewAnnotation(); + if (annotation == null) { + return false; + } + return StringUtils.hasText(annotation.designDocument()) && StringUtils.hasText(annotation.viewName()); + } + /** * Returns the @View annotation if set, null otherwise. * @@ -67,7 +80,7 @@ public class CouchbaseQueryMethod extends QueryMethod { } /** - * If the method has a @N1QL annotation. + * If the method has a @Query annotation. * * @return true if it has the annotation, false otherwise. */ @@ -76,16 +89,16 @@ public class CouchbaseQueryMethod extends QueryMethod { } /** - * Returns the @N1QL annotation if set, null otherwise. + * Returns the @Query annotation if set, null otherwise. * * @return the n1ql annotation if present. */ - public N1QL getN1qlAnnotation() { - return method.getAnnotation(N1QL.class); + public Query getN1qlAnnotation() { + return method.getAnnotation(Query.class); } /** - * If the method has a @N1QL annotation with an inline N1QL statement inside. + * If the method has a @Query annotation with an inline Query statement inside. * * @return true if this has the annotation and N1QL inline statement, false otherwise. */ @@ -94,7 +107,7 @@ public class CouchbaseQueryMethod extends QueryMethod { } /** - * Returns the query string declared in a {@link N1QL} annotation or {@literal null} if neither the annotation found + * Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation found * nor the attribute was specified. * * @return the query statement if present. diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java index 684e8163..ff14d683 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java @@ -218,7 +218,7 @@ public class N1qlQueryCreator extends AbstractQueryCreatorSELECT x FROM y clause needed for entity mapping. + */ public static final String PLACEHOLDER_SELECT_FROM = "$SELECT_ENTITY$"; + + /** + * Use this placeholder in a {@link org.springframework.data.couchbase.core.view.Query @Query} annotation's inline + * statement. This will be replaced by the bucket name corresponding to the repository's entity. + */ public static final String PLACEHOLDER_BUCKET = "$BUCKET$"; + + /** + * Use this placeholder in a {@link org.springframework.data.couchbase.core.view.Query @Query} annotation's inline + * statement. This will be replaced by the fields allowing to construct the repository's entity (SELECT clause). + */ public static final String PLACEHOLDER_ENTITY = "$ENTITY$"; private final Statement statement; diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java index 85fd29fd..df1cd860 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java @@ -69,11 +69,13 @@ public class ViewBasedCouchbaseQuery implements RepositoryQuery { * @return the design document name. */ private String designDocName() { - if (method.hasViewAnnotation()) { - return method.getViewAnnotation().designDocument(); - } else { - return StringUtils.uncapitalize(method.getEntityInformation().getJavaType().getSimpleName()); - } + if (method.hasViewSpecification()) { + return method.getViewAnnotation().designDocument(); + } else if (method.hasViewAnnotation()) { + return StringUtils.uncapitalize(method.getEntityInformation().getJavaType().getSimpleName()); + } else { + throw new IllegalStateException("View-based query should only happen on a method with @View annotation"); + } } /** @@ -82,10 +84,12 @@ public class ViewBasedCouchbaseQuery implements RepositoryQuery { * @return the view name. */ private String viewName() { - if (method.hasViewAnnotation()) { + if (method.hasViewSpecification()) { return method.getViewAnnotation().viewName(); - } else { + } else if (method.hasViewAnnotation()) { return StringUtils.uncapitalize(method.getName().replaceFirst("find", "")); + } else { + throw new IllegalStateException("View-based query should only happen on a method with @View annotation"); } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java index 29c7d8b1..e71ada75 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java @@ -136,17 +136,17 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, mappingContext); String namedQueryName = queryMethod.getNamedQueryName(); - if (queryMethod.hasN1qlAnnotation()) { + if (queryMethod.hasViewAnnotation()) { + return new ViewBasedCouchbaseQuery(queryMethod, couchbaseOperations); + } else if (queryMethod.hasN1qlAnnotation()) { if (queryMethod.hasInlineN1qlQuery()) { return new StringN1qlBasedQuery(queryMethod.getInlineN1qlQuery(), queryMethod, couchbaseOperations); } else if (namedQueries.hasQuery(namedQueryName)) { String namedQuery = namedQueries.getQuery(namedQueryName); return new StringN1qlBasedQuery(namedQuery, queryMethod, couchbaseOperations); - } else { - return new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations); - } + } //otherwise will do default, queryDerivation } - return new ViewBasedCouchbaseQuery(queryMethod, couchbaseOperations); + return new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations); } }