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:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user