From 58d98c5f288ec09ca73355122f518eb4a122ec82 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Simon=20Basl=C3=A9?=
Date: Tue, 20 Oct 2015 17:44:43 +0200
Subject: [PATCH] DATACOUCH-168 - Support and document all reduce functions
Detection whether or not reduce should be activated is now centralized. Triggers are:
- using the count prefix instead of find in method name
- setting reduce = true in the View annotation explicitly.
The reduce can thus be returning any kind of value (added an integration example with _stats), which is now properly documented.
---
.../CouchbaseRepositoryViewListener.java | 4 ++-
.../CouchbaseRepositoryViewTests.java | 12 +++++++
.../repository/CustomUserRepository.java | 5 +++
src/main/asciidoc/migrating.adoc | 11 ++++--
src/main/asciidoc/repository.adoc | 4 ++-
.../data/couchbase/core/view/View.java | 2 ++
.../query/ViewBasedCouchbaseQuery.java | 16 ++++-----
.../repository/query/ViewQueryCreator.java | 35 +++++++++++++++----
8 files changed, 71 insertions(+), 18 deletions(-)
diff --git a/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java b/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java
index 7ad4a4ec..ad67a203 100644
--- a/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java
+++ b/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java
@@ -56,9 +56,11 @@ public class CouchbaseRepositoryViewListener extends DependencyInjectionTestExec
private void createAndWaitForDesignDocs(final Bucket client) {
String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(null, null); } }";
String mapFunctionName = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(doc.username, null); } }";
+ String mapFunctionAge = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(doc.age, doc.age); } }";
View view = DefaultView.create("customFindAllView", mapFunction, "_count");
View customFindByNameView = DefaultView.create("customFindByNameView", mapFunctionName, "_count");
- List views = Arrays.asList(view, customFindByNameView);
+ View customFindByAgeStatsView = DefaultView.create("customFindByAgeStatsView", mapFunctionAge, "_stats");
+ List views = Arrays.asList(view, customFindByNameView, customFindByAgeStatsView);
DesignDocument designDoc = DesignDocument.create("user", views);
client.bucketManager().upsertDesignDocument(designDoc);
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 6e5c5a16..6f18bc15 100644
--- a/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java
+++ b/src/integration/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java
@@ -25,6 +25,7 @@ import java.util.Arrays;
import java.util.List;
import com.couchbase.client.java.Bucket;
+import com.couchbase.client.java.document.json.JsonObject;
import com.couchbase.client.java.view.Stale;
import com.couchbase.client.java.view.ViewQuery;
import com.couchbase.client.java.view.ViewResult;
@@ -111,6 +112,17 @@ public class CouchbaseRepositoryViewTests {
assertEquals(12, count);
}
+ @Test
+ public void shouldDeriveViewParametersAndReduceNonNumerical() {
+ JsonObject reduceResult = repository.findByAgeLessThan(50);
+
+ assertNotNull(reduceResult);
+ assertEquals(51, (long) reduceResult.getLong("count"));
+ assertEquals(50, (long) reduceResult.getLong("max"));
+ assertEquals(0, (long) reduceResult.getLong("min"));
+ assertEquals(1275, (long) reduceResult.getLong("sum"));
+ }
+
@Test
public void shouldDeriveViewParameters() {
String lowKey = "uname-1";
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 cd334a21..a3095818 100644
--- a/src/integration/java/org/springframework/data/couchbase/repository/CustomUserRepository.java
+++ b/src/integration/java/org/springframework/data/couchbase/repository/CustomUserRepository.java
@@ -18,6 +18,8 @@ package org.springframework.data.couchbase.repository;
import java.util.List;
+import com.couchbase.client.java.document.json.JsonObject;
+
import org.springframework.data.couchbase.core.view.View;
/**
@@ -72,4 +74,7 @@ public interface CustomUserRepository extends CouchbaseRepository
@View
long countCustomFindInvalid();
+
+ @View(viewName = "customFindByAgeStatsView", reduce = true)
+ JsonObject findByAgeLessThan(int maxAge);
}
diff --git a/src/main/asciidoc/migrating.adoc b/src/main/asciidoc/migrating.adoc
index 851b40f3..3900e04d 100644
--- a/src/main/asciidoc/migrating.adoc
+++ b/src/main/asciidoc/migrating.adoc
@@ -61,8 +61,15 @@ List findFirst3ByLastnameEquals(String lastName);
[[couchbase.migrating.reduce-in-views]]
=== Reduce in views
-TIP: Reduce is now supported. It will be triggered by prefixing the method name with `count` instead of `find`.
+Reduce is now supported in view-based querying.
+
+It can be triggered by prefixing the method name with `count` instead of `find`.
For example: `countByLastnameContains(String word)` instead of `findByLastnameContains(String word)`.
-WARNING: Don't forget to specify an `int/long` reduce function (not limited to `_count`) in your view if you plan to use that. Similarly, views backing a query derivation should emit a simple key (not `null` nor a compound key).
+Alternatively, it can be explicitly be activated by setting `reduce = true` on the `@View` annotation.
+Be sure to construct your view correctly:
+
+ * specify a reduce function that matches the method return type, which can be anything, eg. long or JSON object
+ * emit a simple key (not `null` nor a compound key).
+ * emit a value suitable for the reduce to work (typically `_count` doesn't need any particular value, but `_stats` will need a numerical value, in addition to the key).
\ No newline at end of file
diff --git a/src/main/asciidoc/repository.adoc b/src/main/asciidoc/repository.adoc
index bf84b817..6a88f731 100644
--- a/src/main/asciidoc/repository.adoc
+++ b/src/main/asciidoc/repository.adoc
@@ -315,7 +315,9 @@ For view-based query derivation, here are the supported keywords (A and B are me
|`IsIn,In`|`findByFieldIn`|`keys=[A]` - A should be a `Collection`/`Array` with elements compatible for storage in a `JsonArray` (or a single element to be stored in a `JsonArray`)
|===============
-TIP: Note that the `reduce function` (not always a count) will be activated by prefixing with `count` and that <> is also supported.
+Note that both `reduce functions` and <> are also supported.
+
+TIP: In order to trigger a `reduce`, you can use the `count` prefix instead of `find`. But sometimes is doesn't make much sense (eg. because you actually use the `_stats` built in function, which returns a JSON object). So alternatively you can also explicitly ask for reduce to be executed by setting `reduce = true` in the `@View` annotation. Be sure to specify a return type that make sense for the reduce function of your view.
WARNING: Compound keys are not supported, and neither are Or composition, Ignore Case and Order By. You have to include a valid entity property in the naming of your method.
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 ad402b6d..cda37872 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
@@ -52,4 +52,6 @@ public @interface View {
*/
String viewName() default "";
+ boolean reduce() default false;
+
}
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 930298e2..514bf7a3 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
@@ -68,13 +68,14 @@ public class ViewBasedCouchbaseQuery implements RepositoryQuery {
protected Object guessViewAndExecute() {
String designDoc = designDocName(method);
String methodName = method.getName();
- boolean isReduce = methodName.startsWith("count");
+ boolean isExplicitReduce = method.hasViewAnnotation() && method.getViewAnnotation().reduce();
+ boolean isReduce = methodName.startsWith("count") || isExplicitReduce;
String viewName = StringUtils.uncapitalize(methodName.replaceFirst("find|count", ""));
ViewQuery simpleQuery = ViewQuery.from(designDoc, viewName)
.stale(operations.getDefaultConsistency().viewConsistency());
if (isReduce) {
- simpleQuery.reduce(isReduce);
+ simpleQuery.reduce();
return executeReduce(simpleQuery, designDoc, viewName);
} else {
return execute(simpleQuery);
@@ -94,15 +95,14 @@ public class ViewBasedCouchbaseQuery implements RepositoryQuery {
//use a ViewQueryCreator to complete the base query
ViewQueryCreator creator = new ViewQueryCreator(tree, new ParametersParameterAccessor(method.getParameters(), runtimeParams),
- baseQuery, operations.getConverter());
- ViewQuery query = creator.createQuery();
+ method.getViewAnnotation(), baseQuery, operations.getConverter());
+ ViewQueryCreator.DerivedViewQuery result = creator.createQuery();
- //detect count prefix in the method name and consider it triggers a reduce
- if (tree.isCountProjection() == Boolean.TRUE) {
- return executeReduce(query, designDoc, viewName);
+ if (result.isReduce) {
+ return executeReduce(result.builtQuery, designDoc, viewName);
} else {
//otherwise just execute the query
- return execute(query);
+ return execute(result.builtQuery);
}
} catch (PropertyReferenceException e) {
/*
diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ViewQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/ViewQueryCreator.java
index 13cc9187..a3d143dc 100644
--- a/src/main/java/org/springframework/data/couchbase/repository/query/ViewQueryCreator.java
+++ b/src/main/java/org/springframework/data/couchbase/repository/query/ViewQueryCreator.java
@@ -30,6 +30,7 @@ import com.couchbase.client.java.document.json.JsonObject;
import com.couchbase.client.java.view.ViewQuery;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
+import org.springframework.data.couchbase.core.view.View;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
@@ -58,18 +59,21 @@ import org.springframework.data.repository.query.parser.PartTree;
*
*
* Additionally, {@link PartTree#isLimiting()} will use {@link ViewQuery#limit(int) limit}
- * and {@link PartTree#isCountProjection()} will trigger a {@link ViewQuery#reduce() reduce}.
+ * and either {@link View#reduce()} or {@link PartTree#isCountProjection()} will trigger a {@link ViewQuery#reduce() reduce}.
*/
-public class ViewQueryCreator extends AbstractQueryCreator {
+public class ViewQueryCreator extends AbstractQueryCreator {
private ViewQuery query;
+ private final View viewAnnotation;
private final PartTree tree;
private final int treeCount;
private final CouchbaseConverter converter;
- public ViewQueryCreator(PartTree tree, ParameterAccessor parameters, ViewQuery query, CouchbaseConverter converter) {
+ public ViewQueryCreator(PartTree tree, ParameterAccessor parameters, View viewAnnotation,
+ ViewQuery query, CouchbaseConverter converter) {
super(tree, parameters);
this.query = query;
+ this.viewAnnotation = viewAnnotation;
this.tree = tree;
this.converter = converter;
@@ -268,7 +272,7 @@ public class ViewQueryCreator extends AbstractQueryCreator
}
@Override
- protected ViewQuery complete(ViewQuery criteria, Sort sort) {
+ protected DerivedViewQuery complete(ViewQuery criteria, Sort sort) {
boolean descending = false;
if (sort != null) {
@@ -290,8 +294,27 @@ public class ViewQueryCreator extends AbstractQueryCreator
query.limit(tree.getMaxResults());
}
- query.reduce(tree.isCountProjection() == Boolean.TRUE);
+ boolean isCount = tree.isCountProjection() == Boolean.TRUE;
+ boolean isExplicitReduce = viewAnnotation != null && viewAnnotation.reduce();
+ if (isCount || isExplicitReduce) {
+ query.reduce();
+ }
- return query;
+ return new DerivedViewQuery(query, tree.isLimiting(), isCount || isExplicitReduce);
+ }
+
+ /**
+ * Wrapper class allowing to see downstream if the built query was built with options like reduce and limit.
+ */
+ protected static class DerivedViewQuery {
+ public final ViewQuery builtQuery;
+ public final boolean isLimited;
+ public final boolean isReduce;
+
+ public DerivedViewQuery(ViewQuery builtQuery, boolean isLimited, boolean isReduce) {
+ this.builtQuery = builtQuery;
+ this.isLimited = isLimited;
+ this.isReduce = isReduce;
+ }
}
}