DATACOUCH-169 - Automatic index creation

Main indexes (ie indexes that can be used by repositories to retrieve ALL the documents of a particular entity type) are covered.

Repository interfaces can be annotated with an annotation for each type of main index that can be automatically created.

The IndexManager is responsible for creating said indexes (potentially skipping creation if an index is already in place, or if the IndexManager has been configured to skip it, see constructor).
Since IndexManager is created as a bean (either in JavaConfig or xml), there can be a different instance eg. in Prod vs Dev.

Added doc and integration test.

Renamed package core.view to core.query since it now contains various non-view related annotations and classes.
This commit is contained in:
Simon Baslé
2015-10-23 15:50:06 +02:00
parent 8ed7d7e336
commit 40d131aab0
51 changed files with 902 additions and 67 deletions

View File

@@ -13,7 +13,7 @@ import org.springframework.core.env.Environment;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.WriteResultChecking;
import org.springframework.data.couchbase.core.view.Consistency;
import org.springframework.data.couchbase.core.query.Consistency;
@Configuration
public class IntegrationTestApplicationConfig extends AbstractCouchbaseConfiguration {

View File

@@ -30,7 +30,7 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.view.Consistency;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.data.couchbase.repository.User;
/**

View File

@@ -38,6 +38,7 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@@ -58,11 +59,14 @@ public class CouchbaseRepositoryViewTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private CustomUserRepository repository;
@Before
public void setup() throws Exception {
repository = new CouchbaseRepositoryFactory(operationsMapping).getRepository(CustomUserRepository.class);
repository = new CouchbaseRepositoryFactory(operationsMapping, indexManager).getRepository(CustomUserRepository.class);
}
@Test

View File

@@ -20,7 +20,7 @@ import java.util.List;
import com.couchbase.client.java.document.json.JsonObject;
import org.springframework.data.couchbase.core.view.View;
import org.springframework.data.couchbase.core.query.View;
/**
* @author David Harrigan

View File

@@ -6,7 +6,7 @@ import java.util.List;
import com.couchbase.client.java.document.json.JsonArray;
import org.springframework.data.couchbase.core.view.Dimensional;
import org.springframework.data.couchbase.core.query.Dimensional;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;

View File

@@ -16,6 +16,7 @@ import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
@@ -37,11 +38,14 @@ public class DimensionalQueryTests {
@Autowired
private RepositoryOperationsMapping templateMapping;
@Autowired
private IndexManager indexManager;
private DimensionalPartyRepository repository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(templateMapping);
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(templateMapping, indexManager);
repository = factory.getRepository(DimensionalPartyRepository.class);
}

View File

@@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -49,11 +50,14 @@ public class N1qlCouchbaseRepositoryTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private PartyPagingRepository repository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = factory.getRepository(PartyPagingRepository.class);
}

View File

@@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.geo.Point;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -50,6 +51,9 @@ public class N1qlCrudRepositoryTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private PartyRepository partyRepository;
private ItemRepository itemRepository;
@@ -58,8 +62,8 @@ public class N1qlCrudRepositoryTests {
@Before
public void setup() throws Exception {
partyRepository = new CouchbaseRepositoryFactory(operationsMapping).getRepository(PartyRepository.class);
itemRepository = new CouchbaseRepositoryFactory(operationsMapping).getRepository(ItemRepository.class);
partyRepository = new CouchbaseRepositoryFactory(operationsMapping, indexManager).getRepository(PartyRepository.class);
itemRepository = new CouchbaseRepositoryFactory(operationsMapping, indexManager).getRepository(ItemRepository.class);
itemRepository.save(item);
partyRepository.save(party);

View File

@@ -16,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
@@ -35,11 +36,14 @@ public class PageAndSliceTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private UserRepository repository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = factory.getRepository(UserRepository.class);
}
@Test

View File

@@ -3,7 +3,7 @@ package org.springframework.data.couchbase.repository;
import java.util.Date;
import java.util.List;
import org.springframework.data.couchbase.core.view.View;
import org.springframework.data.couchbase.core.query.View;
/**
* @author Simon Baslé

View File

@@ -17,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@@ -36,11 +37,14 @@ public class QueryDerivationConversionTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private PartyRepository repository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = factory.getRepository(PartyRepository.class);
}

View File

@@ -34,6 +34,7 @@ import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@@ -53,11 +54,14 @@ public class SimpleCouchbaseRepositoryTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private UserRepository repository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = factory.getRepository(UserRepository.class);
}

View File

@@ -20,8 +20,8 @@ import java.util.List;
import com.couchbase.client.java.view.ViewQuery;
import org.springframework.data.couchbase.core.view.Query;
import org.springframework.data.couchbase.core.view.View;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.View;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;

View File

@@ -36,6 +36,7 @@ import org.springframework.data.couchbase.repository.User;
import org.springframework.data.couchbase.repository.UserRepository;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -52,6 +53,9 @@ public class FeatureDetectionRepositoryTests {
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
@Autowired
private ClusterInfo clusterInfo;
@@ -62,7 +66,7 @@ public class FeatureDetectionRepositoryTests {
@Test
public void testN1qlIncompatibleClusterFailsFastForN1qlBasedRepository() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
try {
factory.getRepository(UserRepository.class);
fail("expected UnsupportedCouchbaseFeatureException");
@@ -73,7 +77,7 @@ public class FeatureDetectionRepositoryTests {
@Test
public void testN1qlIncompatibleClusterDoesntFailForViewBasedRepository() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
ViewOnlyUserRepository repository = factory.getRepository(ViewOnlyUserRepository.class);
assertNotNull(repository);
}

View File

@@ -0,0 +1,15 @@
package org.springframework.data.couchbase.repository.index;
import java.util.List;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.User;
@N1qlSecondaryIndexed(indexName = IndexedRepositoryTests.IGNORED_SECONDARY)
public interface AnotherIndexedUserRepository extends CouchbaseRepository<User, String> {
@ViewIndexed(designDoc = IndexedRepositoryTests.VIEW_DOC, viewName = IndexedRepositoryTests.IGNORED_VIEW_NAME)
public List<User> findByAge(int age);
}

View File

@@ -0,0 +1,24 @@
package org.springframework.data.couchbase.repository.index;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.query.Index;
import com.couchbase.client.java.query.N1qlQuery;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
/**
* A test listener that will remove the indexes created in {@link IndexedRepositoryTests} before test case is run.
*
* @author Simon Baslé
*/
public class IndexedRepositoryTestListener extends DependencyInjectionTestExecutionListener {
@Override
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean("couchbaseBucket");
client.bucketManager().removeDesignDocument(IndexedRepositoryTests.VIEW_DOC);
client.query(N1qlQuery.simple(Index.dropPrimaryIndex(client.name())));
client.query(N1qlQuery.simple(Index.dropIndex(client.name(), IndexedRepositoryTests.SECONDARY)));
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2013 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.repository.index;
import static org.junit.Assert.*;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.N1qlQueryResult;
import com.couchbase.client.java.view.DesignDocument;
import com.couchbase.client.java.view.View;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* This tests automatic index creation features in the Couchbase connector.
* Automatic index creation is performed before construction of the repository implementation.
*
* @author Simon Baslé
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(IndexedRepositoryTestListener.class)
public class IndexedRepositoryTests {
public static final String SECONDARY = "autogeneratedIndexIndexedUserN1qlSecondary";
public static final String VIEW_DOC = "autogeneratedIndex";
public static final String VIEW_NAME = "IndexedUserView";
public static final String IGNORED_VIEW_NAME = "AnotherIndexedUserView";
public static final String IGNORED_SECONDARY = "AnotherIndexedUserN1qlSecondary";
@Autowired
private RepositoryOperationsMapping operationsMapping;
@Autowired
private IndexManager indexManager;
private CouchbaseOperations template;
private RepositoryFactorySupport factory;
private RepositoryFactorySupport ignoringIndexFactory;
private IndexManager ignoringIndexManager = new IndexManager(true, true, true);
@Before
public void setup() throws Exception {
factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
template = operationsMapping.getDefault();
ignoringIndexFactory = new CouchbaseRepositoryFactory(operationsMapping, ignoringIndexManager);
}
@Test
public void shouldFindN1qlPrimaryIndex() {
IndexedUserRepository repository = factory.getRepository(IndexedUserRepository.class);
String bucket = template.getCouchbaseBucket().name();
N1qlQuery existQuery = N1qlQuery.simple("SELECT COUNT(name) = 1 AS exist FROM system:indexes WHERE keyspace_id = \"" + bucket
+ "\" AND is_primary");
N1qlQueryResult exist = template.queryN1QL(existQuery);
assertTrue(exist.finalSuccess());
assertEquals(1, exist.allRows().size());
assertEquals(true, exist.allRows().get(0).value().getBoolean("exist"));
}
@Test
public void shouldFindN1qlSecondaryIndex() {
IndexedUserRepository repository = factory.getRepository(IndexedUserRepository.class);
String bucket = template.getCouchbaseBucket().name();
N1qlQuery existQuery = N1qlQuery.simple("SELECT COUNT(name) = 1 AS exist FROM system:indexes WHERE keyspace_id = \"" + bucket
+ "\" AND name = \"" + SECONDARY + "\"");
N1qlQueryResult exist = template.queryN1QL(existQuery);
assertTrue(exist.finalSuccess());
assertEquals(1, exist.allRows().size());
assertEquals(true, exist.allRows().get(0).value().getBoolean("exist"));
}
@Test
public void shouldFindViewIndex() {
IndexedUserRepository repository = factory.getRepository(IndexedUserRepository.class);
DesignDocument designDoc = template.getCouchbaseBucket()
.bucketManager()
.getDesignDocument(VIEW_DOC);
assertNotNull(designDoc);
for (View view : designDoc.views()) {
if (view.name().equals(VIEW_NAME)) return;
}
fail("View not found");
}
@Test
public void shouldNotFindN1qlSecondaryIndexWithIgnoringIndexManager() {
AnotherIndexedUserRepository repository = ignoringIndexFactory.getRepository(AnotherIndexedUserRepository.class);
String bucket = template.getCouchbaseBucket().name();
N1qlQuery existQuery = N1qlQuery.simple("SELECT COUNT(name) = 1 AS exist FROM system:indexes WHERE keyspace_id = \"" + bucket
+ "\" AND name = \"" + IGNORED_SECONDARY + "\"");
N1qlQueryResult exist = template.queryN1QL(existQuery);
assertTrue(exist.finalSuccess());
assertEquals(1, exist.allRows().size());
assertEquals(false, exist.allRows().get(0).value().getBoolean("exist"));
}
@Test
public void shouldNotFindViewIndexWithIgnoringIndexManager() {
AnotherIndexedUserRepository repository = ignoringIndexFactory.getRepository(AnotherIndexedUserRepository.class);
DesignDocument designDoc = template.getCouchbaseBucket()
.bucketManager()
.getDesignDocument(VIEW_DOC);
if (designDoc != null) {
for (View view : designDoc.views()) {
if (view.name().equals(IGNORED_VIEW_NAME)) fail("Found unexpected " + IGNORED_VIEW_NAME);
}
}
}
}

View File

@@ -0,0 +1,13 @@
package org.springframework.data.couchbase.repository.index;
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.User;
@N1qlPrimaryIndexed
@N1qlSecondaryIndexed(indexName = IndexedRepositoryTests.SECONDARY)
@ViewIndexed(designDoc = IndexedRepositoryTests.VIEW_DOC, viewName = IndexedRepositoryTests.VIEW_NAME)
public interface IndexedUserRepository extends CouchbaseRepository<User, String> {
}