DATACOUCH-138 - Add Page/Slice/PagingAndSortingRepo

Add support for Page / Slice in N1QL query derived methods.
Add PagingAndSortingRepository support through a N1qlCouchbaseRepository implementation. It will only be used as a base if N1QL is actually available on the cluster.

Refactor out some steps of entity related query creation in a N1qlUtils class, so that they can be also done eg. in repository base classes.

Add tests for both N1qlUtils and the N1qlCouchbaseRepository.

Document that PagingAndSortingRepository is supported provided N1QL is available on the Couchbase cluster.
This commit is contained in:
Simon Baslé
2015-09-16 17:41:21 -04:00
parent ef4a79efff
commit 56ae8217e9
24 changed files with 919 additions and 98 deletions

View File

@@ -16,11 +16,9 @@ import com.couchbase.client.java.query.SimpleQuery;
import com.couchbase.client.java.query.Statement;
import com.couchbase.client.java.query.consistency.ScanConsistency;
import org.junit.Test;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.query.QueryMethod;
public class AbstractN1qlBasedQueryTest {
@@ -79,60 +77,111 @@ public class AbstractN1qlBasedQueryTest {
public void shouldChooseCollectionExecutionWhenCollectionType() {
CouchbaseQueryMethod queryMethod = Mockito.mock(CouchbaseQueryMethod.class);
Query query = Mockito.mock(Query.class);
Pageable pageable = Mockito.mock(Pageable.class);
AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class);
when(mock.executeDependingOnType(any(Query.class), any(QueryMethod.class), anyBoolean(), anyBoolean(), anyBoolean()))
when(mock.executeDependingOnType(any(Query.class), any(Query.class), any(QueryMethod.class), any(Pageable.class),
anyBoolean(), anyBoolean(), anyBoolean()))
.thenCallRealMethod();
when(queryMethod.isCollectionQuery()).thenReturn(true);
mock.executeDependingOnType(query, queryMethod, false, false, false);
mock.executeDependingOnType(query, query, queryMethod, pageable, false, false, false);
verify(mock).executeCollection(any(Query.class));
verify(mock, never()).executeEntity(any(Query.class));
verify(mock, never()).executeStream(any(Query.class));
verify(mock, never()).executePaged(any(Query.class), any(Query.class), any(Pageable.class));
verify(mock, never()).executeSliced(any(Query.class), any(Query.class), any(Pageable.class));
}
@Test
public void shouldChooseEntityExecutionWhenEntityType() {
CouchbaseQueryMethod queryMethod = Mockito.mock(CouchbaseQueryMethod.class);
Query query = Mockito.mock(Query.class);
Pageable pageable = Mockito.mock(Pageable.class);
AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class);
when(mock.executeDependingOnType(any(Query.class), any(QueryMethod.class), anyBoolean(), anyBoolean(), anyBoolean()))
when(mock.executeDependingOnType(any(Query.class), any(Query.class), any(QueryMethod.class), any(Pageable.class),
anyBoolean(), anyBoolean(), anyBoolean()))
.thenCallRealMethod();
when(queryMethod.isQueryForEntity()).thenReturn(true);
mock.executeDependingOnType(query, queryMethod, false, false, false);
mock.executeDependingOnType(query, query, queryMethod, pageable, false, false, false);
verify(mock, never()).executeCollection(any(Query.class));
verify(mock).executeEntity(any(Query.class));
verify(mock, never()).executeStream(any(Query.class));
verify(mock, never()).executePaged(any(Query.class), any(Query.class), any(Pageable.class));
verify(mock, never()).executeSliced(any(Query.class), any(Query.class), any(Pageable.class));
}
@Test
public void shouldChooseStreamExecutionWhenStreamType() {
CouchbaseQueryMethod queryMethod = Mockito.mock(CouchbaseQueryMethod.class);
Query query = Mockito.mock(Query.class);
Pageable pageable = Mockito.mock(Pageable.class);
AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class);
when(mock.executeDependingOnType(any(Query.class), any(QueryMethod.class), anyBoolean(), anyBoolean(), anyBoolean()))
when(mock.executeDependingOnType(any(Query.class), any(Query.class), any(QueryMethod.class), any(Pageable.class),
anyBoolean(), anyBoolean(), anyBoolean()))
.thenCallRealMethod();
when(queryMethod.isStreamQuery()).thenReturn(true);
mock.executeDependingOnType(query, queryMethod, false, false, false);
mock.executeDependingOnType(query, query, queryMethod, pageable, false, false, false);
verify(mock, never()).executeCollection(any(Query.class));
verify(mock, never()).executeEntity(any(Query.class));
verify(mock).executeStream(any(Query.class));
verify(mock, never()).executePaged(any(Query.class), any(Query.class), any(Pageable.class));
verify(mock, never()).executeSliced(any(Query.class), any(Query.class), any(Pageable.class));
}
@Test
public void shouldChoosePagedExecutionWhenPageType() {
CouchbaseQueryMethod queryMethod = Mockito.mock(CouchbaseQueryMethod.class);
Query query = Mockito.mock(Query.class);
Pageable pageable = Mockito.mock(Pageable.class);
AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class);
when(mock.executeDependingOnType(any(Query.class), any(Query.class), any(QueryMethod.class), any(Pageable.class),
anyBoolean(), anyBoolean(), anyBoolean()))
.thenCallRealMethod();
mock.executeDependingOnType(query, query, queryMethod, pageable, true, false, false);
verify(mock, never()).executeCollection(any(Query.class));
verify(mock, never()).executeEntity(any(Query.class));
verify(mock, never()).executeStream(any(Query.class));
verify(mock).executePaged(any(Query.class), any(Query.class), any(Pageable.class));
verify(mock, never()).executeSliced(any(Query.class), any(Query.class), any(Pageable.class));
}
@Test
public void shouldChooseSlicedExecutionWhenSliceType() {
CouchbaseQueryMethod queryMethod = Mockito.mock(CouchbaseQueryMethod.class);
Query query = Mockito.mock(Query.class);
Pageable pageable = Mockito.mock(Pageable.class);
AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class);
when(mock.executeDependingOnType(any(Query.class), any(Query.class), any(QueryMethod.class), any(Pageable.class),
anyBoolean(), anyBoolean(), anyBoolean()))
.thenCallRealMethod();
when(queryMethod.isSliceQuery()).thenReturn(true);
mock.executeDependingOnType(query, query, queryMethod, pageable, false, true, false);
verify(mock, never()).executeCollection(any(Query.class));
verify(mock, never()).executeEntity(any(Query.class));
verify(mock, never()).executeStream(any(Query.class));
verify(mock, never()).executePaged(any(Query.class), any(Query.class), any(Pageable.class));
verify(mock).executeSliced(any(Query.class), any(Query.class), any(Pageable.class));
}
@Test
public void shouldThrowWhenUnsupportedType() throws NoSuchMethodException {
CouchbaseQueryMethod queryMethod = Mockito.mock(CouchbaseQueryMethod.class);
Query query = Mockito.mock(Query.class);
Pageable pageable = Mockito.mock(Pageable.class);
AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class);
when(mock.executeDependingOnType(any(Query.class), any(QueryMethod.class), anyBoolean(), anyBoolean(), anyBoolean()))
when(mock.executeDependingOnType(any(Query.class), any(Query.class), any(QueryMethod.class), any(Pageable.class),
anyBoolean(), anyBoolean(), anyBoolean()))
.thenCallRealMethod();
try { mock.executeDependingOnType(query, queryMethod, true, false, false); } catch (UnsupportedOperationException e) { }
try { mock.executeDependingOnType(query, queryMethod, false, true, false); } catch (UnsupportedOperationException e) { }
try { mock.executeDependingOnType(query, queryMethod, false, false, true); } catch (UnsupportedOperationException e) { }
try { mock.executeDependingOnType(query, query, queryMethod, pageable, false, false, true); fail(); } catch (UnsupportedOperationException e) { }
verify(mock, never()).executeCollection(any(Query.class));
verify(mock, never()).executeEntity(any(Query.class));
verify(mock, never()).executeStream(any(Query.class));
verify(mock, never()).executePaged(any(Query.class), any(Query.class), any(Pageable.class));
verify(mock, never()).executeSliced(any(Query.class), any(Query.class), any(Pageable.class));
}
}
}

