DATACOUCH-196 - Fix ViewIndexed generated map and improve usability

The generated view's default map function now emits meta.id so that CRUD findAll(ids) works.

The default value for the view name is now "all".

The ViewIndexed isn't documented as applicable to METHODs (deferred feature).
This commit is contained in:
Simon Baslé
2016-01-29 17:20:57 +01:00
parent 933ff0feaa
commit 524e8202f6
6 changed files with 76 additions and 10 deletions

View File

@@ -0,0 +1,26 @@
package org.springframework.data.couchbase.repository.index;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
@ViewIndexed(designDoc = "foo")
public interface IndexedFooRepository extends CouchbaseRepository<IndexedFooRepository.Foo, String> {
@Document
final class Foo {
@Id
private String id;
private String value1;
private int value2;
public Foo(String id, String value1, int value2) {
this.id = id;
this.value1 = value1;
this.value2 = value2;
}
}
}

View File

@@ -18,6 +18,7 @@ public class IndexedRepositoryTestListener extends DependencyInjectionTestExecut
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean("couchbaseBucket");
client.bucketManager().removeDesignDocument(IndexedRepositoryTests.VIEW_DOC);
client.bucketManager().removeDesignDocument("foo");
client.query(N1qlQuery.simple(Index.dropPrimaryIndex(client.name())));
client.query(N1qlQuery.simple(Index.dropIndex(client.name(), IndexedRepositoryTests.SECONDARY)));
}

View File

@@ -18,6 +18,8 @@ package org.springframework.data.couchbase.repository.index;
import static org.junit.Assert.*;
import java.util.Arrays;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.N1qlQueryResult;
import com.couchbase.client.java.view.DesignDocument;
@@ -144,4 +146,40 @@ public class IndexedRepositoryTests {
}
}
}
@Test
public void shouldFindListOfIdsThroughDefaulViewIndexed() {
IndexedFooRepository.Foo foo1 = new IndexedFooRepository.Foo("foo1", "foo", 1);
IndexedFooRepository.Foo foo2 = new IndexedFooRepository.Foo("foo2", "bar", 2);
IndexedFooRepository repository = factory.getRepository(IndexedFooRepository.class);
DesignDocument designDoc = template.getCouchbaseBucket()
.bucketManager()
.getDesignDocument("foo");
assertNotNull(designDoc);
boolean foundView = false;
for (View view : designDoc.views()) {
if (view.name().equals("all")) {
foundView = true;
break;
}
}
assertTrue("Expected to find view \"all\" on design document \"foo\"", foundView);
repository.save(foo1);
repository.save(foo2);
int count = 0;
for (Object o : repository.findAll(Arrays.asList("foo1", "foo2"))) {
count++;
}
assertEquals(2L, count);
count = 0;
for (Object o : repository.findAll(Arrays.asList("foo1", "foo3"))) {
count++;
}
assertEquals(1L, count);
}
}

View File

@@ -269,7 +269,7 @@ Since every view has a design document and view name, by convention we default t
// do not forget the _count reduce function!
function (doc, meta) {
if (doc._class == "namespace.to.entity.UserInfo") {
emit(null, null);
emit(meta.id, null);
}
}
----
@@ -277,7 +277,7 @@ function (doc, meta) {
Note that the important part in this map function is to only include the document IDs which correspond to our entity. Because the library always adds the `_class` property, this is a quick and easy way to do it. If you have another property in your JSON which does the same job (like a explicit `type` field), then you can use that as well - you don't have to stick to `_class` all the time.
Also make sure to publish your design documents into production so that they can be picked up by the library! Also, if you are curious why we use `emit(null, null)` in the view: the document id is always sent over to the client implicitly, so we can shave off a view bytes in our view by not duplicating the id. If you use `emit(meta.id, null)` it won't hurt much too.
Also make sure to publish your design documents into production so that they can be picked up by the library! Also, if you are curious why we use `emit(meta.id, null)` in the view despite the document id being always sent over to the client implicitly, it is so the view can be queried with a list of ids, eg. in the `findAll(Iterable<ID> ids)` CRUD method.
[[couchbase.repository.indexing]]
=== Automatic Index Management
@@ -286,7 +286,7 @@ In order for the CRUD operations to work, the adequate view must have been creat
In the case where the index creation cost isn't considered too high, it can be triggered automatically instead. You just need to annotate the repositories you want managed with the relevant annotation(s):
- `@ViewIndexed` will create a view like the "all" view previously seen, to list all entities in the bucket. It can also be used on methods, allowing multiple views to be created with custom map function and reduce function.
- `@ViewIndexed` will create a view like the "all" view previously seen, to list all entities in the bucket.
- `@N1qlPrimaryIndexed` can be used to ensure a general-purpose PRIMARY INDEX is available in N1QL.
- `@N1qlSecondaryIndexed` will create a more specific N1QL index that does the same kind of filtering on entity type that the view does. It'll allow for efficient listing of all documents that correspond to a Repository's associated domain object.

View File

@@ -29,24 +29,25 @@ import java.lang.annotation.Target;
* The view must at least be described as a design document name and view name. Default map function
* will filter documents on the type associated to the repository, and default reduce function is "_count".
* <p/>
* One can specify a custom reduce function as well as a non-default map function. This can be done on methods,
* allowing for multiple views per repository to be created.
* One can specify a custom reduce function as well as a non-default map function.
*
* @author Simon Baslé
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewIndexed {
/**
* The design document in which to create/look for the view.
* The design document in which to create/look for the view. Usually to create the backing view for CRUD
* methods, the expected designDoc value is the entity's simple class name, with a lowercase first letter
* (eg. a UserInfo repository would expect a design document named "userInfo").
*/
String designDoc();
/**
* The name of the view.
* The name of the view, defaults to "all" (which is what CRUD methods expect by default).
*/
String viewName();
String viewName() default "all";
/**
* The map function to use (default is empty, which will trigger a default map function filtering on the

View File

@@ -56,7 +56,7 @@ public class IndexManager {
private static final Logger LOGGER = LoggerFactory.getLogger(IndexManager.class);
private static final String TEMPLATE_MAP_FUNCTION = "function (doc, meta) { if(doc.%s == \"%s\") { emit(null, null); } }";
private static final String TEMPLATE_MAP_FUNCTION = "function (doc, meta) { if(doc.%s == \"%s\") { emit(meta.id, null); } }";
private static final JsonObject SUCCESS_MARKER = JsonObject.empty();