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.
This commit is contained in:
@@ -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<View> views = Arrays.asList(view, customFindByNameView);
|
||||
View customFindByAgeStatsView = DefaultView.create("customFindByAgeStatsView", mapFunctionAge, "_stats");
|
||||
List<View> views = Arrays.asList(view, customFindByNameView, customFindByAgeStatsView);
|
||||
DesignDocument designDoc = DesignDocument.create("user", views);
|
||||
client.bucketManager().upsertDesignDocument(designDoc);
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<User, String>
|
||||
|
||||
@View
|
||||
long countCustomFindInvalid();
|
||||
|
||||
@View(viewName = "customFindByAgeStatsView", reduce = true)
|
||||
JsonObject findByAgeLessThan(int maxAge);
|
||||
}
|
||||
|
||||
@@ -61,8 +61,15 @@ List<User> 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).
|
||||
@@ -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 <<repositories.limit-query-result>> is also supported.
|
||||
Note that both `reduce functions` and <<repositories.limit-query-result>> 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.
|
||||
|
||||
|
||||
@@ -52,4 +52,6 @@ public @interface View {
|
||||
*/
|
||||
String viewName() default "";
|
||||
|
||||
boolean reduce() default false;
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
* </ul>
|
||||
* </p>
|
||||
* 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<ViewQuery, ViewQuery> {
|
||||
public class ViewQueryCreator extends AbstractQueryCreator<ViewQueryCreator.DerivedViewQuery, ViewQuery> {
|
||||
|
||||
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<ViewQuery, ViewQuery>
|
||||
}
|
||||
|
||||
@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<ViewQuery, ViewQuery>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user