From 5d8a1d2ce541191ba302ac49c93f2425aa74394d Mon Sep 17 00:00:00 2001 From: John Blum Date: Tue, 11 Aug 2015 23:08:25 -0700 Subject: [PATCH] SGF-392 - Add support for OQL Query statement extensions in Repository Query methods via Annotations. Implementation and unit test coverage complete. (cherry picked from commit 23d05aeaee743922a9d371b8b7f8201e6cb9578c) Signed-off-by: John Blum --- .../mapping/GemfireMappingContext.java | 4 +- .../data/gemfire/mapping/Regions.java | 3 +- .../repository/query/GemfireQueryMethod.java | 9 +- .../repository/query/QueryBuilder.java | 4 +- .../gemfire/repository/query/QueryString.java | 45 ++++- .../StringBasedGemfireRepositoryQuery.java | 31 +++- .../support/GemfireRepositoryFactory.java | 8 +- .../data/gemfire/mapping/RegionsTest.java | 60 ++++--- .../query/GemfireQueryMethodUnitTests.java | 2 + .../repository/query/QueryBuilderTest.java | 110 ++++++++++++ .../query/QueryStringUnitTests.java | 169 ++++++++++++++++-- ...StringBasedGemfireRepositoryQueryTest.java | 111 +++++++++++- 12 files changed, 500 insertions(+), 56 deletions(-) create mode 100644 src/test/java/org/springframework/data/gemfire/repository/query/QueryBuilderTest.java diff --git a/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java b/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java index bc5c47fb..7a951e2e 100644 --- a/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java +++ b/src/main/java/org/springframework/data/gemfire/mapping/GemfireMappingContext.java @@ -26,8 +26,7 @@ import org.springframework.data.util.TypeInformation; * * @author Oliver Gierke */ -public class GemfireMappingContext extends - AbstractMappingContext, GemfirePersistentProperty> { +public class GemfireMappingContext extends AbstractMappingContext, GemfirePersistentProperty> { /* * (non-Javadoc) @@ -47,4 +46,5 @@ public class GemfireMappingContext extends GemfirePersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { return new GemfirePersistentProperty(field, descriptor, owner, simpleTypeHolder); } + } diff --git a/src/main/java/org/springframework/data/gemfire/mapping/Regions.java b/src/main/java/org/springframework/data/gemfire/mapping/Regions.java index 45320408..009c5d6c 100644 --- a/src/main/java/org/springframework/data/gemfire/mapping/Regions.java +++ b/src/main/java/org/springframework/data/gemfire/mapping/Regions.java @@ -75,8 +75,9 @@ public class Regions implements Iterable> { Assert.notNull(type); GemfirePersistentEntity entity = context.getPersistentEntity(type); + String regionName = (entity != null ? entity.getRegionName() : type.getSimpleName()); - return (Region) (entity == null ? regions.get(type.getSimpleName()) : regions.get(entity.getRegionName())); + return (Region) regions.get(regionName); } /** diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java index 3bcc6d09..69438c1f 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethod.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.data.gemfire.repository.query; import java.lang.reflect.Method; @@ -33,9 +34,11 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * Gemfire specific {@link QueryMethod}. + * GemFire specific {@link QueryMethod}. * * @author Oliver Gierke + * @author John Blum + * @see org.springframework.data.repository.query.QueryMethod */ @SuppressWarnings("unused") public class GemfireQueryMethod extends QueryMethod { @@ -76,8 +79,8 @@ public class GemfireQueryMethod extends QueryMethod { private void assertNonPagingQueryMethod(final Method method) { for (Class type : method.getParameterTypes()) { if (Pageable.class.isAssignableFrom(type)) { - throw new IllegalStateException("Pagination is not supported by GemFire Repositories! Offending method: " - + method.toString()); + throw new IllegalStateException(String.format("Pagination is not supported by GemFire Repositories!" + + " Offending method: %1$s", method.toString())); } } } diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java b/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java index b7a70e98..c304c627 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/QueryBuilder.java @@ -34,12 +34,12 @@ class QueryBuilder { private final String query; public QueryBuilder(String source) { - Assert.hasText(source, "The OQL Query string must be specified."); + Assert.hasText(source, "The OQL Query must be specified"); this.query = source; } public QueryBuilder(GemfirePersistentEntity entity, PartTree tree) { - this(String.format(tree.isDistinct() ? "SELECT DISTINCT * FROM /%1$s %2$s" : "SELECT * FROM /%1$s %2$s", + this(String.format("SELECT%1$s * FROM /%2$s %3$s", (tree.isDistinct() ? " DISTINCT" : ""), entity.getRegionName(), DEFAULT_ALIAS)); } diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java b/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java index 67080ae8..908c9be1 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/QueryString.java @@ -23,6 +23,7 @@ import java.util.regex.Pattern; import org.springframework.data.domain.Sort; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import com.gemstone.gemfire.cache.Region; @@ -36,6 +37,19 @@ import com.gemstone.gemfire.cache.Region; */ public class QueryString { + protected static final Pattern HINT_PATTERN = Pattern.compile(""); + protected static final Pattern IMPORT_PATTERN = Pattern.compile("IMPORT .+;"); + protected static final Pattern LIMIT_PATTERN = Pattern.compile("LIMIT \\d+"); + protected static final Pattern TRACE_PATTERN = Pattern.compile(""); + + // OQL Query Templates + private static final String HINTS_QUERY_TEMPLATE = " %2$s"; + private static final String IMPORT_QUERY_TEMPLATE = "IMPORT %1$s; %2$s"; + private static final String LIMIT_QUERY_TEMPLATE = "%1$s LIMIT %2$d"; + private static final String SELECT_QUERY_TEMPLATE = "SELECT %1$s FROM /%2$s"; + private static final String TRACE_QUERY_TEMPLATE = " %1$s"; + + // Query Regular Expression Patterns private static final String IN_PATTERN = "(?<=IN (SET|LIST) )\\$\\d"; private static final String IN_PARAMETER_PATTERN = "(?<=IN (SET|LIST) \\$)\\d"; private static final String REGION_PATTERN = "\\/(\\/?\\w)+"; @@ -69,8 +83,7 @@ public class QueryString { * @param isCountQuery indicates if this is a count query */ public QueryString(Class domainClass, boolean isCountQuery) { - this(String.format(isCountQuery ? "SELECT count(*) FROM /%s" : "SELECT * FROM /%s", - domainClass.getSimpleName())); + this(String.format(SELECT_QUERY_TEMPLATE, (isCountQuery ? "count(*)" : "*"), domainClass.getSimpleName())); } /** @@ -146,6 +159,34 @@ public class QueryString { return this; } + public QueryString withHints(String... hints) { + if (!ObjectUtils.isEmpty(hints)) { + StringBuilder builder = new StringBuilder(); + + for (String hint : hints) { + builder.append(builder.length() > 0 ? ", " : ""); + builder.append(String.format("'%1$s'", hint)); + } + + return new QueryString(String.format(HINTS_QUERY_TEMPLATE, builder.toString(), query)); + } + + return this; + } + + public QueryString withImport(String importExpression) { + return (StringUtils.hasText(importExpression) ? new QueryString( + String.format(IMPORT_QUERY_TEMPLATE, importExpression, query)) : this); + } + + public QueryString withLimit(Integer limit) { + return (limit != null ? new QueryString(String.format(LIMIT_QUERY_TEMPLATE, query, limit)) : this); + } + + public QueryString withTrace() { + return new QueryString(String.format(TRACE_QUERY_TEMPLATE, query)); + } + /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java b/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java index f3eff4c0..ebbb5608 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java +++ b/src/main/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQuery.java @@ -109,16 +109,18 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { public Object execute(Object[] parameters) { QueryMethod localQueryMethod = getQueryMethod(); - ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor( - localQueryMethod.getParameters(), parameters); - QueryString query = (isUserDefinedQuery() ? this.query : this.query.forRegion( localQueryMethod.getEntityInformation().getJavaType(), template.getRegion())); + ParametersParameterAccessor parameterAccessor = new ParametersParameterAccessor( + localQueryMethod.getParameters(), parameters); + for (Integer index : query.getInParameterIndexes()) { query = query.bindIn(toCollection(parameterAccessor.getBindableValue(index - 1))); } + query = applyQueryAnnotationExtensions(localQueryMethod, query); + Collection result = toCollection(template.find(query.toString(), parameters)); if (localQueryMethod.isCollectionQuery()) { @@ -143,6 +145,29 @@ public class StringBasedGemfireRepositoryQuery extends GemfireRepositoryQuery { } } + QueryString applyQueryAnnotationExtensions(final QueryMethod queryMethod, final QueryString queryString) { + QueryString resolvedQueryString = queryString; + + if (queryMethod instanceof GemfireQueryMethod) { + GemfireQueryMethod gemfireQueryMethod = (GemfireQueryMethod) queryMethod; + String query = queryString.toString().toUpperCase(); + + if (gemfireQueryMethod.hasImport() && !QueryString.IMPORT_PATTERN.matcher(query).find()) { + resolvedQueryString = resolvedQueryString.withImport(gemfireQueryMethod.getImport()); + } + if (gemfireQueryMethod.hasHint() && !QueryString.HINT_PATTERN.matcher(query).find()) { + resolvedQueryString = resolvedQueryString.withHints(gemfireQueryMethod.getHints()); + } + if (gemfireQueryMethod.hasLimit() && !QueryString.LIMIT_PATTERN.matcher(query).find()) { + resolvedQueryString = resolvedQueryString.withLimit(gemfireQueryMethod.getLimit()); + } + if (gemfireQueryMethod.hasTrace() && !QueryString.TRACE_PATTERN.matcher(query).find()) { + resolvedQueryString = resolvedQueryString.withTrace(); + } + } + + return resolvedQueryString; + } boolean isSingleResultNonEntityQuery(QueryMethod method, Collection result) { return (!method.isCollectionQuery() && method.getReturnedObjectType() != null diff --git a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java index aeeeba42..b50115ab 100644 --- a/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java +++ b/src/main/java/org/springframework/data/gemfire/repository/support/GemfireRepositoryFactory.java @@ -64,7 +64,7 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport { public GemfireRepositoryFactory(Iterable> regions, MappingContext, GemfirePersistentProperty> context) { Assert.notNull(regions); - this.context = context == null ? new GemfireMappingContext() : context; + this.context = (context != null ? context : new GemfireMappingContext()); this.regions = new Regions(regions, this.context); } @@ -108,8 +108,8 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport { Region region = regions.getRegion(regionName); if (region == null) { - throw new IllegalStateException(String.format("No region '%s' found for domain class %s!" - + " Make sure you have configured a Gemfire region of that name in your application context!", + throw new IllegalStateException(String.format("No Region '%1$s' found for domain class %2$s!" + + " Make sure you have configured a GemFire Region of that name in your application context!", regionName, metadata.getDomainType())); } @@ -118,7 +118,7 @@ public class GemfireRepositoryFactory extends RepositoryFactorySupport { if (regionKeyType != null && entity.getIdProperty() != null) { Assert.isTrue(regionKeyType.isAssignableFrom(entityIdType), String.format( - "The region referenced only supports keys of type %s but the entity to be stored has an id of type %s!", + "The Region referenced only supports keys of type %1$s but the entity to be stored has an id of type %2$s!", regionKeyType, entityIdType)); } diff --git a/src/test/java/org/springframework/data/gemfire/mapping/RegionsTest.java b/src/test/java/org/springframework/data/gemfire/mapping/RegionsTest.java index a0587271..df59b260 100644 --- a/src/test/java/org/springframework/data/gemfire/mapping/RegionsTest.java +++ b/src/test/java/org/springframework/data/gemfire/mapping/RegionsTest.java @@ -16,10 +16,12 @@ package org.springframework.data.gemfire.mapping; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; @@ -32,6 +34,9 @@ import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.gemfire.repository.sample.GuestUser; import org.springframework.data.gemfire.repository.sample.RootUser; import org.springframework.data.gemfire.repository.sample.User; @@ -44,13 +49,17 @@ import com.gemstone.gemfire.cache.Region; * * @author John J. Blum * @see org.junit.Test + * @see org.junit.runner.RunWith * @see org.mockito.Mockito + * @see org.mockito.runners.MockitoJUnitRunner * @see org.springframework.data.gemfire.mapping.Regions * @since 1.3.4 */ @SuppressWarnings("unchecked") +@RunWith(MockitoJUnitRunner.class) public class RegionsTest { + @Mock private MappingContext mockMappingContext; private Region mockUsers; @@ -59,14 +68,14 @@ public class RegionsTest { private Regions regions; - protected Region createMockRegion(final String fullPath) { + protected Region mockRegion(final String fullPath) { // NOTE if the Region path does not contain a "/" then lastIndexOf returns -1 and substring is appropriately // based on a 0 index then by adding 1, ;-) - return createMockRegion(fullPath.substring(fullPath.lastIndexOf(Region.SEPARATOR) + 1), fullPath); + return mockRegion(fullPath.substring(fullPath.lastIndexOf(Region.SEPARATOR) + 1), fullPath); } - protected Region createMockRegion(final String name, final String fullPath) { - Region mockRegion = mock(com.gemstone.gemfire.cache.Region.class, name); + protected Region mockRegion(final String name, final String fullPath) { + Region mockRegion = mock(Region.class, name); when(mockRegion.getName()).thenReturn(name); when(mockRegion.getFullPath()).thenReturn(fullPath); @@ -76,27 +85,24 @@ public class RegionsTest { @Before public void setup() { - mockMappingContext = mock(GemfireMappingContext.class, "GemfireMappingContext"); - - mockUsers = createMockRegion("/Users"); - mockAdminUsers = createMockRegion("/Users/Admin"); - mockGuestUsers = createMockRegion("/Users/Guest"); + mockUsers = mockRegion("/Users"); + mockAdminUsers = mockRegion("/Users/Admin"); + mockGuestUsers = mockRegion("/Users/Guest"); regions = new Regions(Arrays.>asList(mockUsers, mockAdminUsers, mockGuestUsers), mockMappingContext); - assertNotNull(regions); + assertThat(regions, is(notNullValue())); } @After public void tearDown() { - mockMappingContext = null; mockUsers = mockAdminUsers = mockGuestUsers = null; regions = null; } @Test - public void testIterateRegions() { + public void iterateRegions() { List actualRegions = new ArrayList(3); for (Region region : regions) { @@ -110,7 +116,7 @@ public class RegionsTest { } @Test - public void testGetRegionByNameOrPath() { + public void getRegionByNameOrPath() { assertSame(mockUsers, regions.getRegion("Users")); assertSame(mockUsers, regions.getRegion("/Users")); assertSame(mockAdminUsers, regions.getRegion("Admin")); @@ -120,12 +126,21 @@ public class RegionsTest { } @Test(expected = IllegalArgumentException.class) - public void testGetRegionByNameOrPathWithNullArgument() { + public void getRegionByNameOrPathWithNullArgument() { regions.getRegion((String) null); } @Test - public void testGetRegionByDomainClassAndUsingPersistentEntity() { + public void getRegionByDomainClassType() { + when(mockMappingContext.getPersistentEntity(Object.class)).thenReturn(null); + assertSame(mockUsers, regions.getRegion(Users.class)); + assertNull(regions.getRegion(User.class)); + assertNull(regions.getRegion(RootUser.class)); + assertNull(regions.getRegion(GuestUser.class)); + } + + @Test + public void getRegionByDomainClassTypeAndPersistentEntity() { GemfirePersistentEntity mockUsersEntity = mock(GemfirePersistentEntity.class, "UsersGemfirePeristentEntity"); GemfirePersistentEntity mockAdminUserEntity = mock(GemfirePersistentEntity.class, "AdminUserGemfirePeristentEntity"); GemfirePersistentEntity mockGuestUserEntity = mock(GemfirePersistentEntity.class, "GuestUserGemfirePeristentEntity"); @@ -145,16 +160,7 @@ public class RegionsTest { } @Test - public void testGetRegionByDomainClass() { - when(mockMappingContext.getPersistentEntity(Object.class)).thenReturn(null); - assertSame(mockUsers, regions.getRegion(Users.class)); - assertNull(regions.getRegion(User.class)); - assertNull(regions.getRegion(RootUser.class)); - assertNull(regions.getRegion(GuestUser.class)); - } - - @Test - public void testGetRegionByPersistentEntity() { + public void getRegionByPersistentEntity() { GemfirePersistentEntity mockPersistentEntity = mock(GemfirePersistentEntity.class, "GemfirePersistentEntity"); when(mockMappingContext.getPersistentEntity(any(Class.class))).thenReturn(mockPersistentEntity); @@ -165,7 +171,7 @@ public class RegionsTest { assertNull(regions.getRegion(GuestUser.class)); } - protected static interface Users { + protected interface Users { } } diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java index 619d8ca9..9f5a3b26 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/repository/query/GemfireQueryMethodUnitTests.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.data.gemfire.repository.query; import static org.hamcrest.CoreMatchers.equalTo; @@ -48,6 +49,7 @@ import org.springframework.util.ObjectUtils; * Unit tests for {@link GemfireQueryMethod}. * * @author Oliver Gierke + * @author John Blum */ @RunWith(MockitoJUnitRunner.class) public class GemfireQueryMethodUnitTests { diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/QueryBuilderTest.java b/src/test/java/org/springframework/data/gemfire/repository/query/QueryBuilderTest.java new file mode 100644 index 00000000..c1eb85f5 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/repository/query/QueryBuilderTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2010-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.gemfire.repository.query; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.data.gemfire.mapping.GemfirePersistentEntity; +import org.springframework.data.repository.query.parser.PartTree; + +/** + * The QueryBuilderTest class is a test suite of test cases testing the contract and functionality of the QueryBuilder + * class. + * + * @author John Blum + * @see org.junit.Rule + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.repository.query.QueryBuilder + * @since 1.7.0 + */ +public class QueryBuilderTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void createQueryBuilderNonDistinct() { + GemfirePersistentEntity mockPersistentEntity = mock(GemfirePersistentEntity.class, "MockGemfirePersistentEntity"); + PartTree mockPartTree = mock(PartTree.class, "MockPartTree"); + + when(mockPersistentEntity.getRegionName()).thenReturn("Example"); + when(mockPartTree.isDistinct()).thenReturn(false); + + QueryBuilder queryBuilder = new QueryBuilder(mockPersistentEntity, mockPartTree); + + assertThat(queryBuilder.toString(), is(equalTo("SELECT * FROM /Example x"))); + + verify(mockPersistentEntity, times(1)).getRegionName(); + verify(mockPartTree, times(1)).isDistinct(); + } + + @Test + public void createQueryBuilderWithDistinct() { + GemfirePersistentEntity mockPersistentEntity = mock(GemfirePersistentEntity.class, "MockGemfirePersistentEntity"); + PartTree mockPartTree = mock(PartTree.class, "MockPartTree"); + + when(mockPersistentEntity.getRegionName()).thenReturn("Example"); + when(mockPartTree.isDistinct()).thenReturn(true); + + QueryBuilder queryBuilder = new QueryBuilder(mockPersistentEntity, mockPartTree); + + assertThat(queryBuilder.toString(), is(equalTo("SELECT DISTINCT * FROM /Example x"))); + + verify(mockPersistentEntity, times(1)).getRegionName(); + verify(mockPartTree, times(1)).isDistinct(); + } + + @Test + public void createQueryBuilderWithNullQueryString() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectCause(is(nullValue(Throwable.class))); + expectedException.expectMessage(is(equalTo("The OQL Query must be specified"))); + + new QueryBuilder(null); + } + + @Test + public void createWithPredicate() { + Predicate mockPredicate = mock(Predicate.class, "MockPredicate"); + + when(mockPredicate.toString(eq(QueryBuilder.DEFAULT_ALIAS))).thenReturn("x.id = 1"); + + QueryBuilder queryBuilder = new QueryBuilder(String.format("SELECT * FROM /Example %s", + QueryBuilder.DEFAULT_ALIAS)); + + QueryString queryString = queryBuilder.create(mockPredicate); + + assertThat(queryString, is(notNullValue())); + assertThat(queryString.toString(), is(equalTo("SELECT * FROM /Example x WHERE x.id = 1"))); + + verify(mockPredicate, times(1)).toString(eq(QueryBuilder.DEFAULT_ALIAS)); + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/QueryStringUnitTests.java b/src/test/java/org/springframework/data/gemfire/repository/query/QueryStringUnitTests.java index b5ac61c2..fd4962ac 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/query/QueryStringUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/repository/query/QueryStringUnitTests.java @@ -13,10 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.data.gemfire.repository.query; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -59,6 +62,82 @@ public class QueryStringUnitTests { return new Sort(orders); } + @Test + public void createQueryStringWithCount() { + QueryString queryString = new QueryString(Person.class, true); + + assertThat(queryString, is(notNullValue())); + assertThat(queryString.toString(), is(equalTo("SELECT count(*) FROM /Person"))); + } + + @Test + public void createQueryStringWithoutCount() { + QueryString queryString = new QueryString(Person.class); + + assertThat(queryString, is(notNullValue())); + assertThat(queryString.toString(), is(equalTo("SELECT * FROM /Person"))); + } + + @Test + public void hintPatternMatches() { + assertThat(QueryString.HINT_PATTERN.matcher("").find(), is(true)); + assertThat(QueryString.HINT_PATTERN.matcher(" SELECT * FROM /Example WHERE id = $1").find(), is(true)); + assertThat(QueryString.HINT_PATTERN.matcher(" SELECT * FROM /Person WHERE lastName = $1 AND birthDate = $2").find(), is(true)); + } + + @Test + public void hintPatternNoMatches() { + assertThat(QueryString.HINT_PATTERN.matcher("HINT").find(), is(false)); + assertThat(QueryString.HINT_PATTERN.matcher("").find(), is(false)); + assertThat(QueryString.HINT_PATTERN.matcher("").find(), is(false)); + assertThat(QueryString.HINT_PATTERN.matcher("").find(), is(false)); + assertThat(QueryString.HINT_PATTERN.matcher("").find(), is(false)); + assertThat(QueryString.HINT_PATTERN.matcher("SELECT * FROM /Example").find(), is(false)); + assertThat(QueryString.HINT_PATTERN.matcher("SELECT * FROM /Hint").find(), is(false)); + assertThat(QueryString.HINT_PATTERN.matcher("SELECT x.hint FROM /Clues x WHERE x.hint > $1").find(), is(false)); + } + + @Test + public void importPatternMatches() { + assertThat(QueryString.IMPORT_PATTERN.matcher("IMPORT *;").find(), is(true)); + assertThat(QueryString.IMPORT_PATTERN.matcher("IMPORT org.example.*;").find(), is(true)); + assertThat(QueryString.IMPORT_PATTERN.matcher("IMPORT org.example.Type;").find(), is(true)); + assertThat(QueryString.IMPORT_PATTERN.matcher("IMPORT org.example.app.DomainType; SELECT * FROM /Domain").find(), is(true)); + } + + @Test + public void importPatternNoMatches() { + assertThat(QueryString.IMPORT_PATTERN.matcher("IMPORT").find(), is(false)); + assertThat(QueryString.IMPORT_PATTERN.matcher("IMPORT ;").find(), is(false)); + assertThat(QueryString.IMPORT_PATTERN.matcher("IMPORT *").find(), is(false)); + assertThat(QueryString.IMPORT_PATTERN.matcher("SELECT * FROM /Example").find(), is(false)); + } + + @Test + public void limitPatternMatches() { + assertThat(QueryString.LIMIT_PATTERN.matcher("LIMIT 10").find(), is(true)); + assertThat(QueryString.LIMIT_PATTERN.matcher("SELECT * FROM /Example LIMIT 10").find(), is(true)); + assertThat(QueryString.LIMIT_PATTERN.matcher("SELECT * FROM /Example WHERE id = $1 LIMIT 10").find(), is(true)); + } + + @Test + public void limitPatternNoMatches() { + assertThat(QueryString.LIMIT_PATTERN.matcher("LIMIT").find(), is(false)); + assertThat(QueryString.LIMIT_PATTERN.matcher("LIMIT lO").find(), is(false)); + assertThat(QueryString.LIMIT_PATTERN.matcher("LIMIT FF").find(), is(false)); + assertThat(QueryString.LIMIT_PATTERN.matcher("LIMIT ten").find(), is(false)); + assertThat(QueryString.LIMIT_PATTERN.matcher("SELECT * FROM /Example LIMIT").find(), is(false)); + assertThat(QueryString.LIMIT_PATTERN.matcher("SELECT * FROM /Example WHERE id = $1 LIM 10").find(), is(false)); + } + + @Test + public void tracePatternMatches() { + assertThat(QueryString.TRACE_PATTERN.matcher("").find(), is(true)); + assertThat(QueryString.TRACE_PATTERN.matcher(" SELECT * FROM /Example").find(), is(true)); + assertThat(QueryString.TRACE_PATTERN.matcher("SELECT * FROM /Example").find(), is(true)); + assertThat(QueryString.TRACE_PATTERN.matcher("SELECT * FROM /Example").find(), is(true)); + } + // SGF-251 @Test public void replacesDomainObjectWithRegionPathCorrectly() { @@ -79,19 +158,21 @@ public class QueryStringUnitTests { when(region.getFullPath()).thenReturn("/People"); - assertThat(query.forRegion(Person.class, region).toString(), is("SELECT * FROM /People p WHERE p.firstname = $1")); + assertThat(query.forRegion(Person.class, region).toString(), + is("SELECT * FROM /People p WHERE p.firstname = $1")); verify(region, never()).getName(); } // SGF-252 @Test - public void replacesFullyQualifiedSubregionReferenceWithRegionPathCorrectly() { + public void replacesFullyQualifiedSubRegionReferenceWithRegionPathCorrectly() { QueryString query = new QueryString("SELECT * FROM //Local/Root/Users u WHERE u.username = $1"); when(region.getFullPath()).thenReturn("/Local/Root/Users"); - assertThat(query.forRegion(RootUser.class, region).toString(), is("SELECT * FROM /Local/Root/Users u WHERE u.username = $1")); + assertThat(query.forRegion(RootUser.class, region).toString(), is(equalTo( + "SELECT * FROM /Local/Root/Users u WHERE u.username = $1"))); verify(region, never()).getName(); } @@ -110,13 +191,20 @@ public class QueryStringUnitTests { assertThat(indexes, is((Iterable) Arrays.asList(1, 2))); } + @Test + public void addsNoOrderByClauseCorrectly() { + QueryString query = new QueryString("SELECT * FROM /People p").orderBy(null); + + assertThat(query.toString(), is(equalTo("SELECT * FROM /People p"))); + } + @Test public void addsOrderByClauseCorrectly() { QueryString query = new QueryString("SELECT * FROM /People p WHERE p.lastName = $1") .orderBy(createSort(createOrder("lastName", Sort.Direction.DESC), createOrder("firstName"))); - assertEquals("SELECT * FROM /People p WHERE p.lastName = $1 ORDER BY lastName DESC, firstName ASC", - query.toString()); + assertThat(query.toString(), is(equalTo( + "SELECT * FROM /People p WHERE p.lastName = $1 ORDER BY lastName DESC, firstName ASC"))); } @Test @@ -124,15 +212,74 @@ public class QueryStringUnitTests { QueryString query = new QueryString("SELECT DISTINCT p.lastName FROM /People p WHERE p.firstName = $1") .orderBy(createSort(createOrder("lastName"))); - assertEquals("SELECT DISTINCT p.lastName FROM /People p WHERE p.firstName = $1 ORDER BY lastName ASC", - query.toString()); + assertThat(query.toString(), is(equalTo( + "SELECT DISTINCT p.lastName FROM /People p WHERE p.firstName = $1 ORDER BY lastName ASC"))); } @Test - public void addsNoOrderByClauseCorrectly() { - QueryString query = new QueryString("SELECT * FROM /People p").orderBy(null); + public void withHints() { + assertThat(new QueryString("SELECT * FROM /Example").withHints("IdIdx").toString(), + is(equalTo(" SELECT * FROM /Example"))); + assertThat(new QueryString("SELECT * FROM /Example").withHints("IdIdx", "SpatialIdx", "TxDateIdx").toString(), + is(equalTo(" SELECT * FROM /Example"))); + } - assertEquals("SELECT * FROM /People p", query.toString()); + @Test + public void withoutHints() { + QueryString expectedQueryString = new QueryString("SELECT * FROM /Example"); + + assertThat(expectedQueryString.withHints((String[]) null), is(sameInstance(expectedQueryString))); + assertThat(expectedQueryString.withHints(), is(sameInstance(expectedQueryString))); + } + + @Test + public void withImport() { + assertThat(new QueryString("SELECT * FROM /People").withImport("org.example.app.domain.Person").toString(), + is(equalTo("IMPORT org.example.app.domain.Person; SELECT * FROM /People"))); + } + + @Test + public void withoutImport() { + QueryString expectedQueryString = new QueryString("SELECT * FROM /Example"); + + assertThat(expectedQueryString.withImport(null), is(sameInstance(expectedQueryString))); + assertThat(expectedQueryString.withImport(""), is(sameInstance(expectedQueryString))); + assertThat(expectedQueryString.withImport(" "), is(sameInstance(expectedQueryString))); + } + + @Test + public void withLimit() { + assertThat(new QueryString("SELECT * FROM /Example").withLimit(10).toString(), + is(equalTo("SELECT * FROM /Example LIMIT 10"))); + assertThat(new QueryString("SELECT * FROM /Example").withLimit(-5).toString(), + is(equalTo("SELECT * FROM /Example LIMIT -5"))); + assertThat(new QueryString("SELECT * FROM /Example").withLimit(0).toString(), + is(equalTo("SELECT * FROM /Example LIMIT 0"))); + } + + @Test + public void withoutLimit() { + QueryString expectedQueryString = new QueryString("SELECT * FROM /Example"); + assertThat(expectedQueryString.withLimit(null), is(sameInstance(expectedQueryString))); + } + + @Test + public void withTrace() { + assertThat(new QueryString("SELECT * FROM /Example").withTrace().toString(), + is(equalTo(" SELECT * FROM /Example"))); + } + + @Test + public void withHintAndTrace() { + assertThat(new QueryString("SELECT * FROM /Example").withHints("IdIdx").withTrace().toString(), + is(equalTo(" SELECT * FROM /Example"))); + } + + @Test + public void withHintImportLimitAndTrace() { + assertThat(new QueryString("SELECT * FROM /Example").withImport("org.example.domain.Type") + .withHints("IdIdx", "NameIdx").withLimit(20).withTrace().toString(), + is(equalTo(" IMPORT org.example.domain.Type; SELECT * FROM /Example LIMIT 20"))); } } diff --git a/src/test/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQueryTest.java b/src/test/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQueryTest.java index 2e654dac..ec1055dd 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQueryTest.java +++ b/src/test/java/org/springframework/data/gemfire/repository/query/StringBasedGemfireRepositoryQueryTest.java @@ -16,17 +16,27 @@ package org.springframework.data.gemfire.repository.query; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.junit.Test; @@ -37,7 +47,7 @@ import com.gemstone.gemfire.cache.query.internal.ResultsBag; /** * The SpringBasedGemfireRepositoryQueryTest class is a test suite of test cases testing the contract and functionality * of the StringBasedGemfireRepositoryQuery class. - *

+ * * @author John Blum * @see org.mockito.Mockito * @see org.junit.Test @@ -110,4 +120,103 @@ public class StringBasedGemfireRepositoryQueryTest { assertTrue(list.isEmpty()); } + @Test + public void applyAllQueryAnnotationExtensions() { + GemfireQueryMethod mockQueryMethod = mock(GemfireQueryMethod.class, "MockGemfireQueryMethod"); + + when(mockQueryMethod.hasHint()).thenReturn(true); + when(mockQueryMethod.getHints()).thenReturn(Arrays.asList("IdIdx", "NameIdx").toArray(new String[2])); + when(mockQueryMethod.hasImport()).thenReturn(true); + when(mockQueryMethod.getImport()).thenReturn("org.example.domain.Type"); + when(mockQueryMethod.hasLimit()).thenReturn(true); + when(mockQueryMethod.getLimit()).thenReturn(10); + when(mockQueryMethod.hasTrace()).thenReturn(true); + + QueryString queryString = new QueryString("SELECT * FROM /Example"); + + assertThat(queryString.toString(), is(equalTo("SELECT * FROM /Example"))); + + StringBasedGemfireRepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery(); + + QueryString actualQueryString = repositoryQuery.applyQueryAnnotationExtensions(mockQueryMethod, queryString); + + assertThat(actualQueryString, is(notNullValue())); + assertThat(actualQueryString, is(not(sameInstance(queryString)))); + assertThat(actualQueryString.toString(), is(equalTo( + " IMPORT org.example.domain.Type; SELECT * FROM /Example LIMIT 10"))); + + verify(mockQueryMethod, times(1)).hasHint(); + verify(mockQueryMethod, times(1)).getHints(); + verify(mockQueryMethod, times(1)).hasImport(); + verify(mockQueryMethod, times(1)).getImport(); + verify(mockQueryMethod, times(1)).hasLimit(); + verify(mockQueryMethod, times(1)).getLimit(); + verify(mockQueryMethod, times(1)).hasTrace(); + } + + @Test + public void applyHintLimitAndTraceQueryAnnotationExtensionsWithExistingHintAndLimit() { + GemfireQueryMethod mockQueryMethod = mock(GemfireQueryMethod.class, "MockGemfireQueryMethod"); + + when(mockQueryMethod.hasHint()).thenReturn(true); + when(mockQueryMethod.getHints()).thenReturn(Collections.singletonList("FirstNameIdx").toArray(new String[1])); + when(mockQueryMethod.hasImport()).thenReturn(false); + when(mockQueryMethod.hasLimit()).thenReturn(true); + when(mockQueryMethod.getLimit()).thenReturn(50); + when(mockQueryMethod.hasTrace()).thenReturn(true); + + QueryString queryString = new QueryString(" SELECT * FROM /Example LIMIT 25"); + + assertThat(queryString.toString(), is(equalTo(" SELECT * FROM /Example LIMIT 25"))); + + StringBasedGemfireRepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery(); + + QueryString actualQueryString = repositoryQuery.applyQueryAnnotationExtensions(mockQueryMethod, queryString); + + assertThat(actualQueryString, is(notNullValue())); + assertThat(actualQueryString, is(not(sameInstance(queryString)))); + assertThat(actualQueryString.toString(), is(equalTo( + " SELECT * FROM /Example LIMIT 25"))); + + verify(mockQueryMethod, times(1)).hasHint(); + verify(mockQueryMethod, never()).getHints(); + verify(mockQueryMethod, times(1)).hasImport(); + verify(mockQueryMethod, never()).getImport(); + verify(mockQueryMethod, times(1)).hasLimit(); + verify(mockQueryMethod, never()).getLimit(); + verify(mockQueryMethod, times(1)).hasTrace(); + } + + @Test + public void applyImportAndTraceQueryAnnotationExtensionsWithExistingTrace() { + GemfireQueryMethod mockQueryMethod = mock(GemfireQueryMethod.class, "MockGemfireQueryMethod"); + + when(mockQueryMethod.hasHint()).thenReturn(false); + when(mockQueryMethod.hasImport()).thenReturn(true); + when(mockQueryMethod.getImport()).thenReturn("org.example.domain.Type"); + when(mockQueryMethod.hasLimit()).thenReturn(false); + when(mockQueryMethod.hasTrace()).thenReturn(true); + + QueryString queryString = new QueryString(" SELECT * FROM /Example"); + + assertThat(queryString.toString(), is(equalTo(" SELECT * FROM /Example"))); + + StringBasedGemfireRepositoryQuery repositoryQuery = new StringBasedGemfireRepositoryQuery(); + + QueryString actualQueryString = repositoryQuery.applyQueryAnnotationExtensions(mockQueryMethod, queryString); + + assertThat(actualQueryString, is(notNullValue())); + assertThat(actualQueryString, is(not(sameInstance(queryString)))); + assertThat(actualQueryString.toString(), is(equalTo( + "IMPORT org.example.domain.Type; SELECT * FROM /Example"))); + + verify(mockQueryMethod, times(1)).hasHint(); + verify(mockQueryMethod, never()).getHints(); + verify(mockQueryMethod, times(1)).hasImport(); + verify(mockQueryMethod, times(1)).getImport(); + verify(mockQueryMethod, times(1)).hasLimit(); + verify(mockQueryMethod, never()).getLimit(); + verify(mockQueryMethod, times(1)).hasTrace(); + } + }