View File

@@ -11,7 +11,7 @@ public class StringN1QlBasedQueryTest {
@Test
public void testReplaceFullSelectPlaceholderOnce() throws Exception {
String statement = PLACEHOLDER_SELECT_FROM + " where " + PLACEHOLDER_SELECT_FROM;
Statement parsed = StringN1qlBasedQuery.prepare(statement, "B", "_class", String.class);
Statement parsed = StringN1qlBasedQuery.prepare(statement, "B", "_class", String.class, false);
assertEquals("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS, `B`.* FROM `B` where "
+ PLACEHOLDER_SELECT_FROM, parsed.toString());
@@ -20,7 +20,7 @@ public class StringN1QlBasedQueryTest {
@Test
public void testReplaceAllBucketPlaceholder() throws Exception {
String statement = "SELECT * FROM " + PLACEHOLDER_BUCKET + " WHERE " + PLACEHOLDER_BUCKET + ".test = 1";
Statement parsed = StringN1qlBasedQuery.prepare(statement, "B", "_class", String.class);
Statement parsed = StringN1qlBasedQuery.prepare(statement, "B", "_class", String.class, false);
assertEquals("SELECT * FROM `B` WHERE `B`.test = 1", parsed.toString());
}
@@ -28,7 +28,7 @@ public class StringN1QlBasedQueryTest {
@Test
public void testReplaceFirstEntityPlaceholder() throws Exception {
String statement = "SELECT " + PLACEHOLDER_ENTITY + " FROM b where b.test = 1 and " + PLACEHOLDER_ENTITY;
Statement parsed = StringN1qlBasedQuery.prepare(statement, "A", "_class", String.class);
Statement parsed = StringN1qlBasedQuery.prepare(statement, "A", "_class", String.class, false);
assertEquals("SELECT META(`A`).id AS _ID, META(`A`).cas AS _CAS FROM b where b.test = 1 and "
+ PLACEHOLDER_ENTITY, parsed.toString());
@@ -37,9 +37,17 @@ public class StringN1QlBasedQueryTest {
@Test
public void testReplaceTypePlaceholder() throws Exception {
String statement = "SELECT " + PLACEHOLDER_ENTITY + " FROM b WHERE b.test = 1 AND " + PLACEHOLDER_FILTER_TYPE;
Statement parsed = StringN1qlBasedQuery.prepare(statement, "A", "@class", String.class);
Statement parsed = StringN1qlBasedQuery.prepare(statement, "A", "@class", String.class, false);
assertEquals("SELECT META(`A`).id AS _ID, META(`A`).cas AS _CAS FROM b WHERE b.test = 1 AND `@class` = "
+ "\"java.lang.String\"", parsed.toString());
}
@Test
public void testReplaceSelectFromPlaceholderWithCountIfCountTrue() {
String statement = PLACEHOLDER_SELECT_FROM + " WHERE true";
Statement parsed = StringN1qlBasedQuery.prepare(statement, "A", "_class", String.class, true);
assertEquals("SELECT COUNT(*) AS " + CountFragment.COUNT_ALIAS + " FROM `A` WHERE true", parsed.toString());
}
}

View File

@@ -0,0 +1,123 @@
package org.springframework.data.couchbase.repository.query.support;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.stubbing.Answer;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.repository.Party;
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
import org.springframework.data.couchbase.repository.query.CountFragment;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.repository.core.EntityMetadata;
public class N1qlUtilsTest {
@Test
public void testEscapedBucket() throws Exception {
String bucketName = "b";
String expected = "`b`";
String real = N1qlUtils.escapedBucket(bucketName).toString();
assertEquals(expected, real);
}
@Test
public void testCreateSelectClauseForEntity() throws Exception {
String expected = "SELECT META(`b`).id AS _ID, META(`b`).cas AS _CAS, `b`.*";
String real = N1qlUtils.createSelectClauseForEntity("b").toString();
assertEquals(expected, real);
}
@Test
public void testCreateSelectFromForEntity() throws Exception {
String expected = "SELECT META(`b`).id AS _ID, META(`b`).cas AS _CAS, `b`.* FROM `b`";
String real = N1qlUtils.createSelectFromForEntity("b").toString();
assertEquals(expected, real);
}
@Test
public void testCreateWhereFilterForEntity() throws Exception {
String expected = "`_class` = \"java.lang.String\"";
CouchbaseConverter converter = mock(CouchbaseConverter.class);
when(converter.getTypeKey()).thenReturn("_class");
EntityMetadata metadata = mock(EntityMetadata.class);
when(metadata.getJavaType()).thenReturn(String.class);
String real = N1qlUtils.createWhereFilterForEntity(null, converter, metadata).toString();
assertEquals(expected, real);
}
@Test
public void testCreateWhereFilterForEntityTakesTypeKeyIntoAccount() throws Exception {
String expected = "`hohoho` = \"java.lang.String\"";
CouchbaseConverter converter = mock(CouchbaseConverter.class);
when(converter.getTypeKey()).thenReturn("hohoho");
EntityMetadata metadata = mock(EntityMetadata.class);
when(metadata.getJavaType()).thenReturn(String.class);
String real = N1qlUtils.createWhereFilterForEntity(null, converter, metadata).toString();
assertEquals(expected, real);
}
@Test
public void testGetPathWithAlternativeFieldNames() throws Exception {
CouchbaseConverter converter = mock(CouchbaseConverter.class);
PropertyPath partyDescPropertyPath = PropertyPath.from("description", Party.class);
MappingContext mockMapping = mock(CouchbaseMappingContext.class);
when(converter.getMappingContext()).thenReturn(mockMapping);
N1qlUtils.getPathWithAlternativeFieldNames(converter, partyDescPropertyPath);
verify(mockMapping).getPersistentPropertyPath(partyDescPropertyPath);
verifyNoMoreInteractions(mockMapping);
}
@Test
@Ignore("rather test the dotted path converter")
public void testGetDottedPathWithAlternativeFieldNames() throws Exception {
}
@Test
public void testCreateSortUsesPropertiesAsIsWithEscaping() throws Exception {
CouchbaseConverter converter = mock(CouchbaseConverter.class);
com.couchbase.client.java.query.dsl.Sort[] realSort =
N1qlUtils.createSort(new Sort("description", "attendees"), converter);
assertEquals(2, realSort.length);
assertEquals(com.couchbase.client.java.query.dsl.Sort.asc("`description`").toString(), realSort[0].toString());
assertEquals(com.couchbase.client.java.query.dsl.Sort.asc("`attendees`").toString(), realSort[1].toString());
verifyZeroInteractions(converter);
}
@Test
public void testCreateCountQueryForEntity() throws Exception {
CouchbaseConverter converter = mock(CouchbaseConverter.class);
when(converter.getTypeKey()).thenReturn("_class", "otherField");
CouchbaseEntityInformation entityInformation = mock(CouchbaseEntityInformation.class);
when(entityInformation.getJavaType()).thenReturn(String.class);
String expectedDefault = "SELECT COUNT(*) AS " + CountFragment.COUNT_ALIAS + " FROM `b` WHERE `_class` = \"java.lang.String\"";
String expectedTypeKey = "SELECT COUNT(*) AS " + CountFragment.COUNT_ALIAS + " FROM `b` WHERE `otherField` = \"java.lang.String\"";
String real = N1qlUtils.createCountQueryForEntity("b", converter, entityInformation).toString();
String realWithTypeKey = N1qlUtils.createCountQueryForEntity("b", converter, entityInformation).toString();
assertEquals(expectedDefault, real);
assertEquals(expectedTypeKey, realWithTypeKey);
}
}