diff --git a/QueryPartitionedCollection.md b/QueryPartitionedCollection.md new file mode 100644 index 00000000..b94ec9fb --- /dev/null +++ b/QueryPartitionedCollection.md @@ -0,0 +1,58 @@ +### How to Query Partitioned DocumentDB Collection + +With DocumentDB, you can configure [partition key](https://docs.microsoft.com/en-us/azure/cosmos-db/partition-data) for your collection. + +Below is an example about how to query partitioned collection with this spring data module. + +#### Example + +Given a document entity structure: +``` + @Document + @Data + @AllArgsConstructor + public class Address { + @Id + String postalCode; + String street; + @PartitionKey + String city; + } +``` + +Write the repository interface: +``` + @Repository + public interface AddressRepository extends DocumentDbRepository { + // Add query methods here, refer to below + } +``` + +Query by field name: +``` + List
findByCity(String city); +``` + +Delete by field name: +``` + void deleteByStreet(String street); +``` + +For `Partitioned collection`, if you want to query records by `findById(id)`, exception will be thrown. +``` + // Incorrect for partitioned collection, exception will be thrown + Address result = repository.findById(id); // Caution: Works for non-partitioned collection +``` + +Instead, you can query records by ID field name with custom query. +``` + // Correct, postalCode is the ID field in Address domain + @Repository + public interface AddressRepository extends DocumentDbRepository { + List
findByPostalCode(String postalCode); + } + + // Query + List
result = repository.findByPostalCode(postalCode); +``` + diff --git a/README.md b/README.md index 4a0553f1..14b669d9 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ * [Sample Code](#sample-codes) * [Feature List](#feature-list) * [Quick Start](#quick-start) +* [Query Partitioned Collection](QueryPartitionedCollection.md) * [Filing Issues](#filing-issues) * [How to Contribute](#how-to-contribute) * [Code of Conduct](#code-of-conduct) @@ -38,7 +39,7 @@ Please refer to [sample project here](./samplecode). - Custom collection Name. By default, collection name will be class name of user domain class. To customize it, add annotation `@Document(collection="myCustomCollectionName")` to domain class, that's all. - Supports [Azure Cosmos DB partition](https://docs.microsoft.com/en-us/azure/cosmos-db/partition-data). To specify a field of domain class to be partition key field, just annotate it with `@PartitionKey`. When you do CRUD operation, pls specify your partition value. For more sample on partition CRUD, pls refer to [test here](./src/test/java/com/microsoft/azure/spring/data/cosmosdb/documentdb/repository/AddressRepositoryIT.java) -- Supports [Spring Data custom query](https://docs.spring.io/spring-data/commons/docs/current/reference/html/#repositories.query-methods.details) find operation. +- Supports [Spring Data custom query](https://docs.spring.io/spring-data/commons/docs/current/reference/html/#repositories.query-methods.details) find operation, e.g., `findByAFieldAndBField - Supports [spring-boot-starter-data-rest](https://projects.spring.io/spring-data-rest/). - Supports List and nested type in domain class. @@ -104,7 +105,10 @@ public class User { private String lastName; ... // setters and getters - + + public User() { + } + public User(String id, String firstName, String lastName) { this.id = id; this.firstName = firstName; diff --git a/pom.xml b/pom.xml index ac257801..5f18cfb9 100644 --- a/pom.xml +++ b/pom.xml @@ -132,6 +132,12 @@ lombok test + + com.google.guava + guava + 18.0 + test + diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbOperations.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbOperations.java index f3c2f28f..8351cff7 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbOperations.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbOperations.java @@ -7,6 +7,7 @@ package com.microsoft.azure.spring.data.documentdb.core; import com.microsoft.azure.documentdb.DocumentCollection; +import com.microsoft.azure.documentdb.PartitionKey; import com.microsoft.azure.spring.data.documentdb.core.convert.MappingDocumentDbConverter; import com.microsoft.azure.spring.data.documentdb.core.query.Query; @@ -20,47 +21,42 @@ DocumentCollection createCollectionIfNotExists(String collectionName, String partitionKeyFieldName, Integer requestUnit); - List findAll(Class entityClass, - String partitionKeyFieldName, - String partitionKeyFieldValue); + List findAll(Class entityClass); - List findAll(String collectionName, - Class entityClass, - String partitionKeyFieldName, - String partitionKeyFieldValue); + List findAll(String collectionName, Class entityClass); T findById(Object id, - Class entityClass, - String partitionKeyFieldValue); + Class entityClass); T findById(String collectionName, Object id, - Class entityClass, - String partitionKeyFieldValue); + Class entityClass); List find(Query query, Class entityClass, String collectionName); - T insert(T objectToSave, String partitionKeyFieldValue); + T insert(T objectToSave, PartitionKey partitionKey); T insert(String collectionName, T objectToSave, - String partitionKeyFieldValue); + PartitionKey partitionKey); - void upsert(T object, Object id, String partitionKeyFieldValue); + void upsert(T object, Object id, PartitionKey partitionKey); void upsert(String collectionName, T object, Object id, - String partitionKeyFieldValue); + PartitionKey partitionKey); void deleteById(String collectionName, Object id, Class domainClass, - String partitionKeyFieldValue); + PartitionKey partitionKey); void deleteAll(String collectionName); + List delete(Query query, Class entityClass, String collectionName); + MappingDocumentDbConverter getConverter(); } diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplate.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplate.java index 658fb561..1647a5c6 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplate.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplate.java @@ -11,6 +11,8 @@ import com.microsoft.azure.spring.data.documentdb.DocumentDbFactory; import com.microsoft.azure.spring.data.documentdb.core.convert.MappingDocumentDbConverter; import com.microsoft.azure.spring.data.documentdb.core.query.Query; +import com.microsoft.azure.spring.data.documentdb.repository.support.DocumentDbEntityInformation; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -21,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; public class DocumentDbTemplate implements DocumentDbOperations, ApplicationContextAware { private static final Logger LOGGER = LoggerFactory.getLogger(DocumentDbTemplate.class); @@ -54,16 +57,16 @@ public DocumentDbTemplate(DocumentClient client, public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { } - public T insert(T objectToSave, String partitionKeyFieldValue) { + public T insert(T objectToSave, PartitionKey partitionKey) { return insert(getCollectionName(objectToSave.getClass()), objectToSave, - partitionKeyFieldValue); + partitionKey); } public T insert(String collectionName, T objectToSave, - String partitionKeyFieldValue) { + PartitionKey partitionKey) { final Document document = new Document(); mappingDocumentDbConverter.write(objectToSave, document); @@ -75,7 +78,7 @@ public T insert(String collectionName, try { documentDbFactory.getDocumentClient() .createDocument(getCollectionLink(this.databaseName, collectionName), document, - getRequestOptions(partitionKeyFieldValue, null), false); + getRequestOptions(partitionKey, null), false); return objectToSave; } catch (DocumentClientException e) { throw new RuntimeException("insert exception", e); @@ -83,23 +86,20 @@ public T insert(String collectionName, } public T findById(Object id, - Class entityClass, - String partitionKeyFieldValue) { + Class entityClass) { return findById(getCollectionName(entityClass), id, - entityClass, - partitionKeyFieldValue); + entityClass); } public T findById(String collectionName, Object id, - Class entityClass, - String partitionKeyFieldValue) { + Class entityClass) { try { final Resource resource = documentDbFactory.getDocumentClient() .readDocument(getDocumentLink(this.databaseName, collectionName, (String) id), - getRequestOptions(partitionKeyFieldValue, null)).getResource(); + new RequestOptions()).getResource(); if (resource instanceof Document) { final Document document = (Document) resource; @@ -116,12 +116,12 @@ public T findById(String collectionName, } } - public void upsert(T object, Object id, String partitionKeyFieldValue) { - upsert(getCollectionName(object.getClass()), object, id, partitionKeyFieldValue); + public void upsert(T object, Object id, PartitionKey partitionKey) { + upsert(getCollectionName(object.getClass()), object, id, partitionKey); } - public void upsert(String collectionName, T object, Object id, String partitionKeyFieldValue) { + public void upsert(String collectionName, T object, Object id, PartitionKey partitionKey) { try { Document originalDoc = new Document(); if (object instanceof Document) { @@ -138,23 +138,19 @@ public void upsert(String collectionName, T object, Object id, String partit documentDbFactory.getDocumentClient().upsertDocument( getCollectionLink(this.databaseName, collectionName), originalDoc, - getRequestOptions(partitionKeyFieldValue, null), false); + getRequestOptions(partitionKey, null), false); } catch (DocumentClientException ex) { throw new RuntimeException("Failed to upsert document to database.", ex); } } - public List findAll(Class entityClass, - String partitionKeyFieldName, - String partitionKeyFieldValue) { - return findAll(getCollectionName(entityClass), entityClass, partitionKeyFieldName, partitionKeyFieldValue); + public List findAll(Class entityClass) { + return findAll(getCollectionName(entityClass), entityClass); } public List findAll(String collectionName, - final Class entityClass, - String partitionKeyFieldName, - String partitionKeyFieldValue) { + final Class entityClass) { final List collections = documentDbFactory.getDocumentClient(). queryCollections( getDatabaseLink(this.databaseName), @@ -170,15 +166,10 @@ public List findAll(String collectionName, final FeedOptions feedOptions = new FeedOptions(); feedOptions.setEnableCrossPartitionQuery(true); - SqlQuerySpec sqlQuerySpec = new SqlQuerySpec("SELECT * FROM root c"); - if (partitionKeyFieldName != null && !partitionKeyFieldName.isEmpty()) { - sqlQuerySpec = new SqlQuerySpec("SELECT * FROM root c WHERE c." + partitionKeyFieldName + "=@partition", - new SqlParameterCollection(new SqlParameter("@partition", partitionKeyFieldValue))); - feedOptions.setPartitionKey(new PartitionKey(partitionKeyFieldValue)); - } + final SqlQuerySpec sqlQuerySpec = new SqlQuerySpec("SELECT * FROM root c"); final List results = documentDbFactory.getDocumentClient() - .queryDocuments(collections.get(0).getSelfLink(), sqlQuerySpec, feedOptions, partitionKeyFieldName) + .queryDocuments(collections.get(0).getSelfLink(), sqlQuerySpec, feedOptions) .getQueryIterable().toList(); final List entities = new ArrayList<>(); @@ -250,12 +241,6 @@ private Database createDatabaseIfNotExists(String dbName) { } } - public DocumentCollection createCollection(String collectionName, - RequestOptions collectionOptions, - String partitionKeyFieldName) { - return createCollection(this.databaseName, collectionName, collectionOptions, partitionKeyFieldName); - } - public DocumentCollection createCollection(String dbName, String collectionName, RequestOptions collectionOptions, @@ -314,7 +299,7 @@ public DocumentCollection createCollectionIfNotExists(String collectionName, public void deleteById(String collectionName, Object id, Class domainClass, - String partitionKeyFieldValue) { + PartitionKey partitionKey) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("execute deleteById in database {} collection {}", this.databaseName, collectionName); } @@ -322,7 +307,7 @@ public void deleteById(String collectionName, try { documentDbFactory.getDocumentClient().deleteDocument( getDocumentLink(this.databaseName, collectionName, id.toString()), - getRequestOptions(partitionKeyFieldValue, null)); + getRequestOptions(partitionKey, null)); } catch (DocumentClientException ex) { throw new RuntimeException("deleteById exception", ex); @@ -345,14 +330,14 @@ private String getPartitionKeyPath(String partitionKey) { return "/" + partitionKey; } - private RequestOptions getRequestOptions(String partitionKeyValue, Integer requestUnit) { - if ((partitionKeyValue == null || partitionKeyValue.isEmpty()) && requestUnit == null) { + private RequestOptions getRequestOptions(PartitionKey key, Integer requestUnit) { + if (key == null && requestUnit == null) { return null; } final RequestOptions requestOptions = new RequestOptions(); - if (!(partitionKeyValue == null || partitionKeyValue.isEmpty())) { - requestOptions.setPartitionKey(new PartitionKey(partitionKeyValue)); + if (key != null) { + requestOptions.setPartitionKey(key); } if (requestUnit != null) { requestOptions.setOfferThroughput(requestUnit); @@ -362,24 +347,21 @@ private RequestOptions getRequestOptions(String partitionKeyValue, Integer reque } public List find(Query query, Class domainClass, String collectionName) { - final SqlQuerySpec sqlQuerySpec = createSqlQuerySpec(query); + final SqlQuerySpec sqlQuerySpec = createSqlQuerySpec(query, domainClass); - final List collections = documentDbFactory.getDocumentClient(). - queryCollections( - getDatabaseLink(this.databaseName), - new SqlQuerySpec("SELECT * FROM ROOT r WHERE r.id=@id", - new SqlParameterCollection(new SqlParameter("@id", collectionName))), null) - .getQueryIterable().toList(); + // TODO (wepa) Collection link should be created locally without accessing database, + // but currently exception will be thrown if not fetching collection url from database. + // Run repository integration test to reproduce. + final DocumentCollection collection = getDocCollection(collectionName); + final FeedOptions feedOptions = new FeedOptions(); - if (collections.size() != 1) { - throw new RuntimeException("expect only one collection: " + collectionName - + " in database: " + this.databaseName + ", but found " + collections.size()); + final Optional partitionKeyValue = getPartitionKeyValue(query, domainClass); + if (!partitionKeyValue.isPresent()) { + feedOptions.setEnableCrossPartitionQuery(true); } - final FeedOptions feedOptions = new FeedOptions(); - feedOptions.setEnableCrossPartitionQuery(true); final List results = documentDbFactory.getDocumentClient() - .queryDocuments(collections.get(0).getSelfLink(), + .queryDocuments(collection.getSelfLink(), sqlQuerySpec, feedOptions) .getQueryIterable().toList(); @@ -392,19 +374,119 @@ public List find(Query query, Class domainClass, String collectionName return entities; } - private static SqlQuerySpec createSqlQuerySpec(Query query) { - String queryStr = "SELECT * FROM ROOT r WHERE r."; + private DocumentCollection getDocCollection(String collectionName) { + final List collections = documentDbFactory.getDocumentClient(). + queryCollections( + getDatabaseLink(this.databaseName), + new SqlQuerySpec("SELECT * FROM ROOT r WHERE r.id=@id", + new SqlParameterCollection(new SqlParameter("@id", collectionName))), null) + .getQueryIterable().toList(); + + if (collections.size() != 1) { + throw new RuntimeException("expect only one collection: " + collectionName + + " in database: " + this.databaseName + ", but found " + collections.size()); + } + + return collections.get(0); + } + + private static SqlQuerySpec createSqlQuerySpec(Query query, Class entityClass) { + String queryStr = "SELECT * FROM ROOT r WHERE "; + final SqlParameterCollection parameterCollection = new SqlParameterCollection(); for (final Map.Entry entry : query.getCriteria().entrySet()) { - queryStr += entry.getKey() + "=@" + entry.getKey(); + if (queryStr.contains("=@")) { + queryStr += " AND "; + } + + String fieldName = entry.getKey(); + if (isIdField(fieldName, entityClass)) { + fieldName = "id"; + } + + queryStr += "r." + fieldName + "=@" + entry.getKey(); parameterCollection.add(new SqlParameter("@" + entry.getKey(), entry.getValue())); } + return new SqlQuerySpec(queryStr, parameterCollection); } + private static boolean isIdField(String fieldName, Class entityClass) { + if (StringUtils.isEmpty(fieldName)) { + return false; + } + + final DocumentDbEntityInformation entityInfo = new DocumentDbEntityInformation(entityClass); + return fieldName.equals(entityInfo.getId().getName()); + } + @Override public MappingDocumentDbConverter getConverter() { return this.mappingDocumentDbConverter; } + + @Override + public List delete(Query query, Class entityClass, String collectionName) { + final SqlQuerySpec sqlQuerySpec = createSqlQuerySpec(query, entityClass); + final Optional partitionKeyValue = getPartitionKeyValue(query, entityClass); + + final DocumentCollection collection = getDocCollection(collectionName); + final FeedOptions feedOptions = new FeedOptions(); + if (!partitionKeyValue.isPresent()) { + feedOptions.setEnableCrossPartitionQuery(true); + } + + final List results = documentDbFactory.getDocumentClient() + .queryDocuments(collection.getSelfLink(), sqlQuerySpec, feedOptions).getQueryIterable().toList(); + + final RequestOptions options = new RequestOptions(); + if (partitionKeyValue.isPresent()) { + options.setPartitionKey(new PartitionKey(partitionKeyValue.get())); + } + + final List deletedResult = new ArrayList<>(); + for (final Document document : results) { + try { + documentDbFactory.getDocumentClient().deleteDocument((document).getSelfLink(), options); + deletedResult.add(getConverter().read(entityClass, document)); + } catch (DocumentClientException e) { + throw new IllegalStateException( + String.format("Failed to delete document [%s]", (document).getSelfLink()), e); + } + } + + return deletedResult; + } + + private Optional getPartitionKeyValue(Query query, Class domainClass) { + if (query == null) { + return Optional.empty(); + } + + final Optional partitionKeyName = getPartitionKeyField(domainClass); + if (!partitionKeyName.isPresent()) { + return Optional.empty(); + } + + final Map criteria = query.getCriteria(); + // TODO (wepa) Only one partition key value is supported now + final Optional matchedKey = criteria.keySet().stream() + .filter(key -> partitionKeyName.get().equals(key)).findFirst(); + + if (!matchedKey.isPresent()) { + return Optional.empty(); + } + + return Optional.of(criteria.get(matchedKey.get())); + } + + private Optional getPartitionKeyField(Class domainClass) { + final DocumentDbEntityInformation entityInfo = new DocumentDbEntityInformation(domainClass); + if (entityInfo.getPartitionKeyFieldName() == null) { + return Optional.empty(); + } + + return Optional.of(entityInfo.getPartitionKeyFieldName()); + } } diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/core/query/Criteria.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/core/query/Criteria.java index c83cbdc7..c8e84503 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/core/query/Criteria.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/core/query/Criteria.java @@ -5,17 +5,25 @@ */ package com.microsoft.azure.spring.data.documentdb.core.query; -import java.util.LinkedHashMap; - +import java.util.ArrayList; +import java.util.List; public class Criteria implements CriteriaDefinition { private String key; private Object value; + private List criteriaChain; + + public Criteria(String key) { + this.criteriaChain = new ArrayList<>(); + this.criteriaChain.add(this); + this.key = key; + } - public Criteria(String key, LinkedHashMap value) { + protected Criteria(List criteriaChain, String key) { + this.criteriaChain = criteriaChain; + this.criteriaChain.add(this); this.key = key; - this.value = value.get(key); } public Object getCriteriaObject() { @@ -26,11 +34,20 @@ public String getKey() { return key; } - public static Criteria where(String key, Object value) { - return new Criteria(key, (LinkedHashMap) value); + public static Criteria where(String key) { + return new Criteria(key); } public Criteria is(Object o) { + this.value = o; return this; } + + public Criteria and(String key) { + return new Criteria(this.criteriaChain, key); + } + + public List getCriteriaChain() { + return criteriaChain; + } } diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/core/query/Query.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/core/query/Query.java index 6e4a1897..ae4861a5 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/core/query/Query.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/core/query/Query.java @@ -6,21 +6,25 @@ package com.microsoft.azure.spring.data.documentdb.core.query; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; public class Query { private final Map criteria = new LinkedHashMap<>(); - public static Query query(CriteriaDefinition criteriaDefinition) { - return new Query(criteriaDefinition); + public static Query query(Criteria criteria) { + return new Query(criteria); } public Query() { } - public Query(CriteriaDefinition criteriaDefinition) { - addCriteria(criteriaDefinition); + public Query(Criteria criteria) { + final List criteriaList = criteria.getCriteriaChain(); + for (final Criteria c : criteriaList) { + addCriteria(c); + } } public Query addCriteria(CriteriaDefinition criteriaDefinition) { diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/DocumentDbRepository.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/DocumentDbRepository.java index 6f7e381b..0427c89e 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/DocumentDbRepository.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/DocumentDbRepository.java @@ -10,16 +10,8 @@ import org.springframework.data.repository.NoRepositoryBean; import java.io.Serializable; -import java.util.List; @NoRepositoryBean public interface DocumentDbRepository extends CrudRepository { - List findAll(String partitionKeyValue); - - T findOne(ID id, String partitionKeyValue); - - void delete(ID id, String partitionKeyValue); - - void delete(T entity, String partitionKeyValue); } diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/AbstractDocumentDbQuery.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/AbstractDocumentDbQuery.java index 7457a87a..e6c2706b 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/AbstractDocumentDbQuery.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/AbstractDocumentDbQuery.java @@ -34,9 +34,9 @@ public Object execute(Object[] parameters) { private DocumentDbQueryExecution getExecution(Query query, DocumentDbParameterAccessor accessor) { if (isDeleteQuery()) { - return new DocumentDbQueryExecution.DeleteExecution(); + return new DocumentDbQueryExecution.DeleteExecution(operations); } else { - return new DocumentDbQueryExecution.SingleEntityExecution(operations); + return new DocumentDbQueryExecution.MultiEntityExecution(operations); } } diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/DocumentDbQueryCreator.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/DocumentDbQueryCreator.java index 58dfcb29..e905fc3a 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/DocumentDbQueryCreator.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/DocumentDbQueryCreator.java @@ -8,6 +8,7 @@ import com.microsoft.azure.spring.data.documentdb.core.mapping.DocumentDbPersistentProperty; import com.microsoft.azure.spring.data.documentdb.core.query.Criteria; import com.microsoft.azure.spring.data.documentdb.core.query.Query; +import org.apache.commons.lang3.NotImplementedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Sort; @@ -18,10 +19,7 @@ import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.PartTree; -import java.util.ArrayList; -import java.util.Collection; import java.util.Iterator; -import java.util.LinkedHashMap; public class DocumentDbQueryCreator extends AbstractQueryCreator { @@ -44,24 +42,22 @@ protected Criteria create(Part part, Iterator iterator) { final PersistentPropertyPath propertyPath = mappingContext.getPersistentPropertyPath(part.getProperty()); final DocumentDbPersistentProperty property = propertyPath.getLeafProperty(); - final LinkedHashMap params = new LinkedHashMap(); - final Collection clonedIterator = new ArrayList(); + final Criteria criteria = from(part, property, Criteria.where(propertyPath.toDotPath()), iterator); - while (iterator.hasNext()) { - final Object obj = iterator.next(); - params.put(propertyPath.toDotPath(), obj); - clonedIterator.add(obj); - } - - final Criteria criteria = from(part, property, Criteria.where(propertyPath.toDotPath(), params), - clonedIterator.iterator()); return criteria; } @Override protected Criteria and(Part part, Criteria base, Iterator iterator) { - // not supported yet - return null; + if (base == null) { + return create(part, iterator); + } + + final PersistentPropertyPath path = + mappingContext.getPersistentPropertyPath(part.getProperty()); + final DocumentDbPersistentProperty property = path.getLeafProperty(); + + return from(part, property, base.and(path.toDotPath()), iterator); } @Override @@ -73,7 +69,7 @@ protected Query complete(Criteria criteria, Sort sort) { @Override protected Criteria or(Criteria base, Criteria criteria) { // not supported yet - return null; + throw new NotImplementedException("Criteria or is not supported."); } private Criteria from(Part part, DocumentDbPersistentProperty property, diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/DocumentDbQueryExecution.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/DocumentDbQueryExecution.java index 9a965181..7540cf53 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/DocumentDbQueryExecution.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/DocumentDbQueryExecution.java @@ -7,8 +7,6 @@ import com.microsoft.azure.spring.data.documentdb.core.DocumentDbOperations; import com.microsoft.azure.spring.data.documentdb.core.query.Query; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; - public interface DocumentDbQueryExecution { Object execute(Query query, Class type, String collection); @@ -27,11 +25,11 @@ public Object execute(Query query, Class type, String collection) { } } - final class SingleEntityExecution implements DocumentDbQueryExecution { + final class MultiEntityExecution implements DocumentDbQueryExecution { private final DocumentDbOperations operations; - public SingleEntityExecution(DocumentDbOperations operations) { + public MultiEntityExecution(DocumentDbOperations operations) { this.operations = operations; } @@ -42,11 +40,15 @@ public Object execute(Query query, Class type, String collection) { } final class DeleteExecution implements DocumentDbQueryExecution { + private final DocumentDbOperations operations; + + public DeleteExecution(DocumentDbOperations operations) { + this.operations = operations; + } @Override public Object execute(Query query, Class type, String collection) { - // deletion not supported yet - throw new NotImplementedException(); + return operations.delete(query, type, collection); } } } diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/PartTreeDocumentDbQuery.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/PartTreeDocumentDbQuery.java index 2dc06799..8d454994 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/PartTreeDocumentDbQuery.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/query/PartTreeDocumentDbQuery.java @@ -8,6 +8,7 @@ import com.microsoft.azure.spring.data.documentdb.core.DocumentDbOperations; import com.microsoft.azure.spring.data.documentdb.core.mapping.DocumentDbPersistentProperty; import com.microsoft.azure.spring.data.documentdb.core.query.Query; +import org.apache.commons.lang3.NotImplementedException; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.parser.PartTree; @@ -37,7 +38,7 @@ protected Query createQuery(DocumentDbParameterAccessor accessor) { final Query query = creator.createQuery(); if (tree.isLimiting()) { - System.out.println("not implement yet!"); + throw new NotImplementedException("Limiting is not supported."); } return query; } diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/support/DocumentDbEntityInformation.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/support/DocumentDbEntityInformation.java index 3795b9b9..e4bc0f66 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/support/DocumentDbEntityInformation.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/support/DocumentDbEntityInformation.java @@ -8,6 +8,7 @@ import com.microsoft.azure.spring.data.documentdb.core.mapping.Document; import com.microsoft.azure.spring.data.documentdb.core.mapping.PartitionKey; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.springframework.data.annotation.Id; import org.springframework.data.repository.core.support.AbstractEntityInformation; @@ -48,6 +49,10 @@ public ID getId(T entity) { return (ID) ReflectionUtils.getField(id, entity); } + public Field getId() { + return this.id; + } + public Class getIdType() { return (Class) id.getType(); } diff --git a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/support/SimpleDocumentDbRepository.java b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/support/SimpleDocumentDbRepository.java index 20d8a11c..87bd4c61 100644 --- a/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/support/SimpleDocumentDbRepository.java +++ b/src/main/java/com/microsoft/azure/spring/data/documentdb/repository/support/SimpleDocumentDbRepository.java @@ -8,7 +8,9 @@ import com.microsoft.azure.spring.data.documentdb.core.DocumentDbOperations; +import com.microsoft.azure.documentdb.PartitionKey; import com.microsoft.azure.spring.data.documentdb.repository.DocumentDbRepository; +import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationContext; import org.springframework.util.Assert; @@ -52,17 +54,25 @@ public S save(S entity) { if (entityInformation.isNew(entity)) { documentDbOperations.insert(entityInformation.getCollectionName(), entity, - entityInformation.getPartitionKeyFieldValue(entity)); + createKey(entityInformation.getPartitionKeyFieldValue(entity))); } else { documentDbOperations.upsert(entityInformation.getCollectionName(), entity, entityInformation.getId(entity), - entityInformation.getPartitionKeyFieldValue(entity)); + createKey(entityInformation.getPartitionKeyFieldValue(entity))); } return entity; } + private PartitionKey createKey(String partitionKeyValue) { + if (StringUtils.isEmpty(partitionKeyValue)) { + return null; + } + + return new PartitionKey(partitionKeyValue); + } + /** * batch save entities * @@ -85,14 +95,13 @@ public Iterable save(Iterable entities) { } /** - * find all entities from one collection without partition + * find all entities from one collection without configuring partition key value * * @return */ @Override public Iterable findAll() { - return documentDbOperations.findAll(entityInformation.getCollectionName(), - entityInformation.getJavaType(), null, null); + return documentDbOperations.findAll(entityInformation.getCollectionName(), entityInformation.getJavaType()); } /** @@ -104,6 +113,7 @@ public Iterable findAll() { @Override public List findAll(Iterable ids) { final List entities = new ArrayList(); + for (final ID id : ids) { final T entity = findOne(id); @@ -124,7 +134,7 @@ public List findAll(Iterable ids) { public T findOne(ID id) { Assert.notNull(id, "id must not be null"); return documentDbOperations.findById( - entityInformation.getCollectionName(), id, entityInformation.getJavaType(), null); + entityInformation.getCollectionName(), id, entityInformation.getJavaType()); } /** @@ -138,7 +148,7 @@ public long count() { } /** - * delete one document per id without partitions + * delete one document per id without configuring partition key value * * @param id */ @@ -151,16 +161,18 @@ public void delete(ID id) { } /** - * delete one document per entity without partitions + * delete one document per entity * * @param entity */ @Override public void delete(T entity) { + final String paritionKeyValue = entityInformation.getPartitionKeyFieldValue(entity); + documentDbOperations.deleteById(entityInformation.getCollectionName(), entityInformation.getId(entity), entityInformation.getJavaType(), - null); + paritionKeyValue == null ? null : new PartitionKey(paritionKeyValue)); } /** @@ -193,62 +205,4 @@ public void delete(Iterable entities) { public boolean exists(ID primaryKey) { return findOne(primaryKey) != null; } - - /** - * find all entities from one collection with partitions - * - * @param partitionKeyValue - * @return - */ - public List findAll(String partitionKeyValue) { - return documentDbOperations.findAll(entityInformation.getCollectionName(), - entityInformation.getJavaType(), - entityInformation.getPartitionKeyFieldName(), - partitionKeyValue); - } - - /** - * find one entity per id with partitions - * - * @param id - * @param partitionKeyValue - * @return - */ - public T findOne(ID id, String partitionKeyValue) { - Assert.notNull(id, "id must not be null"); - Assert.notNull(partitionKeyValue, "partitionKeyValue must not be null"); - - return documentDbOperations.findById( - entityInformation.getCollectionName(), - id, - entityInformation.getJavaType(), - partitionKeyValue); - } - - /** - * delete an entity per id with partitions - * - * @param id - * @param partitionKeyValue - */ - public void delete(ID id, String partitionKeyValue) { - documentDbOperations.deleteById(entityInformation.getCollectionName(), - id, - entityInformation.getJavaType(), - partitionKeyValue); - - } - - /** - * delete an entity with partitions - * - * @param entity - * @param partitionKeyValue - */ - public void delete(T entity, String partitionKeyValue) { - documentDbOperations.deleteById(entityInformation.getCollectionName(), - entityInformation.getId(entity), - entityInformation.getJavaType(), - partitionKeyValue); - } } diff --git a/src/test/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplateIT.java b/src/test/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplateIT.java index 853da6b9..ff5971c0 100644 --- a/src/test/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplateIT.java +++ b/src/test/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplateIT.java @@ -43,8 +43,6 @@ public class DocumentDbTemplateIT { private static final List
ADDRESSES = Constants.ADDRESSES; private static final Person TEST_PERSON = new Person(TEST_ID, "testfirstname", "testlastname", HOBBIES, ADDRESSES); - private static final String PARTITION_KEY = "lastName"; - @Value("${documentdb.uri}") private String documentDbUri; @Value("${documentdb.key}") @@ -85,24 +83,13 @@ public void cleanup() { } @Test(expected = RuntimeException.class) - public void testInsertDuplicateId() throws Exception { + public void testInsertDuplicateId() { dbTemplate.insert(Person.class.getSimpleName(), TEST_PERSON, null); } @Test public void testFindAll() { - final List result = dbTemplate.findAll(Person.class.getSimpleName(), Person.class, null, null); - assertThat(result.size()).isEqualTo(1); - assertThat(result.get(0)).isEqualTo(TEST_PERSON); - } - - @Test - public void testFindAllPartition() { - setupPartition(); - - final List result = dbTemplate.findAll(Person.class.getSimpleName(), - Person.class, PARTITION_KEY, TEST_PERSON.getLastName()); - + final List result = dbTemplate.findAll(Person.class.getSimpleName(), Person.class); assertThat(result.size()).isEqualTo(1); assertThat(result.get(0)).isEqualTo(TEST_PERSON); } @@ -110,24 +97,11 @@ public void testFindAllPartition() { @Test public void testFindById() { final Person result = dbTemplate.findById(Person.class.getSimpleName(), - TEST_PERSON.getId(), Person.class, null); - assertTrue(result.equals(TEST_PERSON)); - - final Person nullResult = dbTemplate.findById(Person.class.getSimpleName(), - TEST_NOTEXIST_ID, Person.class, null); - assertThat(nullResult).isNull(); - } - - @Test - public void testFindByIdPartition() { - setupPartition(); - - final Person result = dbTemplate.findById(Person.class.getSimpleName(), - TEST_PERSON.getId(), Person.class, TEST_PERSON.getLastName()); + TEST_PERSON.getId(), Person.class); assertTrue(result.equals(TEST_PERSON)); final Person nullResult = dbTemplate.findById(Person.class.getSimpleName(), - TEST_NOTEXIST_ID, Person.class, TEST_PERSON.getLastName()); + TEST_NOTEXIST_ID, Person.class); assertThat(nullResult).isNull(); } @@ -141,27 +115,7 @@ public void testUpsertNewDocument() { dbTemplate.upsert(Person.class.getSimpleName(), newPerson, null, null); - final List result = dbTemplate.findAll(Person.class, null, null); - - assertThat(result.size()).isEqualTo(1); - assertTrue(result.get(0).getFirstName().equals(firstName)); - } - - @Test - public void testUpsertNewDocumentPartition() { - // Delete first as was inserted in setup - dbTemplate.deleteById(Person.class.getSimpleName(), - TEST_PERSON.getId(), Person.class, null); - - setupPartition(); - - final String firstName = "newFirstName_" + UUID.randomUUID().toString(); - final Person newPerson = new Person(null, firstName, "newLastName", null, null); - - final String partitionKeyValue = newPerson.getLastName(); - dbTemplate.upsert(Person.class.getSimpleName(), newPerson, null, partitionKeyValue); - - final List result = dbTemplate.findAll(Person.class, PARTITION_KEY, partitionKeyValue); + final List result = dbTemplate.findAll(Person.class); assertThat(result.size()).isEqualTo(1); assertTrue(result.get(0).getFirstName().equals(firstName)); @@ -174,20 +128,7 @@ public void testUpdate() { dbTemplate.upsert(Person.class.getSimpleName(), updated, updated.getId(), null); final Person result = dbTemplate.findById(Person.class.getSimpleName(), - updated.getId(), Person.class, null); - - assertTrue(result.equals(updated)); - } - - @Test - public void testUpdatePartition() { - setupPartition(); - final Person updated = new Person(TEST_PERSON.getId(), "updatedname", - TEST_PERSON.getLastName(), TEST_PERSON.getHobbies(), TEST_PERSON.getShippingAddresses()); - dbTemplate.upsert(Person.class.getSimpleName(), updated, updated.getId(), updated.getLastName()); - - final Person result = dbTemplate.findById(Person.class.getSimpleName(), - updated.getId(), Person.class, updated.getLastName()); + updated.getId(), Person.class); assertTrue(result.equals(updated)); } @@ -196,37 +137,12 @@ public void testUpdatePartition() { public void testDeleteById() { final Person person2 = new Person("newid", "newfn", "newln", HOBBIES, ADDRESSES); dbTemplate.insert(person2, null); - assertThat(dbTemplate.findAll(Person.class, null, null).size()).isEqualTo(2); + assertThat(dbTemplate.findAll(Person.class).size()).isEqualTo(2); dbTemplate.deleteById(Person.class.getSimpleName(), TEST_PERSON.getId(), null, null); - final List result = dbTemplate.findAll(Person.class, null, null); + final List result = dbTemplate.findAll(Person.class); assertThat(result.size()).isEqualTo(1); assertTrue(result.get(0).equals(person2)); } - - @Test - public void testDeleteByIdPartition() { - setupPartition(); - - // insert new document with same partition key - final Person person2 = new Person("newid", "newfn", TEST_PERSON.getLastName(), HOBBIES, ADDRESSES); - dbTemplate.insert(Person.class.getSimpleName(), person2, person2.getLastName()); - - assertThat(dbTemplate.findAll(Person.class, PARTITION_KEY, person2.getLastName()).size()).isEqualTo(2); - - dbTemplate.deleteById(Person.class.getSimpleName(), - TEST_PERSON.getId(), Person.class, TEST_PERSON.getLastName()); - - final List result = dbTemplate.findAll(Person.class, PARTITION_KEY, person2.getLastName()); - assertThat(result.size()).isEqualTo(1); - assertTrue(result.get(0).equals(person2)); - } - - private void setupPartition() { - cleanup(); - - dbTemplate.createCollectionIfNotExists(Person.class.getSimpleName(), PARTITION_KEY, 1000); - dbTemplate.insert(Person.class.getSimpleName(), TEST_PERSON, TEST_PERSON.getLastName()); - } } diff --git a/src/test/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplatePartitionIT.java b/src/test/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplatePartitionIT.java new file mode 100644 index 00000000..b6a76e88 --- /dev/null +++ b/src/test/java/com/microsoft/azure/spring/data/documentdb/core/DocumentDbTemplatePartitionIT.java @@ -0,0 +1,172 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for + * license information. + */ + +package com.microsoft.azure.spring.data.documentdb.core; + +import com.microsoft.azure.documentdb.ConnectionPolicy; +import com.microsoft.azure.documentdb.ConsistencyLevel; +import com.microsoft.azure.documentdb.DocumentClient; +import com.microsoft.azure.documentdb.PartitionKey; +import com.microsoft.azure.spring.data.documentdb.Constants; +import com.microsoft.azure.spring.data.documentdb.core.convert.MappingDocumentDbConverter; +import com.microsoft.azure.spring.data.documentdb.core.mapping.DocumentDbMappingContext; +import com.microsoft.azure.spring.data.documentdb.core.query.Criteria; +import com.microsoft.azure.spring.data.documentdb.core.query.Query; +import com.microsoft.azure.spring.data.documentdb.domain.Address; +import com.microsoft.azure.spring.data.documentdb.domain.Person; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.domain.EntityScanner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.PropertySource; +import org.springframework.data.annotation.Persistent; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertTrue; + +@RunWith(SpringJUnit4ClassRunner.class) +@PropertySource(value = {"classpath:application.properties"}) +public class DocumentDbTemplatePartitionIT { + private static final String TEST_ID = "template_it_id"; + private static final String TEST_NOTEXIST_ID = "non_exist_id"; + + private static final String TEST_DB_NAME = "template_it_db"; + private static final List HOBBIES = Constants.HOBBIES; + private static final List
ADDRESSES = Constants.ADDRESSES; + private static final Person TEST_PERSON = new Person(TEST_ID, "testfirstname", "testlastname", HOBBIES, ADDRESSES); + + private static final String PARTITION_KEY = "lastName"; + + @Value("${documentdb.uri}") + private String documentDbUri; + @Value("${documentdb.key}") + private String documentDbKey; + + private DocumentClient documentClient; + private DocumentDbTemplate dbTemplate; + + private MappingDocumentDbConverter dbConverter; + private DocumentDbMappingContext mappingContext; + + @Autowired + private ApplicationContext applicationContext; + + @Before + public void setup() { + mappingContext = new DocumentDbMappingContext(); + try { + mappingContext.setInitialEntitySet(new EntityScanner(this.applicationContext) + .scan(Persistent.class)); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e.getMessage()); + + } + dbConverter = new MappingDocumentDbConverter(mappingContext); + documentClient = new DocumentClient(documentDbUri, documentDbKey, + ConnectionPolicy.GetDefault(), ConsistencyLevel.Session); + + dbTemplate = new DocumentDbTemplate(documentClient, dbConverter, TEST_DB_NAME); + + dbTemplate.createCollectionIfNotExists(Person.class.getSimpleName(), PARTITION_KEY, 1000); + dbTemplate.insert(Person.class.getSimpleName(), TEST_PERSON, new PartitionKey(TEST_PERSON.getLastName())); + } + + @After + public void cleanup() { + dbTemplate.deleteAll(Person.class.getSimpleName()); + } + + @Test + public void testFindAllByPartition() { + final Criteria criteria = new Criteria("lastName"); + criteria.is(TEST_PERSON.getLastName()); + final Query query = new Query(criteria); + + final List result = dbTemplate.find(query, Person.class, Person.class.getSimpleName()); + assertThat(result.size()).isEqualTo(1); + assertTrue(result.get(0).equals(TEST_PERSON)); + } + + @Test + public void testFindByIdWithPartition() { + final Criteria criteria = new Criteria("id"); + criteria.is(TEST_PERSON.getId()); + criteria.and("lastName").is(TEST_PERSON.getLastName()); + final Query query = new Query(criteria); + + final List result = dbTemplate.find(query, Person.class, Person.class.getSimpleName()); + assertThat(result.size()).isEqualTo(1); + assertTrue(result.get(0).equals(TEST_PERSON)); + } + + @Test + public void testFindByNonExistIdWithPartition() { + final Criteria criteria = new Criteria("id"); + criteria.is(TEST_NOTEXIST_ID); + criteria.and("lastName").is(TEST_PERSON.getLastName()); + final Query query = new Query(criteria); + + final List result = dbTemplate.find(query, Person.class, Person.class.getSimpleName()); + assertThat(result.size()).isEqualTo(0); + } + + @Test + public void testUpsertNewDocumentPartition() { + final String firstName = "newFirstName_" + UUID.randomUUID().toString(); + final Person newPerson = new Person(null, firstName, "newLastName", null, null); + + final String partitionKeyValue = newPerson.getLastName(); + dbTemplate.upsert(Person.class.getSimpleName(), newPerson, null, new PartitionKey(partitionKeyValue)); + + final List result = dbTemplate.findAll(Person.class); + + assertThat(result.size()).isEqualTo(2); + + final Person person = result.stream() + .filter(p -> p.getLastName().equals(partitionKeyValue)).findFirst().get(); + assertThat(person.getFirstName()).isEqualTo(firstName); + } + + @Test + public void testUpdatePartition() { + final Person updated = new Person(TEST_PERSON.getId(), "updatedname", + TEST_PERSON.getLastName(), TEST_PERSON.getHobbies(), TEST_PERSON.getShippingAddresses()); + dbTemplate.upsert(Person.class.getSimpleName(), updated, updated.getId(), + new PartitionKey(updated.getLastName())); + + final List result = dbTemplate.findAll(Person.class); + final Person person = result.stream().filter(p -> TEST_PERSON.getId().equals(p.getId())).findFirst().get(); + + assertTrue(person.equals(updated)); + } + + @Test + public void testDeleteByIdPartition() { + // insert new document with same partition key + final Person person2 = new Person("newid", "newfn", TEST_PERSON.getLastName(), HOBBIES, ADDRESSES); + dbTemplate.insert(Person.class.getSimpleName(), person2, new PartitionKey(person2.getLastName())); + + final List inserted = dbTemplate.findAll(Person.class); + assertThat(inserted.size()).isEqualTo(2); + assertThat(inserted.get(0).getLastName()).isEqualTo(TEST_PERSON.getLastName()); + assertThat(inserted.get(1).getLastName()).isEqualTo(TEST_PERSON.getLastName()); + + dbTemplate.deleteById(Person.class.getSimpleName(), + TEST_PERSON.getId(), Person.class, new PartitionKey(TEST_PERSON.getLastName())); + + final List result = dbTemplate.findAll(Person.class); + assertThat(result.size()).isEqualTo(1); + assertTrue(result.get(0).equals(person2)); + } +} diff --git a/src/test/java/com/microsoft/azure/spring/data/documentdb/core/converter/DocumentDbConverterUnitTest.java b/src/test/java/com/microsoft/azure/spring/data/documentdb/core/converter/DocumentDbConverterUnitTest.java index c6c9f874..e5180b4d 100644 --- a/src/test/java/com/microsoft/azure/spring/data/documentdb/core/converter/DocumentDbConverterUnitTest.java +++ b/src/test/java/com/microsoft/azure/spring/data/documentdb/core/converter/DocumentDbConverterUnitTest.java @@ -78,7 +78,7 @@ public void testConvertFromDocumentToEntity() { assertThat(person.getId()).isEqualTo(id); assertThat(person.getFirstName()).isEqualTo(firstName); assertThat(person.getLastName()).isEqualTo(lastName); - assertThat(person.getHobbies().equals(hobbies)); - assertThat(person.getShippingAddresses().equals(addresses)); + assertThat(person.getHobbies()).isEqualTo(hobbies); + assertThat(person.getShippingAddresses()).isEqualTo(addresses); } } diff --git a/src/test/java/com/microsoft/azure/spring/data/documentdb/core/query/CriteriaUnitTest.java b/src/test/java/com/microsoft/azure/spring/data/documentdb/core/query/CriteriaUnitTest.java index 2ecd21cf..ed4d2ea2 100644 --- a/src/test/java/com/microsoft/azure/spring/data/documentdb/core/query/CriteriaUnitTest.java +++ b/src/test/java/com/microsoft/azure/spring/data/documentdb/core/query/CriteriaUnitTest.java @@ -7,6 +7,7 @@ import org.junit.Test; +import java.util.ArrayList; import java.util.LinkedHashMap; import static org.assertj.core.api.Assertions.assertThat; @@ -15,9 +16,8 @@ public class CriteriaUnitTest { @Test public void testSimpleCriteria() { - final LinkedHashMap value = new LinkedHashMap(); - value.put("name", "test"); - final Criteria c = new Criteria("name", value); + final Criteria c = new Criteria(new ArrayList<>(), "name"); + c.is("test"); assertThat(c.getKey()).isEqualTo("name"); assertThat(c.getCriteriaObject()).isEqualTo("test"); diff --git a/src/test/java/com/microsoft/azure/spring/data/documentdb/core/query/QueryUnitTest.java b/src/test/java/com/microsoft/azure/spring/data/documentdb/core/query/QueryUnitTest.java index 5f03d571..b2ca4f5c 100644 --- a/src/test/java/com/microsoft/azure/spring/data/documentdb/core/query/QueryUnitTest.java +++ b/src/test/java/com/microsoft/azure/spring/data/documentdb/core/query/QueryUnitTest.java @@ -7,6 +7,7 @@ import org.junit.Test; +import java.util.ArrayList; import java.util.LinkedHashMap; import static org.assertj.core.api.Assertions.assertThat; @@ -15,10 +16,10 @@ public class QueryUnitTest { @Test public void testAddCriteria() { - final LinkedHashMap values = new LinkedHashMap<>(); - values.putIfAbsent("name", "test"); + final Criteria criteria = new Criteria(new ArrayList<>(), "name"); + criteria.is("test"); - final Query query = new Query().addCriteria(new Criteria("name", values)); + final Query query = new Query().addCriteria(criteria); assertThat(query.getCriteria().size()).isEqualTo(1); assertThat(query.getCriteria().get("name")).isEqualTo("test"); @@ -26,11 +27,7 @@ public void testAddCriteria() { @Test public void testWhere() { - final LinkedHashMap values = new LinkedHashMap<>(); - values.putIfAbsent("name", "test"); - - final Query query = new Query((Criteria.where("name", values))); - + final Query query = new Query((Criteria.where("name").is("test"))); assertThat(query.getCriteria().size()).isEqualTo(1); assertThat(query.getCriteria().get("name")).isEqualTo("test"); } diff --git a/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/AddressRepository.java b/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/AddressRepository.java index fdb6517b..d32bbd72 100644 --- a/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/AddressRepository.java +++ b/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/AddressRepository.java @@ -8,6 +8,17 @@ import com.microsoft.azure.spring.data.documentdb.domain.Address; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface AddressRepository extends DocumentDbRepository { + void deleteByPostalCodeAndCity(String postalCode, String city); + + void deleteByCity(String city); + + List
findByPostalCodeAndCity(String postalCode, String city); + + List
findByCity(String city); + + List
findByPostalCode(String postalCode); } diff --git a/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/AddressRepositoryIT.java b/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/AddressRepositoryIT.java index 527d815f..c3a46c1a 100644 --- a/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/AddressRepositoryIT.java +++ b/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/AddressRepositoryIT.java @@ -8,7 +8,9 @@ import com.microsoft.azure.spring.data.documentdb.domain.Address; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; @@ -26,15 +28,20 @@ public class AddressRepositoryIT { private static final Address TEST_ADDRESS1_PARTITION1 = new Address("111", "111st avenue", "redmond"); private static final Address TEST_ADDRESS2_PARTITION1 = new Address("222", "98th street", "redmond"); private static final Address TEST_ADDRESS1_PARTITION2 = new Address("333", "103rd street", "bellevue"); + private static final Address TEST_ADDRESS4_PARTITION3 = new Address("111", "100rd street", "new york"); @Autowired AddressRepository repository; + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Before public void setup() { repository.save(TEST_ADDRESS1_PARTITION1); repository.save(TEST_ADDRESS1_PARTITION2); repository.save(TEST_ADDRESS2_PARTITION1); + repository.save(TEST_ADDRESS4_PARTITION3); } @After @@ -47,38 +54,67 @@ public void testFindAll() { // findAll cross partition final List
result = toList(repository.findAll()); - assertThat(result.size()).isEqualTo(3); + assertThat(result.size()).isEqualTo(4); + } - // findAll per partition - final List
partition1 = repository.findAll(TEST_ADDRESS1_PARTITION1.getCity()); - assertThat(partition1.size()).isEqualTo(2); + @Test + public void testFindByIdForPartitionedCollection() { + final List
addresses = repository.findByPostalCode("111"); - final List
partition2 = repository.findAll(TEST_ADDRESS1_PARTITION2.getCity()); - assertThat(partition2.size()).isEqualTo(1); + assertThat(addresses.size()).isEqualTo(2); + assertThat(addresses.get(0).getPostalCode().equals("111")); + assertThat(addresses.get(1).getPostalCode().equals("111")); } @Test - public void testCountAndDeleteByID() { - long count = repository.count(); - assertThat(count).isEqualTo(3); + public void testFindByPartitionedCity() { + final String city = TEST_ADDRESS1_PARTITION1.getCity(); + final List
result = toList(repository.findByCity(city)); - repository.delete(TEST_ADDRESS1_PARTITION1.getPostalCode(), TEST_ADDRESS1_PARTITION1.getCity()); + assertThat(result.size()).isEqualTo(2); + assertThat(result.get(0).getCity()).isEqualTo(city); + assertThat(result.get(1).getCity()).isEqualTo(city); + } - final List
result = toList(repository.findAll()); - assertThat(result.size()).isEqualTo(2); + @Test + public void testCount() { + final long count = repository.count(); + assertThat(count).isEqualTo(4); + } - count = repository.count(); - assertThat(count).isEqualTo(2); + @Test + public void deleteWithoutPartitionedColumnShouldFail() { + expectedException.expect(UnsupportedOperationException.class); + expectedException.expectMessage("PartitionKey value must be supplied for this operation."); + + repository.delete(TEST_ADDRESS1_PARTITION1.getPostalCode()); + } + + @Test + public void canDeleteByIdAndPartitionedCity() { + final long count = repository.count(); + assertThat(count).isEqualTo(4); + + repository.deleteByPostalCodeAndCity( + TEST_ADDRESS1_PARTITION1.getPostalCode(), TEST_ADDRESS1_PARTITION1.getCity()); + + final List
result = toList(repository.findAll()); + + assertThat(result.size()).isEqualTo(3); } @Test - public void testCountAndDeleteEntity() { - repository.delete(TEST_ADDRESS1_PARTITION1, TEST_ADDRESS1_PARTITION1.getCity()); + public void canDeleteByPartitionedCity() { + final long count = repository.count(); + assertThat(count).isEqualTo(4); + + repository.deleteByCity(TEST_ADDRESS1_PARTITION1.getCity()); final List
result = toList(repository.findAll()); assertThat(result.size()).isEqualTo(2); + assertThat(result.get(0).getCity()).isNotEqualTo(TEST_ADDRESS1_PARTITION1.getCity()); } @Test @@ -88,10 +124,12 @@ public void testUpdateEntity() { repository.save(updatedAddress); - final Address address = repository.findOne(updatedAddress.getPostalCode(), updatedAddress.getCity()); + final List
results = + repository.findByPostalCodeAndCity(updatedAddress.getPostalCode(), updatedAddress.getCity()); - assertThat(address.getStreet()).isEqualTo(updatedAddress.getStreet()); - assertThat(address.getPostalCode()).isEqualTo(updatedAddress.getPostalCode()); + assertThat(results.size()).isEqualTo(1); + assertThat(results.get(0).getStreet()).isEqualTo(updatedAddress.getStreet()); + assertThat(results.get(0).getPostalCode()).isEqualTo(updatedAddress.getPostalCode()); } private List toList(Iterable iterable) { diff --git a/src/test/java/com/microsoft/azure/spring/data/documentdb/domain/PersonRepository.java b/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/PersonRepository.java similarity index 68% rename from src/test/java/com/microsoft/azure/spring/data/documentdb/domain/PersonRepository.java rename to src/test/java/com/microsoft/azure/spring/data/documentdb/repository/PersonRepository.java index 7004f326..13eba4f4 100644 --- a/src/test/java/com/microsoft/azure/spring/data/documentdb/domain/PersonRepository.java +++ b/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/PersonRepository.java @@ -4,9 +4,9 @@ * license information. */ -package com.microsoft.azure.spring.data.documentdb.domain; +package com.microsoft.azure.spring.data.documentdb.repository; -import com.microsoft.azure.spring.data.documentdb.repository.DocumentDbRepository; +import com.microsoft.azure.spring.data.documentdb.domain.Person; import org.springframework.stereotype.Repository; @Repository diff --git a/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/SimpleDocumentDbRepositoryUnitTest.java b/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/SimpleDocumentDbRepositoryUnitTest.java index b1f7309d..5b784755 100644 --- a/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/SimpleDocumentDbRepositoryUnitTest.java +++ b/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/SimpleDocumentDbRepositoryUnitTest.java @@ -12,8 +12,11 @@ import com.microsoft.azure.spring.data.documentdb.domain.Person; import com.microsoft.azure.spring.data.documentdb.repository.support.DocumentDbEntityInformation; import com.microsoft.azure.spring.data.documentdb.repository.support.SimpleDocumentDbRepository; +import org.assertj.core.util.Lists; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @@ -32,20 +35,24 @@ public class SimpleDocumentDbRepositoryUnitTest { new Person("test_person", "firstname", "lastname", Constants.HOBBIES, Constants.ADDRESSES); + private static final String PARTITION_VALUE_REQUIRED_MSG = + "PartitionKey value must be supplied for this operation."; + SimpleDocumentDbRepository repository; @Mock DocumentDbOperations dbOperations; @Mock DocumentDbEntityInformation entityInformation; + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Before public void setUp() { when(entityInformation.getJavaType()).thenReturn(Person.class); when(entityInformation.getCollectionName()).thenReturn(Person.class.getSimpleName()); - when(entityInformation.getPartitionKeyFieldName()).thenReturn("lastName"); when(entityInformation.getRequestUint()).thenReturn(1000); - when(dbOperations.findAll(anyString(), any(), anyString(), anyString())) - .thenReturn(Arrays.asList(TEST_PERSON)); + when(dbOperations.findAll(anyString(), any())).thenReturn(Arrays.asList(TEST_PERSON)); repository = new SimpleDocumentDbRepository(entityInformation, dbOperations); } @@ -54,21 +61,34 @@ public void setUp() { public void testSave() { repository.save(TEST_PERSON); - final List result = repository.findAll(TEST_PERSON.getLastName()); + final List result = Lists.newArrayList(repository.findAll()); assertEquals(1, result.size()); assertEquals(TEST_PERSON, result.get(0)); } @Test public void testFindOne() { - when(dbOperations.findById(anyString(), any(), any(), anyString())).thenReturn(TEST_PERSON); + when(dbOperations.findById(anyString(), any(), any())).thenReturn(TEST_PERSON); repository.save(TEST_PERSON); - final Person result = repository.findOne(TEST_PERSON.getId(), TEST_PERSON.getLastName()); + final Person result = repository.findOne(TEST_PERSON.getId()); assertEquals(TEST_PERSON, result); } + @Test + public void testFindOneExceptionForPartitioned() { + expectedException.expect(UnsupportedOperationException.class); + expectedException.expectMessage(PARTITION_VALUE_REQUIRED_MSG); + + repository.save(TEST_PERSON); + + when(dbOperations.findById(anyString(), any(), any())) + .thenThrow(new UnsupportedOperationException(PARTITION_VALUE_REQUIRED_MSG)); + + final Person result = repository.findOne(TEST_PERSON.getId()); + } + @Test public void testUpdate() { final List
updatedAddress = @@ -78,9 +98,9 @@ public void testUpdate() { Arrays.asList("updated hobbies"), updatedAddress); repository.save(updatedPerson); - when(dbOperations.findById(anyString(), any(), any(), anyString())).thenReturn(updatedPerson); + when(dbOperations.findById(anyString(), any(), any())).thenReturn(updatedPerson); - final Person result = repository.findOne(TEST_PERSON.getId(), TEST_PERSON.getLastName()); + final Person result = repository.findOne(TEST_PERSON.getId()); assertEquals(updatedPerson, result); } } diff --git a/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/support/DocumentDbRepositoryFactoryBeanUnitTest.java b/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/support/DocumentDbRepositoryFactoryBeanUnitTest.java index 8ddf7863..ec593f8a 100644 --- a/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/support/DocumentDbRepositoryFactoryBeanUnitTest.java +++ b/src/test/java/com/microsoft/azure/spring/data/documentdb/repository/support/DocumentDbRepositoryFactoryBeanUnitTest.java @@ -6,7 +6,7 @@ package com.microsoft.azure.spring.data.documentdb.repository.support; import com.microsoft.azure.spring.data.documentdb.core.DocumentDbTemplate; -import com.microsoft.azure.spring.data.documentdb.domain.PersonRepository; +import com.microsoft.azure.spring.data.documentdb.repository.PersonRepository; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock;