DATACOUCH-139 - Add limited support for @View query derivation.

@View can now trigger a basic query derivation. If viewName's explicitly
set, query derivation is attempted. Otherwise, no derivation is made but
instead the viewName is guessed by using the method name and removing a
"find" or "count" prefix.

Methods prefixed with "count" (and in general detected as count
projections) will trigger a reduce on the view.
This commit is contained in:
Simon Baslé
2015-07-15 17:04:15 +02:00
parent 4920acc214
commit 4b453d4246
8 changed files with 545 additions and 48 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2015 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.
@@ -16,6 +16,7 @@
package org.springframework.data.couchbase.repository;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -32,6 +33,7 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution
/**
* @author Michael Nitschinger
* @author Simon Baslé
*/
public class CouchbaseRepositoryViewListener extends DependencyInjectionTestExecutionListener {
@@ -51,8 +53,10 @@ 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); } }";
View view = DefaultView.create("customFindAllView", mapFunction, "_count");
List<View> views = Collections.singletonList(view);
View customFindByNameView = DefaultView.create("customFindByNameView", mapFunctionName, "_count");
List<View> views = Arrays.asList(view, customFindByNameView);
DesignDocument designDoc = DesignDocument.create("user", views);
client.bucketManager().upsertDesignDocument(designDoc);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2015 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.
@@ -18,8 +18,10 @@ 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 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;
@@ -33,12 +35,14 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author David Harrigan
* @author Simon Baslé
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@@ -77,13 +81,89 @@ public class CouchbaseRepositoryViewTests {
}
@Test
public void shouldTrimOffFindOnCustomFinder() {
public void shouldDetectMethodNameWithoutPropertyAndIssueGenericQueryOnView() {
Iterable<User> users = repository.findRandomMethodName();
assertNotNull(users);
assertTrue(users.iterator().hasNext());
try {
repository.findAllSomething();
repository.findIncorrectExplicitView();
fail("Expected InvalidDataAccessResourceException");
} catch (InvalidDataAccessResourceUsageException e) {
assertTrue(e.getMessage(), e.getMessage().startsWith("View user/allSomething does not exist"));
}
}
@Test(expected = PropertyReferenceException.class)
public void shouldFailDeriveOnBadProperty() {
repository.findAllByUsernameEqualAndUserblablaIs("uname-1", "blabla");
}
@Test
public void shouldDeriveViewParametersAndReduce() {
long count = repository.countByUsernameGreaterThanEqualAndUsernameLessThan("uname-8", "uname-9");
assertEquals(12, count);
}
@Test
public void shouldDeriveViewParameters() {
String lowKey = "uname-1";
String middleKey = "uname-10";
String highKey = "uname-11";
List<String> keys = Arrays.asList(lowKey, middleKey, highKey);
User u1 = repository.findByUsernameIs(lowKey);
User u2 = repository.findByUsernameIs(middleKey);
User u3 = repository.findByUsernameIs(highKey);
List<User> in = repository.findAllByUsernameIn(keys);
List<User> gteLte = repository.findByUsernameGreaterThanEqualAndUsernameLessThanEqual(lowKey, highKey);
List<User> between = repository.findByUsernameBetween(lowKey, highKey);
List<User> gteLimited = repository.findTop3ByUsernameGreaterThanEqual(lowKey);
assertNotNull(u1);
assertNotNull(u2);
assertNotNull(u3);
assertEquals(lowKey, u1.getUsername());
assertEquals(middleKey, u2.getUsername());
assertEquals(highKey, u3.getUsername());
List<User> expected = Arrays.asList(u1, u2, u3);
assertEquals(expected, in);
assertEquals(expected, gteLte);
assertEquals(expected, between);
assertEquals(expected, gteLimited);
}
@Test
public void shouldDeriveToEmptyClause() {
List<User> users = repository.findAllByUsername();
assertNotNull(users);
assertEquals(100, users.size());
}
@Test
public void shouldDetermineViewNameFromMethodPrefix() {
try {
repository.findByIncorrectView();
fail("Expected InvalidDataAccessResourceException");
} catch (InvalidDataAccessResourceUsageException e) {
assertTrue(e.getMessage(), e.getMessage().startsWith("View user/byIncorrectView does not exist"));
}
}
@Test
public void shouldDetermineViewNameFromCountPrefixAndReduce() {
long count = repository.countCustomFindAllView();
assertEquals(100, count);
try {
repository.countCustomFindInvalid();
fail("Expected InvalidDataAccessResourceException");
} catch (InvalidDataAccessResourceUsageException e) {
assertTrue(e.getMessage(), e.getMessage().startsWith("View user/customFindInvalid does not exist"));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2015 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.
@@ -16,10 +16,13 @@
package org.springframework.data.couchbase.repository;
import java.util.List;
import org.springframework.data.couchbase.core.view.View;
/**
* @author David Harrigan
* @author Simon Baslé
*/
public interface CustomUserRepository extends CouchbaseRepository<User, String> {
@@ -31,7 +34,42 @@ public interface CustomUserRepository extends CouchbaseRepository<User, String>
@View(designDocument = "userCustom", viewName = "customCountView")
long count();
@View
Iterable<User> findAllSomething();
@View(viewName = "allSomething")
Iterable<User> findIncorrectExplicitView();
@View(viewName = "customFindAllView")
Iterable<User> findRandomMethodName();
@View(viewName = "customFindByNameView")
long countByUsernameGreaterThanEqualAndUsernameLessThan(String lowBound, String highBound);
@View(viewName = "customFindByNameView")
User findByUsernameIs(String lowKey);
@View(viewName = "customFindByNameView")
List<User> findAllByUsernameIn(List<String> keys);
@View(viewName = "customFindByNameView")
List<User> findByUsernameGreaterThanEqualAndUsernameLessThanEqual(String lowKey, String highKey);
@View(viewName = "customFindByNameView")
List<User> findByUsernameBetween(String lowKey, String highKey);
@View(viewName = "customFindByNameView")
List<User> findTop3ByUsernameGreaterThanEqual(String lowKey);
@View(viewName = "customFindAllView")
List<User> findAllByUsername();
@View(viewName = "customFindAllView")
List<User> findAllByUsernameEqualAndUserblablaIs(String s, String blabla);
@View
List<User> findByIncorrectView();
@View
long countCustomFindAllView();
@View
long countCustomFindInvalid();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2015 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.
@@ -20,6 +20,7 @@ import org.springframework.data.annotation.Id;
/**
* @author Michael Nitschinger
* @author Simon Baslé
*/
public class User {
@@ -41,4 +42,21 @@ public class User {
return key;
}
@Override
public String toString() {
return this.key;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return key.equals(user.key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}