diff --git a/src/main/java/org/springframework/data/aerospike/convert/AerospikeWriteData.java b/src/main/java/org/springframework/data/aerospike/convert/AerospikeWriteData.java index 58dd8ffbb..df3a7fbb1 100644 --- a/src/main/java/org/springframework/data/aerospike/convert/AerospikeWriteData.java +++ b/src/main/java/org/springframework/data/aerospike/convert/AerospikeWriteData.java @@ -41,6 +41,8 @@ public class AerospikeWriteData { private Key key; @Getter private final String namespace; + @Getter + private String setName; private Collection bins; @Getter private int expiration; diff --git a/src/main/java/org/springframework/data/aerospike/convert/MappingAerospikeWriteConverter.java b/src/main/java/org/springframework/data/aerospike/convert/MappingAerospikeWriteConverter.java index f13fc1d53..47da8e5c0 100644 --- a/src/main/java/org/springframework/data/aerospike/convert/MappingAerospikeWriteConverter.java +++ b/src/main/java/org/springframework/data/aerospike/convert/MappingAerospikeWriteConverter.java @@ -116,7 +116,13 @@ public Optional getNewKey(AerospikeWriteData data, if (idProperty != null) { String id = accessor.getProperty(idProperty, String.class); // currently id can only be a String Assert.notNull(id, "Id must not be null!"); - return Optional.of(new Key(data.getNamespace(), entity.getSetName(), id)); + String setName; + if (data.getSetName() != null) { + setName = data.getSetName(); + } else { + setName = entity.getSetName(); + } + return Optional.of(new Key(data.getNamespace(), setName, id)); } else { // id is mandatory throw new AerospikeException(OP_NOT_APPLICABLE, "Id has not been provided"); diff --git a/src/main/java/org/springframework/data/aerospike/core/AerospikeInternalOperations.java b/src/main/java/org/springframework/data/aerospike/core/AerospikeInternalOperations.java index 1d14e9d75..c4f0f41ff 100644 --- a/src/main/java/org/springframework/data/aerospike/core/AerospikeInternalOperations.java +++ b/src/main/java/org/springframework/data/aerospike/core/AerospikeInternalOperations.java @@ -21,6 +21,22 @@ public interface AerospikeInternalOperations { */ Object findByIdInternal(Object id, Class entityClass, Class targetClass, Qualifier... qualifiers); + /** + * Find document by providing id with a given set name. + *

+ * Documents will be mapped to the given targetClass. + * + * @param id The id of the document to find. Must not be {@literal null}. + * @param entityClass The class to get the entity properties from (such as expiration). Must not be + * {@literal null}. + * @param targetClass The class to map the document to. + * @param setName Set name to find the document from. + * @param qualifiers {@link Qualifier}s provided to build a filter Expression for the query. Optional argument. + * @return The document from Aerospike, returned document will be mapped to targetClass's type. + */ + Object findByIdInternal(Object id, Class entityClass, Class targetClass, String setName, + Qualifier... qualifiers); + /** * Find documents by providing multiple ids, set name will be determined by the given entityClass. *

@@ -36,6 +52,23 @@ public interface AerospikeInternalOperations { List findByIdsInternal(Collection ids, Class entityClass, Class targetClass, Qualifier... qualifiers); + /** + * Find documents by providing multiple ids with a given set name. + *

+ * Documents will be mapped to the given targetClass. + * + * @param ids The ids of the documents to find. Must not be {@literal null}. + * @param entityClass The class to get the entity properties from (such as expiration). Must not be + * {@literal null}. + * @param targetClass The class to map the document to. + * @param setName Set name to find the document from. + * @param qualifiers {@link Qualifier}s provided to build a filter Expression for the query. Optional argument. + * @return The documents from Aerospike, returned documents will be mapped to targetClass's type, if no document + * exists, an empty list is returned. + */ + List findByIdsInternal(Collection ids, Class entityClass, Class targetClass, + String setName, Qualifier... qualifiers); + /** * Batch delete documents by providing their ids. Set name will be determined by the given entityClass. *

@@ -46,4 +79,15 @@ List findByIdsInternal(Collection ids, Class entityClass, Class< * @throws AerospikeException.BatchRecordArray if batch delete results contain errors or null records */ void deleteByIdsInternal(Collection ids, Class entityClass); + + /** + * Batch delete documents by providing their ids with a given set name. + *

+ * This operation requires Server version 6.0+. + * + * @param ids The ids of the documents to delete. Must not be {@literal null}. + * @param setName Set name to delete the documents from. + * @throws AerospikeException.BatchRecordArray if batch delete results contain errors or null records + */ + void deleteByIdsInternal(Collection ids, String setName); } diff --git a/src/main/java/org/springframework/data/aerospike/core/AerospikeOperations.java b/src/main/java/org/springframework/data/aerospike/core/AerospikeOperations.java index ed49d3eb0..694fa2fd6 100644 --- a/src/main/java/org/springframework/data/aerospike/core/AerospikeOperations.java +++ b/src/main/java/org/springframework/data/aerospike/core/AerospikeOperations.java @@ -57,6 +57,14 @@ public interface AerospikeOperations { */ String getSetName(Class entityClass); + /** + * Returns the set name used for the given document in the namespace configured for the AerospikeTemplate in use. + * + * @param document The document to get the set name for. + * @return The set name used for the given document. + */ + String getSetName(T document); + /** * @return mapping context in use. */ @@ -76,6 +84,17 @@ public interface AerospikeOperations { */ void insert(T document); + /** + * Insert a document with a given set name (overrides the set associated with the document) using + * {@link com.aerospike.client.policy.RecordExistsAction#CREATE_ONLY} policy. + *

+ * If document has version property it will be updated with the server's version after successful operation. + * + * @param document The document to insert. Must not be {@literal null}. + * @param setName Set name to override the set associated with the document. + */ + void insert(T document, String setName); + /** * Insert multiple documents in one batch request. The policies are analogous to {@link #insert(Object)}. *

@@ -91,6 +110,23 @@ public interface AerospikeOperations { */ void insertAll(Iterable documents); + /** + * Insert multiple documents with a given set name (overrides the set associated with the document) in one batch + * request. The policies are analogous to {@link #insert(Object)}. + *

+ * The order of returned results is preserved. The execution order is NOT preserved. + *

+ * This operation requires Server version 6.0+. + * + * @param documents Documents to insert. Must not be {@literal null}. + * @param setName Set name to override the set associated with the document. + * @throws AerospikeException.BatchRecordArray if batch insert succeeds, but results contain errors or null + * records + * @throws org.springframework.dao.DataAccessException if batch operation failed (see + * {@link DefaultAerospikeExceptionTranslator} for details) + */ + void insertAll(Iterable documents, String setName); + /** * Save a document. *

@@ -111,6 +147,27 @@ public interface AerospikeOperations { */ void save(T document); + /** + * Save a document with a given set name (overrides the set associated with the document) + *

+ * If the document has version property - CAS algorithm is used for updating record. Version property is used for + * deciding whether to create a new record or update an existing one. If the version is set to zero - new record + * will be created, creation will fail is such record already exists. If version is greater than zero - existing + * record will be updated with {@link com.aerospike.client.policy.RecordExistsAction#UPDATE_ONLY} policy combined + * with removing bins at first (analogous to {@link com.aerospike.client.policy.RecordExistsAction#REPLACE_ONLY}) + * taking into consideration the version property of the document. Version property will be updated with the + * server's version after successful operation. + *

+ * If the document does not have version property - record is updated with + * {@link com.aerospike.client.policy.RecordExistsAction#UPDATE} policy combined with removing bins at first + * (analogous to {@link com.aerospike.client.policy.RecordExistsAction#REPLACE}). This means that when such record + * does not exist it will be created, otherwise updated - an "upsert". + * + * @param document The document to save. Must not be {@literal null}. + * @param setName Set name to override the set associated with the document. + */ + void save(T document, String setName); + /** * Save multiple documents in one batch request. The policies are analogous to {@link #save(Object)}. *

@@ -126,6 +183,23 @@ public interface AerospikeOperations { */ void saveAll(Iterable documents); + /** + * Save multiple documents in one batch request with a given set (overrides the set associated with the documents). + * The policies are analogous to {@link #save(Object)}. + *

+ * The order of returned results is preserved. The execution order is NOT preserved. + *

+ * This operation requires Server version 6.0+. + * + * @param documents Documents to save. Must not be {@literal null}. + * @param setName Set name to override the set associated with the documents. + * @throws AerospikeException.BatchRecordArray if batch save succeeds, but results contain errors or null + * records + * @throws org.springframework.dao.DataAccessException if batch operation failed (see + * {@link DefaultAerospikeExceptionTranslator} for details) + */ + void saveAll(Iterable documents, String setName); + /** * Persist a document using specified WritePolicy. * @@ -135,6 +209,16 @@ public interface AerospikeOperations { */ void persist(T document, WritePolicy writePolicy); + /** + * Persist a document using specified WritePolicy and a given set (overrides the set associated with the document). + * + * @param document The document to persist. Must not be {@literal null}. + * @param writePolicy The Aerospike write policy for the inner Aerospike put operation. Must not be + * {@literal null}. + * @param setName Set name to override the set associated with the document. + */ + void persist(T document, WritePolicy writePolicy, String setName); + /** * Update a document using {@link com.aerospike.client.policy.RecordExistsAction#UPDATE_ONLY} policy combined with * removing bins at first (analogous to {@link com.aerospike.client.policy.RecordExistsAction#REPLACE_ONLY}) taking @@ -146,6 +230,19 @@ public interface AerospikeOperations { */ void update(T document); + /** + * Update a document with a given set (overrides the set associated with the document) using + * {@link com.aerospike.client.policy.RecordExistsAction#UPDATE_ONLY} policy combined with removing bins at first + * (analogous to {@link com.aerospike.client.policy.RecordExistsAction#REPLACE_ONLY}) taking into consideration the + * version property of the document if it is present. + *

+ * If document has version property it will be updated with the server's version after successful operation. + * + * @param document The document to update. Must not be {@literal null}. + * @param setName Set name to override the set associated with the document. + */ + void update(T document, String setName); + /** * Update document's specific fields based on a given collection of fields using * {@link com.aerospike.client.policy.RecordExistsAction#UPDATE_ONLY} policy. You can instantiate the document with @@ -155,9 +252,24 @@ public interface AerospikeOperations { * If document has version property it will be updated with the server's version after successful operation. * * @param document The document to update. Must not be {@literal null}. + * @param fields Specific fields to update. */ void update(T document, Collection fields); + /** + * Update document's specific fields based on a given collection of fields with a given set (overrides the set + * associated with the document). using {@link com.aerospike.client.policy.RecordExistsAction#UPDATE_ONLY} policy. + * You can instantiate the document with only the relevant fields and specify the list of fields that you want to + * update. Taking into consideration the version property of the document if it is present. + *

+ * If document has version property it will be updated with the server's version after successful operation. + * + * @param document The document to update. Must not be {@literal null}. + * @param setName Set name to override the set associated with the document. + * @param fields Specific fields to update. + */ + void update(T document, String setName, Collection fields); + /** * Update multiple documents in one batch request. The policies are analogous to {@link #update(Object)}. *

@@ -173,10 +285,28 @@ public interface AerospikeOperations { */ void updateAll(Iterable documents); + /** + * Update multiple documents in one batch request with a given set (overrides the set associated with the + * documents). The policies are analogous to {@link #update(Object)}. + *

+ * The order of returned results is preserved. The execution order is NOT preserved. + *

+ * This operation requires Server version 6.0+. + * + * @param documents Documents to update. Must not be {@literal null}. + * @param setName Set name to override the set associated with the document. + * @throws AerospikeException.BatchRecordArray if batch update succeeds, but results contain errors or null + * records + * @throws org.springframework.dao.DataAccessException if batch operation failed (see + * {@link DefaultAerospikeExceptionTranslator} for details) + */ + void updateAll(Iterable documents, String setName); + /** * Truncate/Delete all the documents in the given entity's set. * * @param entityClass The class to extract the Aerospike set from. Must not be {@literal null}. + * @deprecated since 4.6.0, use deleteAll(Class entityClass) instead. */ void delete(Class entityClass); @@ -186,9 +316,42 @@ public interface AerospikeOperations { * @param id The id of the document to delete. Must not be {@literal null}. * @param entityClass The class to extract the Aerospike set from. Must not be {@literal null}. * @return whether the document existed on server before deletion. + * @deprecated since 4.6.0, use deleteById(Object id, Class entityClass) instead. */ boolean delete(Object id, Class entityClass); + /** + * Truncate/Delete all the documents in the given entity's set. + * + * @param entityClass The class to extract the Aerospike set from. Must not be {@literal null}. + */ + void deleteAll(Class entityClass); + + /** + * Truncate/Delete all the documents in the given set. + * + * @param setName Set name to truncate/delete all the documents in. + */ + void deleteAll(String setName); + + /** + * Delete a document by id, set name will be determined by the given entityClass. + * + * @param id The id of the document to delete. Must not be {@literal null}. + * @param entityClass The class to extract the Aerospike set from. Must not be {@literal null}. + * @return whether the document existed on server before deletion. + */ + boolean deleteById(Object id, Class entityClass); + + /** + * Delete a document by id with a given set name. + * + * @param id The id of the document to delete. Must not be {@literal null}. + * @param setName Set name to delete the document from. + * @return whether the document existed on server before deletion. + */ + boolean deleteById(Object id, String setName); + /** * Delete a document. * @@ -197,6 +360,15 @@ public interface AerospikeOperations { */ boolean delete(T document); + /** + * Delete a document with a given set name. + * + * @param document The document to delete. Must not be {@literal null}. + * @param setName Set name to delete the document from. + * @return whether the document existed on server before deletion. + */ + boolean delete(T document, String setName); + /** * Delete documents by providing multiple ids using a single batch delete operation, set name will be determined by * the given entityClass. @@ -212,6 +384,19 @@ public interface AerospikeOperations { */ void deleteByIds(Iterable ids, Class entityClass); + /** + * Delete documents by providing multiple ids using a single batch delete operation with a given set. + *

+ * This operation requires Server version 6.0+. + * + * @param ids The ids of the documents to delete. Must not be {@literal null}. + * @param setName Set name to delete the documents from. + * @throws AerospikeException.BatchRecordArray if batch delete results contain errors + * @throws org.springframework.dao.DataAccessException if batch operation failed (see + * {@link DefaultAerospikeExceptionTranslator} for details) + */ + void deleteByIds(Iterable ids, String setName); + /** * Executes a single batch delete for several entities. *

@@ -238,6 +423,15 @@ public interface AerospikeOperations { */ boolean exists(Object id, Class entityClass); + /** + * Check if a document exists by providing document id and a set name. + * + * @param id The id to check if exists. Must not be {@literal null}. + * @param setName Set name to check if document exists. + * @return whether the document exists. + */ + boolean exists(Object id, String setName); + /** * Find documents in the given entityClass's set using a query and map them to the given class type. * @@ -258,6 +452,16 @@ public interface AerospikeOperations { */ Stream find(Query query, Class entityClass, Class targetClass); + /** + * Find documents in the given set using a query and map them to the given target class type. + * + * @param query The query to filter results. Must not be {@literal null}. + * @param setName Set name to find the documents in. + * @param targetClass The class to map the document to. Must not be {@literal null}. + * @return A Stream of matching documents, returned documents will be mapped to targetClass's type. + */ + Stream find(Query query, Class targetClass, String setName); + /** * Find all documents in the given entityClass's set and map them to the given class type. * @@ -276,6 +480,15 @@ public interface AerospikeOperations { */ Stream findAll(Class entityClass, Class targetClass); + /** + * Find all documents in the given set and map them to the given class type. + * + * @param targetClass The class to map the documents to. Must not be {@literal null}. + * @param setName Set name to find all documents. + * @return A Stream of matching documents, returned documents will be mapped to entityClass's type. + */ + Stream findAll(Class targetClass, String setName); + /** * Find a document by id, set name will be determined by the given entityClass. *

@@ -289,6 +502,20 @@ public interface AerospikeOperations { */ T findById(Object id, Class entityClass); + /** + * Find a document by id with a given set name. + *

+ * Document will be mapped to the given entityClass. + * + * @param id The id of the document to find. Must not be {@literal null}. + * @param entityClass The class to map the document to and to get entity properties from (such expiration). Must not + * be {@literal null}. + * @param setName Set name to find the document from. + * @return The document from Aerospike, returned document will be mapped to entityClass's type, if document doesn't + * exist return null. + */ + T findById(Object id, Class entityClass, String setName); + /** * Find a document by id, set name will be determined by the given entityClass. *

@@ -302,6 +529,20 @@ public interface AerospikeOperations { */ S findById(Object id, Class entityClass, Class targetClass); + /** + * Find a document by id with a given set name. + *

+ * Document will be mapped to the given entityClass. + * + * @param id The id of the document to find. Must not be {@literal null}. + * @param entityClass The class to get entity properties from (such as expiration). Must not be {@literal null}. + * @param targetClass The class to map the document to. Must not be {@literal null}. + * @param setName Set name to find the document from. + * @return The document from Aerospike, returned document will be mapped to targetClass's type, if document doesn't + * exist return null. + */ + S findById(Object id, Class entityClass, Class targetClass, String setName); + /** * Find documents by providing multiple ids using a single batch read operation, set name will be determined by the * given entityClass. @@ -316,6 +557,19 @@ public interface AerospikeOperations { */ List findByIds(Iterable ids, Class entityClass); + /** + * Find documents with a given set name by providing multiple ids using a single batch read operation. + *

+ * Documents will be mapped to the given entityClass. + * + * @param ids The ids of the documents to find. Must not be {@literal null}. + * @param entityClass The class to map the documents to. Must not be {@literal null}. + * @param setName Set name to find the document from. + * @return The documents from Aerospike, returned documents will be mapped to entityClass's type, if no document + * exists, an empty list is returned. + */ + List findByIds(Iterable ids, Class entityClass, String setName); + /** * Find documents by providing multiple ids using a single batch read operation, set name will be determined by the * given entityClass. @@ -330,6 +584,20 @@ public interface AerospikeOperations { */ List findByIds(Iterable ids, Class entityClass, Class targetClass); + /** + * Find documents with a given set name by providing multiple ids using a single batch read operation. + *

+ * Documents will be mapped to the given targetClass. + * + * @param ids The ids of the documents to find. Must not be {@literal null}. + * @param entityClass The class to get entity properties from (such as expiration). Must not be {@literal null}. + * @param targetClass The class to map the document to. Must not be {@literal null}. + * @param setName Set name to find the document from. + * @return The documents from Aerospike, returned documents will be mapped to targetClass's type, if no document + * exists, an empty list is returned. + */ + List findByIds(Iterable ids, Class entityClass, Class targetClass, String setName); + /** * Executes a single batch request to get results for several entities. *

@@ -353,6 +621,18 @@ public interface AerospikeOperations { */ T add(T document, Map values); + /** + * Add integer/double bin values to existing document bin values with a given set name, read the new modified + * document and map it back the given document class type. + * + * @param document The document to extract the Aerospike set from and to map the documents to. Must not be + * {@literal null}. + * @param setName Set name for the operation. + * @param values a Map of bin names and values to add. Must not be {@literal null}. + * @return Modified document after add operations. + */ + T add(T document, String setName, Map values); + /** * Add integer/double bin value to existing document bin value, read the new modified document and map it back the * given document class type. @@ -365,6 +645,19 @@ public interface AerospikeOperations { */ T add(T document, String binName, long value); + /** + * Add integer/double bin value to existing document bin value with a given set name, read the new modified document + * and map it back the given document class type. + * + * @param document The document to extract the Aerospike set from and to map the documents to. Must not be + * {@literal null}. + * @param setName Set name for the operation. + * @param binName Bin name to use add operation on. Must not be {@literal null}. + * @param value The value to add. + * @return Modified document after add operation. + */ + T add(T document, String setName, String binName, long value); + /** * Append bin string values to existing document bin values, read the new modified document and map it back the * given document class type. @@ -376,6 +669,17 @@ public interface AerospikeOperations { */ T append(T document, Map values); + /** + * Append bin string values to existing document bin values with a given set name, read the new modified document + * and map it back the given document class type. + * + * @param document The document to map the document to. Must not be {@literal null}. + * @param setName Set name for the operation. + * @param values a Map of bin names and values to append. Must not be {@literal null}. + * @return Modified document after append operations. + */ + T append(T document, String setName, Map values); + /** * Append bin string value to existing document bin value, read the new modified document and map it back the given * document class type. @@ -388,6 +692,18 @@ public interface AerospikeOperations { */ T append(T document, String binName, String value); + /** + * Append bin string value to existing document bin value with a given set name, read the new modified document and + * map it back the given document class type. + * + * @param document The document to map the document to. Must not be {@literal null}. + * @param setName Set name for the operation. + * @param binName Bin name to use append operation on. + * @param value The value to append. + * @return Modified document after append operation. + */ + T append(T document, String setName, String binName, String value); + /** * Prepend bin string values to existing document bin values, read the new modified document and map it back the * given document class type. @@ -399,6 +715,17 @@ public interface AerospikeOperations { */ T prepend(T document, Map values); + /** + * Prepend bin string values to existing document bin values with a given set name, read the new modified document + * and map it back the given document class type. + * + * @param document The document to map the document to. Must not be {@literal null}. + * @param setName Set name for the operation. + * @param values a Map of bin names and values to prepend. Must not be {@literal null}. + * @return Modified document after prepend operations. + */ + T prepend(T document, String setName, Map values); + /** * Prepend bin string value to existing document bin value, read the new modified document and map it back the given * document class type. @@ -411,6 +738,18 @@ public interface AerospikeOperations { */ T prepend(T document, String binName, String value); + /** + * Prepend bin string value to existing document bin value with a given set name, read the new modified document and + * map it back the given document class type. + * + * @param document The document to map the document to. Must not be {@literal null}. + * @param setName Set name for the operation. + * @param binName Bin name to use prepend operation on. + * @param value The value to prepend. + * @return Modified document after prepend operation. + */ + T prepend(T document, String setName, String binName, String value); + /** * Execute query, apply statement's aggregation function, and return result iterator. * @@ -423,6 +762,18 @@ public interface AerospikeOperations { */ ResultSet aggregate(Filter filter, Class entityClass, String module, String function, List arguments); + /** + * Execute query with a given set name, apply statement's aggregation function, and return result iterator. + * + * @param filter The filter to pass to the query. + * @param setName Set name to aggregate the documents from. + * @param module server package where user defined function resides. + * @param function aggregation function name. + * @param arguments arguments to pass to function name, if any. + * @return Result iterator. + */ + ResultSet aggregate(Filter filter, String setName, String module, String function, List arguments); + /** * Execute operation against underlying store. * @@ -455,6 +806,18 @@ public interface AerospikeOperations { */ Stream findAll(Sort sort, long offset, long limit, Class entityClass, Class targetClass); + /** + * Find all documents in the given set using a provided sort and map them to the given target class type. + * + * @param sort The sort to affect the returned iterable documents order. + * @param offset The offset to start the range from. + * @param limit The limit of the range. + * @param targetClass The class to map the document to. Must not be {@literal null}. + * @param setName Set name to find the documents. + * @return A stream of matching documents, returned documents will be mapped to targetClass's type. + */ + Stream findAll(Sort sort, long offset, long limit, Class targetClass, String setName); + /** * Find documents in the given entityClass's set using a range (offset, limit) and a sort and map them to the given * class type. @@ -481,6 +844,19 @@ public interface AerospikeOperations { */ Stream findInRange(long offset, long limit, Sort sort, Class entityClass, Class targetClass); + /** + * Find documents in the given set using a range (offset, limit) and a sort and map them to the given target class + * type. + * + * @param offset The offset to start the range from. + * @param limit The limit of the range. + * @param sort The sort to affect the returned Stream of documents order. + * @param targetClass The class to map the document to. Must not be {@literal null}. + * @param setName Set name to find the documents. + * @return A Stream of matching documents, returned documents will be mapped to targetClass's type. + */ + Stream findInRange(long offset, long limit, Sort sort, Class targetClass, String setName); + /** * Return the amount of documents in query results. Set name will be determined by the given entityClass. * @@ -490,6 +866,15 @@ public interface AerospikeOperations { */ long count(Query query, Class entityClass); + /** + * Return the amount of documents in query results with a given set name. + * + * @param query The query that provides the result set for count. + * @param setName Set name to count the documents from. + * @return amount of documents that the given query and entity class supplied. + */ + long count(Query query, String setName); + /** * Return the amount of documents in the given Aerospike set. * @@ -542,6 +927,42 @@ void createIndex(Class entityClass, String indexName, String binName, void createIndex(Class entityClass, String indexName, String binName, IndexType indexType, IndexCollectionType indexCollectionType, CTX... ctx); + /** + * Create an index with the specified name in Aerospike. + * + * @param setName Set name to create the index. + * @param indexName The index name. Must not be {@literal null}. + * @param binName The bin name to create the index on. Must not be {@literal null}. + * @param indexType The type of the index. Must not be {@literal null}. + */ + void createIndex(String setName, String indexName, String binName, + IndexType indexType); + + /** + * Create an index with the specified name in Aerospike. + * + * @param setName Set name to create the index. + * @param indexName The index name. Must not be {@literal null}. + * @param binName The bin name to create the index on. Must not be {@literal null}. + * @param indexType The type of the index. Must not be {@literal null}. + * @param indexCollectionType The collection type of the index. Must not be {@literal null}. + */ + void createIndex(String setName, String indexName, String binName, + IndexType indexType, IndexCollectionType indexCollectionType); + + /** + * Create an index with the specified name in Aerospike. + * + * @param setName Set name to create the index. + * @param indexName The index name. Must not be {@literal null}. + * @param binName The bin name to create the index on. Must not be {@literal null}. + * @param indexType The type of the index. Must not be {@literal null}. + * @param indexCollectionType The collection type of the index. Must not be {@literal null}. + * @param ctx optional context to index on elements within a CDT. + */ + void createIndex(String setName, String indexName, String binName, + IndexType indexType, IndexCollectionType indexCollectionType, CTX... ctx); + /** * Delete an index with the specified name from Aerospike. * @@ -550,6 +971,14 @@ void createIndex(Class entityClass, String indexName, String binName, */ void deleteIndex(Class entityClass, String indexName); + /** + * Delete an index with the specified name from Aerospike with a given set name. + * + * @param setName Set name to delete the index from. + * @param indexName The index name. Must not be {@literal null}. + */ + void deleteIndex(String setName, String indexName); + /** * Checks whether an index with the specified name exists in Aerospike. * @@ -559,9 +988,10 @@ void createIndex(Class entityClass, String indexName, String binName, boolean indexExists(String indexName); /** - * Find all documents in the given entityClass's set using provided {@link Qualifier}s. + * Find all documents in the given entityClass's set using provided {@link Qualifier}. * - * @param entityClass The class to extract the Aerospike set from. Must not be {@literal null}. + * @param entityClass The class to extract the Aerospike set from and to map the entity to. Must not be + * {@literal null}. * @param filter Secondary index filter. * @param qualifier Qualifier to build filter expressions from. Can contain other qualifiers. Must not be * {@literal null}. If filter param is null and qualifier has @@ -570,4 +1000,19 @@ void createIndex(Class entityClass, String indexName, String binName, * @return Stream of entities. */ Stream findAllUsingQuery(Class entityClass, @Nullable Filter filter, Qualifier qualifier); + + /** + * Find all documents in the given set using provided {@link Qualifier}. + * + * @param targetClass The class to map the entity to. Must not be {@literal null}. + * @param setName Set name to find the documents in. + * @param filter Secondary index filter. + * @param qualifier Qualifier to build filter expressions from. Can contain other qualifiers. Must not be + * {@literal null}. If filter param is null and qualifier has + * {@link Qualifier#getExcludeFilter()} == false, secondary index filter is built based on the + * first processed qualifier. + * @return Stream of entities. + */ + Stream findAllUsingQuery(Class targetClass, String setName, @Nullable Filter filter, + Qualifier qualifier); } diff --git a/src/main/java/org/springframework/data/aerospike/core/AerospikeTemplate.java b/src/main/java/org/springframework/data/aerospike/core/AerospikeTemplate.java index 9b9661a37..d192bb674 100644 --- a/src/main/java/org/springframework/data/aerospike/core/AerospikeTemplate.java +++ b/src/main/java/org/springframework/data/aerospike/core/AerospikeTemplate.java @@ -114,12 +114,14 @@ public IAerospikeClient getAerospikeClient() { @Override public void createIndex(Class entityClass, String indexName, String binName, IndexType indexType) { + Assert.notNull(entityClass, "Class must not be null!"); createIndex(entityClass, indexName, binName, indexType, IndexCollectionType.DEFAULT); } @Override public void createIndex(Class entityClass, String indexName, String binName, IndexType indexType, IndexCollectionType indexCollectionType) { + Assert.notNull(entityClass, "Class must not be null!"); createIndex(entityClass, indexName, binName, indexType, indexCollectionType, new CTX[0]); } @@ -128,6 +130,25 @@ public void createIndex(Class entityClass, String indexName, String binName, IndexType indexType, IndexCollectionType indexCollectionType, CTX... ctx) { Assert.notNull(entityClass, "Class must not be null!"); + createIndex(getSetName(entityClass), indexName, binName, indexType, indexCollectionType, ctx); + } + + @Override + public void createIndex(String setName, String indexName, + String binName, IndexType indexType) { + createIndex(setName, indexName, binName, indexType, IndexCollectionType.DEFAULT); + } + + @Override + public void createIndex(String setName, String indexName, String binName, IndexType indexType, + IndexCollectionType indexCollectionType) { + createIndex(setName, indexName, binName, indexType, indexCollectionType, new CTX[0]); + } + + @Override + public void createIndex(String setName, String indexName, String binName, + IndexType indexType, IndexCollectionType indexCollectionType, CTX... ctx) { + Assert.notNull(setName, "Set name type must not be null!"); Assert.notNull(indexName, "Index name must not be null!"); Assert.notNull(binName, "Bin name must not be null!"); Assert.notNull(indexType, "Index type must not be null!"); @@ -135,7 +156,6 @@ public void createIndex(Class entityClass, String indexName, Assert.notNull(ctx, "Ctx must not be null!"); try { - String setName = getSetName(entityClass); IndexTask task = client.createIndex(null, this.namespace, setName, indexName, binName, indexType, indexCollectionType, ctx); if (task != null) { @@ -155,10 +175,15 @@ public void refreshIndexesCache() { @Override public void deleteIndex(Class entityClass, String indexName) { Assert.notNull(entityClass, "Class must not be null!"); + deleteIndex(getSetName(entityClass), indexName); + } + + @Override + public void deleteIndex(String setName, String indexName) { + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(indexName, "Index name must not be null!"); try { - String setName = getSetName(entityClass); IndexTask task = client.dropIndex(null, this.namespace, setName, indexName); if (task != null) { task.waitTillComplete(); @@ -213,9 +238,15 @@ public boolean indexExists(String indexName) { @Override public void save(T document) { Assert.notNull(document, "Document must not be null!"); + save(document, getSetName(document)); + } - AerospikeWriteData data = writeData(document); + @Override + public void save(T document, String setName) { + Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + AerospikeWriteData data = writeData(document, setName); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); if (entity.hasVersionProperty()) { WritePolicy policy = expectGenerationCasAwareSavePolicy(data); @@ -235,9 +266,16 @@ public void save(T document) { @Override public void saveAll(Iterable documents) { Assert.notNull(documents, "Documents for saving must not be null!"); + saveAll(documents, getSetName(documents.iterator().next())); + } + + @Override + public void saveAll(Iterable documents, String setName) { + Assert.notNull(documents, "Documents for saving must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); List> batchWriteDataList = new ArrayList<>(); - documents.forEach(document -> batchWriteDataList.add(getBatchWriteForSave(document))); + documents.forEach(document -> batchWriteDataList.add(getBatchWriteForSave(document, setName))); List batchWriteRecords = batchWriteDataList.stream().map(BatchWriteData::batchRecord).toList(); try { @@ -271,8 +309,16 @@ private void checkForErrorsAndUpdateVersion(List> batchWri public void persist(T document, WritePolicy policy) { Assert.notNull(document, "Document must not be null!"); Assert.notNull(policy, "Policy must not be null!"); + persist(document, policy, getSetName(document)); + } + + @Override + public void persist(T document, WritePolicy policy, String setName) { + Assert.notNull(document, "Document must not be null!"); + Assert.notNull(policy, "Policy must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Operation[] operations = operations(data.getBinsAsArray(), Operation::put); doPersistAndHandleError(data, policy, operations); @@ -281,8 +327,15 @@ public void persist(T document, WritePolicy policy) { @Override public void insert(T document) { Assert.notNull(document, "Document must not be null!"); + insert(document, getSetName(document)); + } - AerospikeWriteData data = writeData(document); + @Override + public void insert(T document, String setName) { + Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + + AerospikeWriteData data = writeData(document, setName); WritePolicy policy = ignoreGenerationSavePolicy(data, RecordExistsAction.CREATE_ONLY); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); if (entity.hasVersionProperty()) { @@ -301,10 +354,17 @@ public void insert(T document) { @Override public void insertAll(Iterable documents) { + Assert.notNull(documents, "Documents must not be null!"); + insertAll(documents, getSetName(documents.iterator().next())); + } + + @Override + public void insertAll(Iterable documents, String setName) { Assert.notNull(documents, "Documents for inserting must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); List> batchWriteDataList = new ArrayList<>(); - documents.forEach(document -> batchWriteDataList.add(getBatchWriteForInsert(document))); + documents.forEach(document -> batchWriteDataList.add(getBatchWriteForInsert(document, setName))); List batchWriteRecords = batchWriteDataList.stream().map(BatchWriteData::batchRecord).toList(); try { @@ -319,8 +379,15 @@ public void insertAll(Iterable documents) { @Override public void update(T document) { Assert.notNull(document, "Document must not be null!"); + update(document, getSetName(document)); + } - AerospikeWriteData data = writeData(document); + @Override + public void update(T document, String setName) { + Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + + AerospikeWriteData data = writeData(document, setName); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); if (entity.hasVersionProperty()) { WritePolicy policy = expectGenerationSavePolicy(data, RecordExistsAction.UPDATE_ONLY); @@ -340,8 +407,15 @@ public void update(T document) { @Override public void update(T document, Collection fields) { Assert.notNull(document, "Document must not be null!"); + update(document, getSetName(document), fields); + } + + @Override + public void update(T document, String setName, Collection fields) { + Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - AerospikeWriteData data = writeDataWithSpecificFields(document, fields); + AerospikeWriteData data = writeDataWithSpecificFields(document, setName, fields); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); if (entity.hasVersionProperty()) { WritePolicy policy = expectGenerationSavePolicy(data, RecordExistsAction.UPDATE_ONLY); @@ -357,10 +431,17 @@ public void update(T document, Collection fields) { @Override public void updateAll(Iterable documents) { - Assert.notNull(documents, "Documents for inserting must not be null!"); + Assert.notNull(documents, "Documents must not be null!"); + updateAll(documents, getSetName(documents.iterator().next())); + } + + @Override + public void updateAll(Iterable documents, String setName) { + Assert.notNull(documents, "Documents must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); List> batchWriteDataList = new ArrayList<>(); - documents.forEach(document -> batchWriteDataList.add(getBatchWriteForUpdate(document))); + documents.forEach(document -> batchWriteDataList.add(getBatchWriteForUpdate(document, setName))); List batchWriteRecords = batchWriteDataList.stream().map(BatchWriteData::batchRecord).toList(); try { @@ -372,26 +453,58 @@ public void updateAll(Iterable documents) { checkForErrorsAndUpdateVersion(batchWriteDataList, batchWriteRecords, "update"); } + @Deprecated @Override public void delete(Class entityClass) { Assert.notNull(entityClass, "Class must not be null!"); + delete(getSetName(entityClass)); + } + + @Deprecated + @Override + public boolean delete(Object id, Class entityClass) { + Assert.notNull(entityClass, "Class must not be null!"); + Assert.notNull(id, "Id must not be null!"); + + try { + Key key = getKey(id, getSetName(entityClass)); + + return this.client.delete(ignoreGenerationDeletePolicy(), key); + } catch (AerospikeException e) { + throw translateError(e); + } + } + + @Override + public void deleteAll(Class entityClass) { + Assert.notNull(entityClass, "Class must not be null!"); + deleteAll(getSetName(entityClass)); + } + + @Override + public void deleteAll(String setName) { + Assert.notNull(setName, "Set name must not be null!"); try { - String set = getSetName(entityClass); - client.truncate(null, getNamespace(), set, null); + client.truncate(null, getNamespace(), setName, null); } catch (AerospikeException e) { throw translateError(e); } } @Override - public boolean delete(Object id, Class entityClass) { - Assert.notNull(id, "Id must not be null!"); + public boolean deleteById(Object id, Class entityClass) { Assert.notNull(entityClass, "Class must not be null!"); + return deleteById(id, getSetName(entityClass)); + } + + @Override + public boolean deleteById(Object id, String setName) { + Assert.notNull(id, "Id must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); try { - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - Key key = getKey(id, entity); + Key key = getKey(id, setName); return this.client.delete(ignoreGenerationDeletePolicy(), key); } catch (AerospikeException e) { @@ -402,9 +515,16 @@ public boolean delete(Object id, Class entityClass) { @Override public boolean delete(T document) { Assert.notNull(document, "Document must not be null!"); + return delete(document, getSetName(document)); + } + + @Override + public boolean delete(T document, String setName) { + Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); try { - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); return this.client.delete(ignoreGenerationDeletePolicy(), data.getKey()); } catch (AerospikeException e) { @@ -416,20 +536,35 @@ public boolean delete(T document) { public void deleteByIds(Iterable ids, Class entityClass) { Assert.notNull(ids, "List of ids must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); + deleteByIds(ids, getSetName(entityClass)); + } + + @Override + public void deleteByIds(Iterable ids, String setName) { + Assert.notNull(ids, "List of ids must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - deleteByIdsInternal(IterableConverter.toList(ids), entityClass); + deleteByIdsInternal(IterableConverter.toList(ids), setName); } @Override public void deleteByIdsInternal(Collection ids, Class entityClass) { + Assert.notNull(ids, "List of ids must not be null!"); + Assert.notNull(entityClass, "Class must not be null!"); + deleteByIdsInternal(ids, getSetName(entityClass)); + } + + @Override + public void deleteByIdsInternal(Collection ids, String setName) { + Assert.notNull(ids, "List of ids must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + if (ids.isEmpty()) { return; } - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - Key[] keys = ids.stream() - .map(id -> getKey(id, entity)) + .map(id -> getKey(id, setName)) .toArray(Key[]::new); checkForErrors(client, keys); @@ -469,12 +604,17 @@ private void deleteEntitiesByIdsInternal(GroupedKeys groupedKeys) { @Override public boolean exists(Object id, Class entityClass) { - Assert.notNull(id, "Id must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); + return exists(id, getSetName(entityClass)); + } + + @Override + public boolean exists(Object id, String setName) { + Assert.notNull(id, "Id must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); try { - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - Key key = getKey(id, entity); + Key key = getKey(id, setName); Record aeroRecord = this.client.operate(null, key, Operation.getHeader()); return aeroRecord != null; @@ -485,45 +625,74 @@ public boolean exists(Object id, Class entityClass) { @Override public Stream findAll(Class entityClass) { - Assert.notNull(entityClass, "Class must not be null!"); + Assert.notNull(entityClass, "Entity class must not be null!"); - return findAllUsingQuery(entityClass, null, null); + return findAll(entityClass, getSetName(entityClass)); } - @SuppressWarnings("unchecked") @Override public Stream findAll(Class entityClass, Class targetClass) { Assert.notNull(entityClass, "Entity class must not be null!"); Assert.notNull(targetClass, "Target class must not be null!"); - return (Stream) findAllUsingQuery(entityClass, targetClass, null); + return findAll(targetClass, getSetName(entityClass)); + } + + @Override + public Stream findAll(Class targetClass, String setName) { + Assert.notNull(targetClass, "Target class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + + return findAllUsingQuery(targetClass, setName, null, null); } - @SuppressWarnings("unchecked") @Override public T findById(Object id, Class entityClass) { Assert.notNull(id, "Id must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); + return findById(id, entityClass, getSetName(entityClass)); + } - return (T) findByIdInternal(id, entityClass, null); + @Override + public T findById(Object id, Class entityClass, String setName) { + Assert.notNull(id, "Id must not be null!"); + Assert.notNull(entityClass, "Class must not be null!"); + return findById(id, entityClass, null, setName); } - @SuppressWarnings("unchecked") @Override public S findById(Object id, Class entityClass, Class targetClass) { Assert.notNull(id, "Id must not be null!"); - Assert.notNull(entityClass, "Entity class must not be null!"); - Assert.notNull(targetClass, "Target class must not be null!"); + Assert.notNull(entityClass, "Class must not be null!"); + return findById(id, entityClass, targetClass, getSetName(entityClass)); + } - return (S) findByIdInternal(id, entityClass, targetClass); + @SuppressWarnings("unchecked") + @Override + public S findById(Object id, Class entityClass, Class targetClass, String setName) { + Assert.notNull(id, "Id must not be null!"); + Assert.notNull(entityClass, "Class must not be null!"); + return (S) findByIdInternal(id, entityClass, targetClass, setName); } @Override public Object findByIdInternal(Object id, Class entityClass, Class targetClass, Qualifier... qualifiers) { + Assert.notNull(id, "Id must not be null!"); + Assert.notNull(entityClass, "Class must not be null!"); + return findByIdInternal(id, entityClass, targetClass, getSetName(entityClass), qualifiers); + } + + @Override + public Object findByIdInternal(Object id, Class entityClass, Class targetClass, String setName, + Qualifier... qualifiers) { + Assert.notNull(id, "Id must not be null!"); + Assert.notNull(entityClass, "Entity class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + try { AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - Key key = getKey(id, entity); + Key key = getKey(id, setName); if (targetClass != null && targetClass != entityClass) { return getRecordMapToTargetClass(entity, key, targetClass, qualifiers); @@ -537,14 +706,25 @@ public Object findByIdInternal(Object id, Class entityClass, Class @Override public List findByIdsInternal(Collection ids, Class entityClass, Class targetClass, Qualifier... qualifiers) { + Assert.notNull(entityClass, "Class must not be null!"); + return findByIdsInternal(ids, entityClass, targetClass, getSetName(entityClass), qualifiers); + } + + @Override + public List findByIdsInternal(Collection ids, Class entityClass, Class targetClass, + String setName, Qualifier... qualifiers) { + Assert.notNull(ids, "List of ids must not be null!"); + Assert.notNull(entityClass, "Entity class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + if (ids.isEmpty()) { return Collections.emptyList(); } try { - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - - Key[] keys = getKeys(ids, entity); + Key[] keys = ids.stream() + .map(id -> getKey(id, setName)) + .toArray(Key[]::new); BatchPolicy policy = getBatchPolicyFilterExp(qualifiers); @@ -568,7 +748,7 @@ public List findByIdsInternal(Collection ids, Class entityClass, } } - private List findByIdsInternalWithoutMapping(Collection ids, Class entityClass, + private List findByIdsInternalWithoutMapping(Collection ids, String setName, Class targetClass, Qualifier... qualifiers) { Assert.notNull(ids, "Ids must not be null"); @@ -577,14 +757,12 @@ private List findByIdsInternalWithoutMapping(Collection ids, Class } try { - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - - Key[] keys = getKeys(ids, entity); + Key[] keys = getKeys(ids, setName); BatchPolicy policy = getBatchPolicyFilterExp(qualifiers); Record[] aeroRecords; - if (targetClass != null && targetClass != entityClass) { + if (targetClass != null) { String[] binNames = getBinNamesFromTargetClass(targetClass); aeroRecords = getAerospikeClient().get(policy, keys, binNames); } else { @@ -609,9 +787,9 @@ private BatchPolicy getBatchPolicyFilterExp(Qualifier[] qualifiers) { return null; } - private Key[] getKeys(Collection ids, AerospikePersistentEntity entity) { + private Key[] getKeys(Collection ids, String setName) { return ids.stream() - .map(id -> getKey(id, entity)) + .map(id -> getKey(id, setName)) .toArray(Key[]::new); } @@ -690,23 +868,29 @@ String[] getBinNamesFromTargetClass(Class targetClass) { return binNamesList.toArray(new String[0]); } - @SuppressWarnings("unchecked") @Override public List findByIds(Iterable ids, Class entityClass) { - Assert.notNull(ids, "List of ids must not be null!"); - Assert.notNull(entityClass, "Class must not be null!"); + return findByIds(ids, entityClass, getSetName(entityClass)); + } - return (List) findByIdsInternal(IterableConverter.toList(ids), entityClass, null); + @Override + public List findByIds(Iterable ids, Class entityClass, String setName) { + return findByIds(ids, entityClass, null, setName); } - @SuppressWarnings("unchecked") @Override public List findByIds(Iterable ids, Class entityClass, Class targetClass) { + return findByIds(ids, entityClass, targetClass, getSetName(entityClass)); + } + + @SuppressWarnings("unchecked") + @Override + public List findByIds(Iterable ids, Class entityClass, Class targetClass, String setName) { Assert.notNull(ids, "List of ids must not be null!"); Assert.notNull(entityClass, "Entity class must not be null!"); - Assert.notNull(targetClass, "Target class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - return (List) findByIdsInternal(IterableConverter.toList(ids), entityClass, targetClass); + return (List) findByIdsInternal(IterableConverter.toList(ids), entityClass, targetClass, setName); } @Override @@ -730,14 +914,18 @@ private GroupedEntities findEntitiesByIdsInternal(GroupedKeys groupedKeys) { @Override public ResultSet aggregate(Filter filter, Class entityClass, String module, String function, List arguments) { - Assert.notNull(entityClass, "Class must not be null!"); + return aggregate(filter, getSetName(entityClass), module, function, arguments); + } - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); + @Override + public ResultSet aggregate(Filter filter, String setName, + String module, String function, List arguments) { + Assert.notNull(setName, "Set name must not be null!"); Statement statement = new Statement(); if (filter != null) statement.setFilter(filter); - statement.setSetName(entity.getSetName()); + statement.setSetName(setName); statement.setNamespace(this.namespace); ResultSet resultSet; if (arguments != null && !arguments.isEmpty()) @@ -748,30 +936,35 @@ public ResultSet aggregate(Filter filter, Class entityClass, return resultSet; } - @SuppressWarnings("unchecked") @Override public Stream findAll(Sort sort, long offset, long limit, Class entityClass) { - Assert.notNull(entityClass, "Class must not be null!"); - - return (Stream) findAllUsingQueryWithPostProcessing(entityClass, null, sort, offset, limit, - null, (Qualifier[]) null); + return findAll(sort, offset, limit, entityClass, getSetName(entityClass)); } - @SuppressWarnings("unchecked") @Override public Stream findAll(Sort sort, long offset, long limit, Class entityClass, Class targetClass) { - Assert.notNull(entityClass, "Class must not be null!"); + return findAll(sort, offset, limit, targetClass, getSetName(entityClass)); + } + + @Override + public Stream findAll(Sort sort, long offset, long limit, Class targetClass, String setName) { + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(targetClass, "Target class must not be null!"); - return (Stream) findAllUsingQueryWithPostProcessing(entityClass, targetClass, sort, offset, limit, - null, (Qualifier[]) null); + return findAllUsingQueryWithPostProcessing(setName, targetClass, sort, offset, limit, + null, null); } public boolean exists(Query query, Class entityClass) { + return exists(query, entityClass, getSetName(entityClass)); + } + + public boolean exists(Query query, Class entityClass, String setName) { Assert.notNull(query, "Query passed in to exist can't be null"); Assert.notNull(entityClass, "Class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - return find(query, entityClass).findAny().isPresent(); + return find(query, entityClass, setName).findAny().isPresent(); } @Override @@ -788,62 +981,65 @@ public T execute(Supplier supplier) { @Override public long count(Query query, Class entityClass) { Assert.notNull(entityClass, "Class must not be null!"); + return count(query, getSetName(entityClass)); + } - Stream results = findAllRecordsUsingQuery(entityClass, query); + @Override + public long count(Query query, String setName) { + Stream results = findAllRecordsUsingQuery(setName, query); return results.count(); } - @SuppressWarnings("unchecked") @Override public Stream find(Query query, Class entityClass) { - Assert.notNull(query, "Query must not be null!"); - Assert.notNull(entityClass, "Class must not be null!"); - - return (Stream) findAllUsingQueryWithPostProcessing(entityClass, null, query); + return find(query, entityClass, getSetName(entityClass)); } - @SuppressWarnings("unchecked") @Override public Stream find(Query query, Class entityClass, Class targetClass) { + return find(query, targetClass, getSetName(entityClass)); + } + + @Override + public Stream find(Query query, Class targetClass, String setName) { Assert.notNull(query, "Query must not be null!"); - Assert.notNull(entityClass, "Entity class must not be null!"); - Assert.notNull(targetClass, "Target lass must not be null!"); + Assert.notNull(targetClass, "Target class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - return (Stream) findAllUsingQueryWithPostProcessing(entityClass, targetClass, query); + return findAllUsingQueryWithPostProcessing(setName, targetClass, query); } - @SuppressWarnings("unchecked") @Override public Stream findInRange(long offset, long limit, Sort sort, Class entityClass) { - Assert.notNull(entityClass, "Class for count must not be null!"); - - return (Stream) findAllUsingQueryWithPostProcessing(entityClass, null, sort, offset, limit, - null, (Qualifier[]) null); + return findInRange(offset, limit, sort, entityClass, getSetName(entityClass)); } - @SuppressWarnings("unchecked") @Override public Stream findInRange(long offset, long limit, Sort sort, Class entityClass, Class targetClass) { - Assert.notNull(entityClass, "Class for count must not be null!"); + return findInRange(offset, limit, sort, targetClass, getSetName(entityClass)); + } + + @Override + public Stream findInRange(long offset, long limit, Sort sort, + Class targetClass, String setName) { Assert.notNull(targetClass, "Target class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - return (Stream) findAllUsingQueryWithPostProcessing(entityClass, targetClass, sort, offset, limit, - null, (Qualifier[]) null); + return findAllUsingQueryWithPostProcessing(setName, targetClass, sort, offset, limit, + null, null); } @Override public long count(Class entityClass) { - Assert.notNull(entityClass, "Type for count must not be null!"); - - String setName = getSetName(entityClass); - return count(setName); + Assert.notNull(entityClass, "Class must not be null!"); + return count(getSetName(entityClass)); } @Override public long count(String setName) { - Assert.notNull(setName, "Set for count must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); try { Node[] nodes = client.getNodes(); @@ -862,10 +1058,17 @@ public long count(String setName) { @Override public T prepend(T document, String fieldName, String value) { + return prepend(document, getSetName(document), fieldName, value); + } + + @Override + public T prepend(T document, String setName, String fieldName, String value) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + Assert.notNull(fieldName, "Field name must not be null!"); try { - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Record aeroRecord = this.client.operate(null, data.getKey(), Operation.prepend(new Bin(fieldName, value)), Operation.get(fieldName)); @@ -878,11 +1081,17 @@ public T prepend(T document, String fieldName, String value) { @Override public T prepend(T document, Map values) { + return prepend(document, getSetName(document), values); + } + + @Override + public T prepend(T document, String setName, Map values) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(values, "Values must not be null!"); try { - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Operation[] ops = operations(values, Operation.Type.PREPEND, Operation.get()); Record aeroRecord = this.client.operate(null, data.getKey(), ops); @@ -894,11 +1103,17 @@ public T prepend(T document, Map values) { @Override public T append(T document, Map values) { + return append(document, getSetName(document), values); + } + + @Override + public T append(T document, String setName, Map values) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(values, "Values must not be null!"); try { - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Operation[] ops = operations(values, Operation.Type.APPEND, Operation.get()); Record aeroRecord = this.client.operate(null, data.getKey(), ops); @@ -910,10 +1125,17 @@ public T append(T document, Map values) { @Override public T append(T document, String binName, String value) { + return append(document, getSetName(document), binName, value); + } + + @Override + public T append(T document, String setName, String binName, String value) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + Assert.notNull(binName, "Bin name must not be null!"); try { - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Record aeroRecord = this.client.operate(null, data.getKey(), Operation.append(new Bin(binName, value)), Operation.get(binName)); @@ -926,11 +1148,17 @@ public T append(T document, String binName, String value) { @Override public T add(T document, Map values) { + return add(document, getSetName(document), values); + } + + @Override + public T add(T document, String setName, Map values) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(values, "Values must not be null!"); try { - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Operation[] ops = operations(values, Operation.Type.ADD, Operation.get()); WritePolicy writePolicy = WritePolicyBuilder.builder(client.getWritePolicyDefault()) @@ -947,11 +1175,17 @@ public T add(T document, Map values) { @Override public T add(T document, String binName, long value) { + return add(document, getSetName(document), binName, value); + } + + @Override + public T add(T document, String setName, String binName, long value) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(binName, "Bin name must not be null!"); try { - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); WritePolicy writePolicy = WritePolicyBuilder.builder(client.getWritePolicyDefault()) .expiration(data.getExpiration()) @@ -1000,49 +1234,48 @@ private Record putAndGetHeader(AerospikeWriteData data, WritePolicy policy, bool return client.operate(policy, key, operations); } - Stream findAllUsingQueryWithPostProcessing(Class entityClass, Class targetClass, Query query) { + private Stream findAllUsingQueryWithPostProcessing(String setName, Class targetClass, Query query) { verifyUnsortedWithOffset(query.getSort(), query.getOffset()); Qualifier qualifier = query.getCriteria().getCriteriaObject(); - Stream results = findAllUsingQueryWithDistinctPredicate(entityClass, targetClass, + Stream results = findAllUsingQueryWithDistinctPredicate(setName, targetClass, getDistinctPredicate(query), qualifier); return applyPostProcessingOnResults(results, query); } @SuppressWarnings("SameParameterValue") - Stream findAllUsingQueryWithPostProcessing(Class entityClass, Class targetClass, Sort sort, - long offset, long limit, Filter filter, - Qualifier... qualifiers) { + private Stream findAllUsingQueryWithPostProcessing(String setName, Class targetClass, Sort sort, + long offset, long limit, Filter filter, + Qualifier qualifier) { verifyUnsortedWithOffset(sort, offset); - Stream results = findAllUsingQuery(entityClass, targetClass, filter, qualifiers); + Stream results = findAllUsingQuery(targetClass, setName, filter, qualifier); return applyPostProcessingOnResults(results, sort, offset, limit); } - Object mapToEntity(KeyRecord keyRecord, Class entityClass, Class targetClass) { - if (targetClass != null) { - return mapToEntity(keyRecord.key, targetClass, keyRecord.record); - } - return mapToEntity(keyRecord.key, entityClass, keyRecord.record); - } - @Override public Stream findAllUsingQuery(Class entityClass, Filter filter, Qualifier qualifier) { - return findAllRecordsUsingQuery(entityClass, null, filter, qualifier) - .map(keyRecord -> mapToEntity(keyRecord.key, entityClass, keyRecord.record)); + return findAllUsingQuery(entityClass, getSetName(entityClass), filter, qualifier); } public Stream findAllUsingQuery(Class entityClass, Class targetClass, Filter filter, - Qualifier... qualifiers) { - return findAllRecordsUsingQuery(entityClass, targetClass, filter, qualifiers) - .map(keyRecord -> mapToEntity(keyRecord, entityClass, targetClass)); + Qualifier qualifier) { + return findAllRecordsUsingQuery(getSetName(entityClass), targetClass, filter, qualifier) + .map(keyRecord -> mapToEntity(keyRecord, targetClass)); } - Stream findAllUsingQueryWithDistinctPredicate(Class entityClass, Class targetClass, - Predicate distinctPredicate, - Qualifier... qualifiers) { - return findAllRecordsUsingQuery(entityClass, targetClass, null, qualifiers) + @Override + public Stream findAllUsingQuery(Class targetClass, String setName, Filter filter, + Qualifier qualifier) { + return findAllRecordsUsingQuery(setName, targetClass, filter, qualifier) + .map(keyRecord -> mapToEntity(keyRecord, targetClass)); + } + + private Stream findAllUsingQueryWithDistinctPredicate(String setName, Class targetClass, + Predicate distinctPredicate, + Qualifier... qualifiers) { + return findAllRecordsUsingQuery(setName, targetClass, null, qualifiers) .filter(distinctPredicate) - .map(keyRecord -> mapToEntity(keyRecord, entityClass, targetClass)); + .map(keyRecord -> mapToEntity(keyRecord, targetClass)); } private Stream applyPostProcessingOnResults(Stream results, Query query) { @@ -1076,28 +1309,27 @@ private Stream applyPostProcessingOnResults(Stream results, Sort sort, return results; } - Stream findAllRecordsUsingQuery(Class entityClass, Query query) { + private Stream findAllRecordsUsingQuery(String setName, Query query) { Assert.notNull(query, "Query must not be null!"); - Assert.notNull(entityClass, "Class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); Qualifier qualifier = query.getCriteria().getCriteriaObject(); - return findAllRecordsUsingQuery(entityClass, null, null, qualifier); + return findAllRecordsUsingQuery(setName, null, null, qualifier); } - Stream findAllRecordsUsingQuery(Class entityClass, Class targetClass, Filter filter, - Qualifier... qualifiers) { + private Stream findAllRecordsUsingQuery(String setName, Class targetClass, Filter filter, + Qualifier... qualifiers) { if (qualifiers != null && qualifiers.length > 0 && !allArrayElementsAreNull(qualifiers)) { validateQualifiers(qualifiers); Qualifier idQualifier = getOneIdQualifier(qualifiers); if (idQualifier != null) { // a special flow if there is id given - return findByIdsInternalWithoutMapping(getIdValue(idQualifier), entityClass, targetClass, + return findByIdsInternalWithoutMapping(getIdValue(idQualifier), setName, targetClass, excludeIdQualifier(qualifiers)).stream(); } } - String setName = getSetName(entityClass); KeyRecordIterator recIterator; if (targetClass != null) { diff --git a/src/main/java/org/springframework/data/aerospike/core/BaseAerospikeTemplate.java b/src/main/java/org/springframework/data/aerospike/core/BaseAerospikeTemplate.java index 3ecd4f5e6..12abad19d 100644 --- a/src/main/java/org/springframework/data/aerospike/core/BaseAerospikeTemplate.java +++ b/src/main/java/org/springframework/data/aerospike/core/BaseAerospikeTemplate.java @@ -28,6 +28,7 @@ import com.aerospike.client.policy.GenerationPolicy; import com.aerospike.client.policy.RecordExistsAction; import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.query.KeyRecord; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -123,8 +124,11 @@ private void loggerSetup() { } public String getSetName(Class entityClass) { - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - return entity.getSetName(); + return mappingContext.getRequiredPersistentEntity(entityClass).getSetName(); + } + + public String getSetName(T document) { + return mappingContext.getRequiredPersistentEntity(document.getClass()).getSetName(); } public MappingContext getMappingContext() { @@ -140,6 +144,10 @@ Class getEntityClass(T entity) { return (Class) entity.getClass(); } + T mapToEntity(KeyRecord keyRecord, Class targetClass) { + return mapToEntity(keyRecord.key, targetClass, keyRecord.record); + } + T mapToEntity(Key key, Class clazz, Record aeroRecord) { if (aeroRecord == null) { return null; @@ -194,14 +202,16 @@ RuntimeException translateError(AerospikeException e) { return translated == null ? e : translated; } - AerospikeWriteData writeData(T document) { + AerospikeWriteData writeData(T document, String setName) { AerospikeWriteData data = AerospikeWriteData.forWrite(getNamespace()); + data.setSetName(setName); converter.write(document, data); return data; } - AerospikeWriteData writeDataWithSpecificFields(T document, Collection fields) { + AerospikeWriteData writeDataWithSpecificFields(T document, String setName, Collection fields) { AerospikeWriteData data = AerospikeWriteData.forWrite(getNamespace()); + data.setSetName(setName); data.setRequestedBins(fieldsToBinNames(document, fields)); converter.write(document, data); return data; @@ -273,6 +283,13 @@ Key getKey(Object id, AerospikePersistentEntity entity) { return new Key(this.namespace, entity.getSetName(), userKey); } + Key getKey(Object id, String setName) { + Assert.notNull(id, "Id must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + String userKey = convertIfNecessary(id, String.class); + return new Key(this.namespace, setName, userKey); + } + GroupedEntities toGroupedEntities(EntitiesKeys entitiesKeys, Record[] records) { GroupedEntities.GroupedEntitiesBuilder builder = GroupedEntities.builder(); @@ -354,10 +371,10 @@ protected Operation[] getPutAndGetHeaderOperations(AerospikeWriteData data, bool : operations(bins, Operation::put, null, Operation.array(Operation.getHeader())); } - public BatchWriteData getBatchWriteForSave(T document) { + public BatchWriteData getBatchWriteForSave(T document, String setName) { Assert.notNull(document, "Document must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); Operation[] operations; @@ -379,10 +396,10 @@ public BatchWriteData getBatchWriteForSave(T document) { entity.hasVersionProperty()); } - public BatchWriteData getBatchWriteForInsert(T document) { + public BatchWriteData getBatchWriteForInsert(T document, String setName) { Assert.notNull(document, "Document must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); Operation[] operations; @@ -397,10 +414,10 @@ public BatchWriteData getBatchWriteForInsert(T document) { entity.hasVersionProperty()); } - public BatchWriteData getBatchWriteForUpdate(T document) { + public BatchWriteData getBatchWriteForUpdate(T document, String setName) { Assert.notNull(document, "Document must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); Operation[] operations; diff --git a/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeInternalOperations.java b/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeInternalOperations.java index bf6caba4d..cebba8de0 100644 --- a/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeInternalOperations.java +++ b/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeInternalOperations.java @@ -22,6 +22,22 @@ public interface ReactiveAerospikeInternalOperations { Mono findByIdInternal(Object id, Class entityClass, Class targetClass, Qualifier... qualifiers); + /** + * Find document by providing id with a given set name. + *

+ * Documents will be mapped to the given targetClass. + * + * @param id The id of the document to find. Must not be {@literal null}. + * @param entityClass The class to get the entity properties from (such as expiration). Must not be + * {@literal null}. + * @param targetClass The class to map the document to. + * @param setName Set name to find the document from. + * @param qualifiers {@link Qualifier}s provided to build a filter Expression for the query. Optional argument. + * @return The document from Aerospike, returned document will be mapped to targetClass's type. + */ + Mono findByIdInternal(Object id, Class entityClass, Class targetClass, String setName, + Qualifier... qualifiers); + /** * Find documents by providing multiple ids, set name will be determined by the given entityClass. *

@@ -36,4 +52,21 @@ Mono findByIdInternal(Object id, Class entityClass, Class target */ Flux findByIdsInternal(Collection ids, Class entityClass, Class targetClass, Qualifier... qualifiers); + + /** + * Find documents by providing multiple ids with a given set name. + *

+ * Documents will be mapped to the given targetClass. + * + * @param ids The ids of the documents to find. Must not be {@literal null}. + * @param entityClass The class to get the entity properties from (such as expiration). Must not be + * {@literal null}. + * @param targetClass The class to map the document to. + * @param setName Set name to find the document from. + * @param qualifiers {@link Qualifier}s provided to build a filter Expression for the query. Optional argument. + * @return The documents from Aerospike, returned documents will be mapped to targetClass's type, if no document + * exists, an empty list is returned. + */ + Flux findByIdsInternal(Collection ids, Class entityClass, Class targetClass, String setName, + Qualifier... qualifiers); } diff --git a/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeOperations.java b/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeOperations.java index 86772a606..508744ee5 100644 --- a/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeOperations.java +++ b/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeOperations.java @@ -73,6 +73,28 @@ public interface ReactiveAerospikeOperations { */ Mono save(T document); + /** + * Reactively save document with a given set name. + *

+ * If document has version property - CAS algorithm is used for updating record. Version property is used for + * deciding whether to create new record or update existing. If version is set to zero - new record will be created, + * creation will fail is such record already exists. If version is greater than zero - existing record will be + * updated with {@link com.aerospike.client.policy.RecordExistsAction#UPDATE_ONLY} policy combined with removing + * bins at first (analogous to {@link com.aerospike.client.policy.RecordExistsAction#REPLACE_ONLY}) taking into + * consideration the version property of the document. Version property will be updated with the server's version + * after successful operation. + *

+ * If document does not have version property - record is updated with + * {@link com.aerospike.client.policy.RecordExistsAction#UPDATE} policy combined with removing bins at first + * (analogous to {@link com.aerospike.client.policy.RecordExistsAction#REPLACE}). This means that when such record + * does not exist it will be created, otherwise updated - an "upsert". + * + * @param document The document to save. Must not be {@literal null}. + * @param setName The set name to save the document. + * @return A Mono of the new saved document. + */ + Mono save(T document, String setName); + /** * Reactively save documents using one batch request. The policies are analogous to {@link #save(Object)}. *

@@ -87,6 +109,22 @@ public interface ReactiveAerospikeOperations { */ Flux saveAll(Iterable documents); + /** + * Reactively save documents using one batch request with a given set name. The policies are analogous to + * {@link #save(Object)}. + *

+ * The order of returned results is preserved. The execution order is NOT preserved. + *

+ * Requires Server version 6.0+. + * + * @param documents documents to save. Must not be {@literal null}. + * @param setName The set name to save to documents. + * @return A Flux of the saved documents, otherwise onError is signalled with + * {@link AerospikeException.BatchRecordArray} if batch save results contain errors, or with + * {@link org.springframework.dao.DataAccessException} if batch operation failed. + */ + Flux saveAll(Iterable documents, String setName); + /** * Reactively insert document using {@link com.aerospike.client.policy.RecordExistsAction#CREATE_ONLY} policy. *

@@ -97,6 +135,18 @@ public interface ReactiveAerospikeOperations { */ Mono insert(T document); + /** + * Reactively insert document with a given set name using + * {@link com.aerospike.client.policy.RecordExistsAction#CREATE_ONLY} policy. + *

+ * If document has version property it will be updated with the server's version after successful operation. + * + * @param document The document to insert. Must not be {@literal null}. + * @param setName The set name to insert the document. + * @return A Mono of the new inserted document. + */ + Mono insert(T document, String setName); + /** * Reactively insert documents using one batch request. The policies are analogous to {@link #insert(Object)}. *

@@ -111,6 +161,22 @@ public interface ReactiveAerospikeOperations { */ Flux insertAll(Iterable documents); + /** + * Reactively insert documents with a given set using one batch request. The policies are analogous to + * {@link #insert(Object)}. + *

+ * The order of returned results is preserved. The execution order is NOT preserved. + *

+ * Requires Server version 6.0+. + * + * @param documents Documents to insert. Must not be {@literal null}. + * @param setName The set name to insert the documents. + * @return A Flux of the inserted documents, otherwise onError is signalled with + * {@link AerospikeException.BatchRecordArray} if batch insert results contain errors, or with + * {@link org.springframework.dao.DataAccessException} if batch operation failed. + */ + Flux insertAll(Iterable documents, String setName); + /** * Reactively update document using {@link com.aerospike.client.policy.RecordExistsAction#UPDATE_ONLY} policy * combined with removing bins at first (analogous to @@ -124,6 +190,20 @@ public interface ReactiveAerospikeOperations { */ Mono update(T document); + /** + * Reactively update document with a given set using + * {@link com.aerospike.client.policy.RecordExistsAction#UPDATE_ONLY} policy combined with removing bins at first + * (analogous to {@link com.aerospike.client.policy.RecordExistsAction#REPLACE_ONLY}) taking into consideration the + * version property of the document if it is present. + *

+ * If document has version property it will be updated with the server's version after successful operation. + * + * @param document The document to update. Must not be {@literal null}. + * @param setName The set name to update the document. + * @return A Mono of the new updated document. + */ + Mono update(T document, String setName); + /** * Reactively update document specific fields based on a given collection of fields. using * {@link com.aerospike.client.policy.RecordExistsAction#UPDATE_ONLY} policy - You can instantiate the document with @@ -137,6 +217,20 @@ public interface ReactiveAerospikeOperations { */ Mono update(T document, Collection fields); + /** + * Reactively update document specific fields based on a given collection of fields with a given set name. using + * {@link com.aerospike.client.policy.RecordExistsAction#UPDATE_ONLY} policy - You can instantiate the document with + * only relevant fields and specify the list of fields that you want to update. taking into consideration the + * version property of the document if it is present. + *

+ * If document has version property it will be updated with the server's version after successful operation. + * + * @param document The document to update. Must not be {@literal null}. + * @param setName The set name to update the document. + * @return A Mono of the new updated document. + */ + Mono update(T document, String setName, Collection fields); + /** * Reactively update documents using one batch request. The policies are analogous to {@link #update(Object)}. *

@@ -151,6 +245,22 @@ public interface ReactiveAerospikeOperations { */ Flux updateAll(Iterable documents); + /** + * Reactively update documents using one batch request with a given set name. The policies are analogous to + * {@link #update(Object)}. + *

+ * The order of returned results is preserved. The execution order is NOT preserved. + *

+ * Requires Server version 6.0+. + * + * @param documents Documents to update. Must not be {@literal null}. + * @param setName The set name to update the documents. + * @return A Flux of the updated documents, otherwise onError is signalled with + * {@link AerospikeException.BatchRecordArray} if batch update results contain errors, or with + * {@link org.springframework.dao.DataAccessException} if batch operation failed. + */ + Flux updateAll(Iterable documents, String setName); + /** * Reactively add integer/double bin values to existing document bin values, read the new modified document and map * it back the given document class type. @@ -162,6 +272,18 @@ public interface ReactiveAerospikeOperations { */ Mono add(T document, Map values); + /** + * Reactively add integer/double bin values to existing document bin values with a given set name, read the new + * modified document and map it back the given document class type. + * + * @param document The document to extract the Aerospike set from and to map the documents to. Must not be + * {@literal null}. + * @param setName Set name for the operation. + * @param values a Map of bin names and values to add. Must not be {@literal null}. + * @return A Mono of the modified document after add operations. + */ + Mono add(T document, String setName, Map values); + /** * Reactively add integer/double bin value to existing document bin value, read the new modified document and map it * back the given document class type. @@ -174,6 +296,19 @@ public interface ReactiveAerospikeOperations { */ Mono add(T document, String binName, long value); + /** + * Reactively add integer/double bin value to existing document bin value with a given set name, read the new + * modified document and map it back the given document class type. + * + * @param document The document to extract the Aerospike set from and to map the documents to. Must not be + * {@literal null}. + * @param setName Set name for the operation. + * @param binName Bin name to use add operation on. Must not be {@literal null}. + * @param value The value to add. + * @return A Mono of the modified document after add operation. + */ + Mono add(T document, String setName, String binName, long value); + /** * Reactively append bin string values to existing document bin values, read the new modified document and map it * back the given document class type. @@ -185,6 +320,18 @@ public interface ReactiveAerospikeOperations { */ Mono append(T document, Map values); + /** + * Reactively append bin string values to existing document bin values with a given set name, read the new modified + * document and map it back the given document class type. + * + * @param document The document to extract the Aerospike set from and to map the documents to. Must not be + * {@literal null}. + * @param setName Set name for the operation. + * @param values a Map of bin names and values to append. Must not be {@literal null}. + * @return A Mono of the modified document after append operations. + */ + Mono append(T document, String setName, Map values); + /** * Reactively append bin string value to existing document bin value, read the new modified document and map it back * the given document class type. @@ -197,6 +344,19 @@ public interface ReactiveAerospikeOperations { */ Mono append(T document, String binName, String value); + /** + * Reactively append bin string value to existing document bin value with a given set name, read the new modified + * document and map it back the given document class type. + * + * @param document The document to extract the Aerospike set from and to map the documents to. Must not be + * {@literal null}. + * @param binName Bin name to use append operation on. + * @param setName Set name for the operation. + * @param value The value to append. + * @return A Mono of the modified document after append operation. + */ + Mono append(T document, String setName, String binName, String value); + /** * Reactively prepend bin string values to existing document bin values, read the new modified document and map it * back the given document class type. @@ -208,6 +368,18 @@ public interface ReactiveAerospikeOperations { */ Mono prepend(T document, Map values); + /** + * Reactively prepend bin string values to existing document bin values with a given set name, read the new modified + * document and map it back the given document class type. + * + * @param document The document to extract the Aerospike set from and to map the documents to. Must not be + * {@literal null}. + * @param setName Set name for the operation. + * @param values a Map of bin names and values to prepend. Must not be {@literal null}. + * @return A Mono of the modified document after prepend operations. + */ + Mono prepend(T document, String setName, Map values); + /** * Reactively prepend bin string value to existing document bin value, read the new modified document and map it * back the given document class type. @@ -220,6 +392,19 @@ public interface ReactiveAerospikeOperations { */ Mono prepend(T document, String binName, String value); + /** + * Reactively prepend bin string value to existing document bin value with a given set name, read the new modified + * document and map it back the given document class type. + * + * @param document The document to extract the Aerospike set from and to map the documents to. Must not be + * {@literal null}. + * @param binName Bin name to use prepend operation on. + * @param setName Set name for the operation. + * @param value The value to prepend. + * @return A Mono of the modified document after prepend operation. + */ + Mono prepend(T document, String setName, String binName, String value); + /** * Reactively find all documents in the given entityClass's set and map them to the given class type. * @@ -238,6 +423,15 @@ public interface ReactiveAerospikeOperations { */ Flux findAll(Class entityClass, Class targetClass); + /** + * Reactively find all documents in the given set and map them to the given class type. + * + * @param targetClass The class to map the documents to. Must not be {@literal null}. + * @param setName The set name to find the document. + * @return A Flux of matching documents, returned documents will be mapped to entityClass's type. + */ + Flux findAll(Class targetClass, String setName); + /** * Reactively find all documents in the given entityClass's set using a provided sort and map them to the given * class type. @@ -258,11 +452,24 @@ public interface ReactiveAerospikeOperations { * @param offset The offset to start the range from. * @param limit The limit of the range. * @param entityClass The class to extract the Aerospike set from. - * @param targetClass The class to map the document to. Must not be {@literal null}. + * @param targetClass The class to map the documents to. Must not be {@literal null}. * @return A Flux of matching documents, returned documents will be mapped to targetClass's type. */ Flux findAll(Sort sort, long offset, long limit, Class entityClass, Class targetClass); + /** + * Reactively find all documents in the given entityClass's set using a provided sort and map them to the given + * target class type. + * + * @param sort The sort to affect the returned iterable documents order. + * @param offset The offset to start the range from. + * @param limit The limit of the range. + * @param targetClass The class to map the documents to. Must not be {@literal null}. + * @param setName The set name to find the documents. + * @return A Flux of matching documents, returned documents will be mapped to targetClass's type. + */ + Flux findAll(Sort sort, long offset, long limit, Class targetClass, String setName); + /** * Reactively find a document by id, set name will be determined by the given entityClass. *

@@ -275,6 +482,19 @@ public interface ReactiveAerospikeOperations { */ Mono findById(Object id, Class entityClass); + /** + * Reactively find a document by id with a given set name. + *

+ * Document will be mapped to the given entityClass. + * + * @param id The id of the document to find. Must not be {@literal null}. + * @param entityClass The class to map the document to and to get entity properties from (such expiration). Must not + * be {@literal null}. + * @param setName Set name to find the document from. + * @return A Mono of the matching document, returned document will be mapped to entityClass's type. + */ + Mono findById(Object id, Class entityClass, String setName); + /** * Reactively find a document by id, set name will be determined by the given entityClass. *

@@ -287,6 +507,20 @@ public interface ReactiveAerospikeOperations { */ Mono findById(Object id, Class entityClass, Class targetClass); + /** + * Reactively find a document by id with a given set name. + *

+ * Document will be mapped to the given targetClass. + * + * @param id The id of the document to find. Must not be {@literal null}. + * @param entityClass The class to map the document to and to get entity properties from (such expiration). Must not + * be {@literal null}. + * @param targetClass The class to map the document to. Must not be {@literal null}. + * @param setName Set name to find the document from. + * @return A Mono of the matching document, returned document will be mapped to targetClass's type. + */ + Mono findById(Object id, Class entityClass, Class targetClass, String setName); + /** * Reactively find documents by providing multiple ids using a single batch read operation, set name will be * determined by the given entityClass. @@ -308,11 +542,23 @@ public interface ReactiveAerospikeOperations { * * @param ids The ids of the documents to find. Must not be {@literal null}. * @param entityClass The class to extract the Aerospike set from. Must not be {@literal null}. - * @param targetClass The class to map the document to. Must not be {@literal null}. + * @param targetClass The class to map the documents to. Must not be {@literal null}. * @return A Flux of matching documents, returned documents will be mapped to targetClass's type. */ Flux findByIds(Iterable ids, Class entityClass, Class targetClass); + /** + * Reactively find documents by providing multiple ids using a single batch read operation with a given set name. + *

+ * Documents will be mapped to the given entityClass. + * + * @param ids The ids of the documents to find. Must not be {@literal null}. + * @param targetClass The class to map the documents to. Must not be {@literal null}. + * @param setName Set name to find the documents from. + * @return A Flux of matching documents, returned documents will be mapped to entityClass's type. + */ + Flux findByIds(Iterable ids, Class targetClass, String setName); + /** * Reactively executes a single batch request to get results for several entities. *

@@ -346,6 +592,16 @@ public interface ReactiveAerospikeOperations { */ Flux find(Query query, Class entityClass, Class targetClass); + /** + * Reactively find documents in the given set using a query and map them to the given target class type. + * + * @param query The query to filter results. Must not be {@literal null}. + * @param targetClass The class to map the document to. Must not be {@literal null}. + * @param setName Set name to find the documents from. + * @return A Flux of matching documents, returned documents will be mapped to targetClass's type. + */ + Flux find(Query query, Class targetClass, String setName); + /** * Reactively find documents in the given entityClass's set using a range (offset, limit) and a sort and map them to * the given class type. @@ -359,6 +615,19 @@ public interface ReactiveAerospikeOperations { */ Flux findInRange(long offset, long limit, Sort sort, Class entityClass); + /** + * Reactively find documents in the given set using a range (offset, limit) and a sort and map them to the given + * class type. + * + * @param offset The offset to start the range from. + * @param limit The limit of the range. + * @param sort The sort to affect the order of the returned Stream of documents. + * @param targetClass The class to map the document to. Must not be {@literal null}. + * @param setName Set name to find the documents from. + * @return A Flux of matching documents, returned documents will be mapped to entityClass's type. + */ + Flux findInRange(long offset, long limit, Sort sort, Class targetClass, String setName); + /** * Reactively find documents in the given entityClass's set using a range (offset, limit) and a sort and map them to * the given target class type. @@ -416,12 +685,28 @@ public interface ReactiveAerospikeOperations { */ Mono exists(Object id, Class entityClass); + /** + * Reactively check if document exists by providing document id with a given set name. + * + * @param id The id to check if exists. Must not be {@literal null}. + * @param setName Set name to check if document exists. + * @return A Mono of whether the document exists. + */ + Mono exists(Object id, String setName); + /** * Reactively truncate/delete all the documents in the given entity's set. * * @param entityClass The class to extract the Aerospike set from. Must not be {@literal null}. */ - Mono delete(Class entityClass); + Mono deleteAll(Class entityClass); + + /** + * Reactively truncate/delete all the documents in the given entity's set. + * + * @param setName Set name to truncate/delete all the documents in. + */ + Mono deleteAll(String setName); /** * Reactively delete document by id, set name will be determined by the given entityClass. @@ -430,7 +715,16 @@ public interface ReactiveAerospikeOperations { * @param entityClass The class to extract the Aerospike set from. Must not be {@literal null}. * @return A Mono of whether the document existed on server before deletion. */ - Mono delete(Object id, Class entityClass); + Mono deleteById(Object id, Class entityClass); + + /** + * Reactively delete document by id with a given set name. + * + * @param id The id of the document to delete. Must not be {@literal null}. + * @param setName Set name to delete the document. + * @return A Mono of whether the document existed on server before deletion. + */ + Mono deleteById(Object id, String setName); /** * Reactively delete document. @@ -440,6 +734,15 @@ public interface ReactiveAerospikeOperations { */ Mono delete(T document); + /** + * Reactively delete document with a given set name. + * + * @param document The document to delete. Must not be {@literal null}. + * @param setName Set name to delete the document. + * @return A Mono of whether the document existed on server before deletion. + */ + Mono delete(T document, String setName); + /** * Reactively delete documents by providing multiple ids using a single batch delete operation, set name will be * determined by the given entityClass. @@ -454,6 +757,19 @@ public interface ReactiveAerospikeOperations { */ Mono deleteByIds(Iterable ids, Class entityClass); + /** + * Reactively delete documents by providing multiple ids using a single batch delete operation with a given set + * name. + *

+ * This operation requires Server version 6.0+. + * + * @param ids The ids of the documents to find. Must not be {@literal null}. + * @param setName Set name to delete the document. + * @return onError is signalled with {@link AerospikeException.BatchRecordArray} if batch delete results contain + * errors, or with {@link org.springframework.dao.DataAccessException} if batch operation failed. + */ + Mono deleteByIds(Iterable ids, String setName); + /** * Executes a single batch delete for several entities. *

@@ -505,6 +821,42 @@ Mono createIndex(Class entityClass, String indexName, String binNam Mono createIndex(Class entityClass, String indexName, String binName, IndexType indexType, IndexCollectionType indexCollectionType, CTX... ctx); + /** + * Reactively create index by specified name in Aerospike. + * + * @param setName Set name to create the index. + * @param indexName The index name. Must not be {@literal null}. + * @param binName The bin name to create the index on. Must not be {@literal null}. + * @param indexType The type of the index. Must not be {@literal null}. + */ + Mono createIndex(String setName, String indexName, + String binName, IndexType indexType); + + /** + * Reactively create index by specified name in Aerospike. + * + * @param setName Set name to create the index. + * @param indexName The index name. Must not be {@literal null}. + * @param binName The bin name to create the index on. Must not be {@literal null}. + * @param indexType The type of the index. Must not be {@literal null}. + * @param indexCollectionType The collection type of the index. Must not be {@literal null}. + */ + Mono createIndex(String setName, String indexName, String binName, + IndexType indexType, IndexCollectionType indexCollectionType); + + /** + * Reactively create index by specified name in Aerospike. + * + * @param setName Set name to create the index. + * @param indexName The index name. Must not be {@literal null}. + * @param binName The bin name to create the index on. Must not be {@literal null}. + * @param indexType The type of the index. Must not be {@literal null}. + * @param indexCollectionType The collection type of the index. Must not be {@literal null}. + * @param ctx optional context to index on elements within a CDT. + */ + Mono createIndex(String setName, String indexName, String binName, + IndexType indexType, IndexCollectionType indexCollectionType, CTX... ctx); + /** * Reactively delete index by specified name from Aerospike. * @@ -513,11 +865,19 @@ Mono createIndex(Class entityClass, String indexName, String binNam */ Mono deleteIndex(Class entityClass, String indexName); + /** + * Reactively delete index by specified name from Aerospike. + * + * @param setName Set name to delete the index from. + * @param indexName The index name. Must not be {@literal null}. + */ + Mono deleteIndex(String setName, String indexName); + /** * Check whether an index with the specified name exists in Aerospike. * * @param indexName The Aerospike index name. Must not be {@literal null}. - * @return true if exists. + * @return Mono of true if exists. */ Mono indexExists(String indexName); diff --git a/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeTemplate.java b/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeTemplate.java index db5de2bb7..3908c92e3 100644 --- a/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeTemplate.java +++ b/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeTemplate.java @@ -47,7 +47,6 @@ import org.springframework.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -108,9 +107,14 @@ public void refreshIndexesCache() { @Override public Mono save(T document) { - Assert.notNull(document, "Document must not be null!"); + Assert.notNull(document, "Document for saving must not be null!"); + return save(document, getSetName(document)); + } - AerospikeWriteData data = writeData(document); + @Override + public Mono save(T document, String setName) { + Assert.notNull(document, "Document for saving must not be null!"); + AerospikeWriteData data = writeData(document, setName); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); if (entity.hasVersionProperty()) { WritePolicy policy = expectGenerationCasAwareSavePolicy(data); @@ -132,9 +136,16 @@ public Mono save(T document) { @Override public Flux saveAll(Iterable documents) { Assert.notNull(documents, "Documents for saving must not be null!"); + return saveAll(documents, getSetName(documents.iterator().next())); + } + + @Override + public Flux saveAll(Iterable documents, String setName) { + Assert.notNull(documents, "Documents for saving must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); List> batchWriteDataList = new ArrayList<>(); - documents.forEach(document -> batchWriteDataList.add(getBatchWriteForSave(document))); + documents.forEach(document -> batchWriteDataList.add(getBatchWriteForSave(document, setName))); List batchWriteRecords = batchWriteDataList.stream().map(BatchWriteData::batchRecord).toList(); @@ -175,9 +186,15 @@ private Mono>> checkForErrorsAndUpdateVersion(List Mono insert(T document) { + return insert(document, getSetName(document)); + } + + @Override + public Mono insert(T document, String setName) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); WritePolicy policy = ignoreGenerationSavePolicy(data, RecordExistsAction.CREATE_ONLY); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); @@ -199,10 +216,16 @@ public Mono insert(T document) { @Override public Flux insertAll(Iterable documents) { + return insertAll(documents, getSetName(documents.iterator().next())); + } + + @Override + public Flux insertAll(Iterable documents, String setName) { Assert.notNull(documents, "Documents for insert must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); List> batchWriteDataList = new ArrayList<>(); - documents.forEach(document -> batchWriteDataList.add(getBatchWriteForInsert(document))); + documents.forEach(document -> batchWriteDataList.add(getBatchWriteForInsert(document, setName))); List batchWriteRecords = batchWriteDataList.stream().map(BatchWriteData::batchRecord).toList(); @@ -211,9 +234,15 @@ public Flux insertAll(Iterable documents) { @Override public Mono update(T document) { + return update(document, getSetName(document)); + } + + @Override + public Mono update(T document, String setName) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); if (entity.hasVersionProperty()) { WritePolicy policy = expectGenerationSavePolicy(data, RecordExistsAction.UPDATE_ONLY); @@ -234,9 +263,16 @@ public Mono update(T document) { @Override public Mono update(T document, Collection fields) { + return update(document, getSetName(document), fields); + } + + @Override + public Mono update(T document, String setName, Collection fields) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + Assert.notNull(fields, "Fields must not be null!"); - AerospikeWriteData data = writeDataWithSpecificFields(document, fields); + AerospikeWriteData data = writeDataWithSpecificFields(document, setName, fields); AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(document.getClass()); if (entity.hasVersionProperty()) { WritePolicy policy = expectGenerationSavePolicy(data, RecordExistsAction.UPDATE_ONLY); @@ -254,58 +290,81 @@ public Mono update(T document, Collection fields) { @Override public Flux updateAll(Iterable documents) { + return updateAll(documents, getSetName(documents.iterator().next())); + } + + @Override + public Flux updateAll(Iterable documents, String setName) { Assert.notNull(documents, "Documents for saving must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); List> batchWriteDataList = new ArrayList<>(); - documents.forEach(document -> batchWriteDataList.add(getBatchWriteForUpdate(document))); + documents.forEach(document -> batchWriteDataList.add(getBatchWriteForUpdate(document, setName))); List batchWriteRecords = batchWriteDataList.stream().map(BatchWriteData::batchRecord).toList(); return batchWriteAndCheckForErrors(batchWriteRecords, batchWriteDataList, "update"); } - @SuppressWarnings("unchecked") @Override public Flux findAll(Class entityClass) { - Assert.notNull(entityClass, "Class must not be null!"); + Assert.notNull(entityClass, "Entity class must not be null!"); - return (Flux) findAllUsingQuery(entityClass, null, null, (Qualifier[]) null); + return findAll(entityClass, getSetName(entityClass)); } - @SuppressWarnings("unchecked") @Override public Flux findAll(Class entityClass, Class targetClass) { - Assert.notNull(entityClass, "Class must not be null!"); + Assert.notNull(entityClass, "Entity class must not be null!"); + Assert.notNull(targetClass, "Target class must not be null!"); + + return findAll(targetClass, getSetName(entityClass)); + } + + @Override + public Flux findAll(Class targetClass, String setName) { Assert.notNull(targetClass, "Target class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - return (Flux) findAllUsingQuery(entityClass, targetClass, null, (Qualifier[]) null); + return findAllUsingQuery(setName, targetClass, null, (Qualifier[]) null); } - @SuppressWarnings("unchecked") @Override public Flux findAll(Sort sort, long offset, long limit, Class entityClass) { Assert.notNull(entityClass, "Class must not be null!"); - return (Flux) findAllUsingQueryWithPostProcessing(entityClass, null, sort, offset, limit, - null, (Qualifier[]) null); + return findAll(sort, offset, limit, entityClass, getSetName(entityClass)); } - @SuppressWarnings("unchecked") @Override public Flux findAll(Sort sort, long offset, long limit, Class entityClass, Class targetClass) { Assert.notNull(entityClass, "Class must not be null!"); Assert.notNull(targetClass, "Target class must not be null!"); - return (Flux) findAllUsingQueryWithPostProcessing(entityClass, targetClass, sort, offset, limit, + return findAll(sort, offset, limit, targetClass, getSetName(entityClass)); + } + + @Override + public Flux findAll(Sort sort, long offset, long limit, Class targetClass, String setName) { + Assert.notNull(targetClass, "Target class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + + return findAllUsingQueryWithPostProcessing(setName, targetClass, sort, offset, limit, null, (Qualifier[]) null); } @Override public Mono add(T document, Map values) { + return add(document, getSetName(document), values); + } + + @Override + public Mono add(T document, String setName, Map values) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(values, "Values must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Operation[] operations = new Operation[values.size() + 1]; int x = 0; @@ -324,10 +383,16 @@ public Mono add(T document, Map values) { @Override public Mono add(T document, String binName, long value) { + return add(document, getSetName(document), binName, value); + } + + @Override + public Mono add(T document, String setName, String binName, long value) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(binName, "Bin name must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); WritePolicy writePolicy = WritePolicyBuilder.builder(this.writePolicyDefault) .expiration(data.getExpiration()) @@ -339,38 +404,62 @@ public Mono add(T document, String binName, long value) { @Override public Mono append(T document, Map values) { + return append(document, getSetName(document), values); + } + + @Override + public Mono append(T document, String setName, Map values) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(values, "Values must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Operation[] operations = operations(values, Operation.Type.APPEND, Operation.get()); return executeOperationsOnValue(document, data, operations, null); } @Override public Mono append(T document, String binName, String value) { + return append(document, getSetName(document), binName, value); + } + + @Override + public Mono append(T document, String setName, String binName, String value) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Operation[] operations = {Operation.append(new Bin(binName, value)), Operation.get(binName)}; return executeOperationsOnValue(document, data, operations, null); } @Override public Mono prepend(T document, Map values) { + return prepend(document, getSetName(document), values); + } + + @Override + public Mono prepend(T document, String setName, Map values) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(values, "Values must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Operation[] operations = operations(values, Operation.Type.PREPEND, Operation.get()); return executeOperationsOnValue(document, data, operations, null); } @Override public Mono prepend(T document, String binName, String value) { + return prepend(document, getSetName(document), binName, value); + } + + @Override + public Mono prepend(T document, String setName, String binName, String value) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); Operation[] operations = {Operation.prepend(new Bin(binName, value)), Operation.get(binName)}; return executeOperationsOnValue(document, data, operations, null); } @@ -385,8 +474,14 @@ private Mono executeOperationsOnValue(T document, AerospikeWriteData data @Override public Mono findById(Object id, Class entityClass) { + Assert.notNull(entityClass, "Class must not be null!"); + return findById(id, entityClass, getSetName(entityClass)); + } + + @Override + public Mono findById(Object id, Class entityClass, String setName) { AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - Key key = getKey(id, entity); + Key key = getKey(id, setName); if (entity.isTouchOnRead()) { Assert.state(!entity.hasExpirationProperty(), @@ -409,8 +504,13 @@ public Mono findById(Object id, Class entityClass) { @Override public Mono findById(Object id, Class entityClass, Class targetClass) { + return findById(id, entityClass, targetClass, getSetName(entityClass)); + } + + @Override + public Mono findById(Object id, Class entityClass, Class targetClass, String setName) { AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - Key key = getKey(id, entity); + Key key = getKey(id, setName); String[] binNames = getBinNamesFromTargetClass(targetClass); @@ -436,8 +536,14 @@ public Mono findById(Object id, Class entityClass, Class targetC @Override public Mono findByIdInternal(Object id, Class entityClass, Class targetClass, Qualifier... qualifiers) { + return findByIdInternal(id, entityClass, targetClass, getSetName(entityClass), qualifiers); + } + + @Override + public Mono findByIdInternal(Object id, Class entityClass, Class targetClass, String setName, + Qualifier... qualifiers) { AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - Key key = getKey(id, entity); + Key key = getKey(id, setName); String[] binNames = getBinNamesFromTargetClass(targetClass); @@ -474,31 +580,25 @@ public Mono findByIdInternal(Object id, Class entityClass, Class @Override public Flux findByIds(Iterable ids, Class entityClass) { - Assert.notNull(ids, "List of ids must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); - - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - - return Flux.fromIterable(ids) - .map(id -> getKey(id, entity)) - .flatMap(reactorClient::get) - .filter(keyRecord -> nonNull(keyRecord.record)) - .map(keyRecord -> mapToEntity(keyRecord.key, entityClass, keyRecord.record)); + return findByIds(ids, entityClass, getSetName(entityClass)); } @Override public Flux findByIds(Iterable ids, Class entityClass, Class targetClass) { - Assert.notNull(ids, "List of ids must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); - Assert.notNull(targetClass, "Target class must not be null!"); - - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); + return findByIds(ids, targetClass, getSetName(entityClass)); + } - String[] binNames = getBinNamesFromTargetClass(targetClass); + @Override + public Flux findByIds(Iterable ids, Class targetClass, String setName) { + Assert.notNull(ids, "List of ids must not be null!"); + Assert.notNull(targetClass, "Class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); return Flux.fromIterable(ids) - .map(id -> getKey(id, entity)) - .flatMap(key -> reactorClient.get(null, key, binNames)) + .map(id -> getKey(id, setName)) + .flatMap(reactorClient::get) .filter(keyRecord -> nonNull(keyRecord.record)) .map(keyRecord -> mapToEntity(keyRecord.key, targetClass, keyRecord.record)); } @@ -506,7 +606,13 @@ public Flux findByIds(Iterable ids, Class entityClass, Class @Override public Flux findByIdsInternal(Collection ids, Class entityClass, Class targetClass, Qualifier... qualifiers) { + return findByIdsInternal(ids, entityClass, targetClass, getSetName(entityClass), qualifiers); + } + @Override + public Flux findByIdsInternal(Collection ids, Class entityClass, Class targetClass, + String setName, + Qualifier... qualifiers) { Assert.notNull(ids, "List of ids must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); @@ -514,8 +620,6 @@ public Flux findByIdsInternal(Collection ids, Class entityClass, return Flux.empty(); } - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - BatchPolicy policy = getBatchPolicyFilterExp(qualifiers); Class target; @@ -526,29 +630,27 @@ public Flux findByIdsInternal(Collection ids, Class entityClass, } return Flux.fromIterable(ids) - .map(id -> getKey(id, entity)) - .flatMap(key -> getFromClient(policy, key, entityClass, targetClass)) + .map(id -> getKey(id, setName)) + .flatMap(key -> getFromClient(policy, key, targetClass)) .filter(keyRecord -> nonNull(keyRecord.record)) .map(keyRecord -> mapToEntity(keyRecord.key, target, keyRecord.record)); } - private Flux findByIdsInternalWithoutMapping(Collection ids, Class entityClass, - Class targetClass, + private Flux findByIdsInternalWithoutMapping(Collection ids, String setName, + Class targetClass, Qualifier... qualifiers) { Assert.notNull(ids, "List of ids must not be null!"); - Assert.notNull(entityClass, "Class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); if (ids.isEmpty()) { return Flux.empty(); } - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - BatchPolicy policy = getBatchPolicyFilterExp(qualifiers); return Flux.fromIterable(ids) - .map(id -> getKey(id, entity)) - .flatMap(key -> getFromClient(policy, key, entityClass, targetClass)) + .map(id -> getKey(id, setName)) + .flatMap(key -> getFromClient(policy, key, targetClass)) .filter(keyRecord -> nonNull(keyRecord.record)); } @@ -561,9 +663,8 @@ private BatchPolicy getBatchPolicyFilterExp(Qualifier[] qualifiers) { return null; } - private Mono getFromClient(BatchPolicy finalPolicy, Key key, Class entityClass, - Class targetClass) { - if (targetClass != null && targetClass != entityClass) { + private Mono getFromClient(BatchPolicy finalPolicy, Key key, Class targetClass) { + if (targetClass != null) { String[] binNames = getBinNamesFromTargetClass(targetClass); return reactorClient.get(finalPolicy, key, binNames); } else { @@ -590,43 +691,48 @@ private Mono findEntitiesByIdsInternal(GroupedKeys groupedKeys) .onErrorMap(this::translateError); } - @SuppressWarnings("unchecked") @Override public Flux find(Query query, Class entityClass) { - Assert.notNull(query, "Query must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); - - return (Flux) findAllUsingQueryWithPostProcessing(entityClass, null, query); + return find(query, entityClass, getSetName(entityClass)); } - @SuppressWarnings("unchecked") @Override public Flux find(Query query, Class entityClass, Class targetClass) { - Assert.notNull(query, "Query must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); + return find(query, targetClass, getSetName(entityClass)); + } + + @Override + public Flux find(Query query, Class targetClass, String setName) { + Assert.notNull(query, "Query must not be null!"); Assert.notNull(targetClass, "Target class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); - return (Flux) findAllUsingQueryWithPostProcessing(entityClass, targetClass, query); + return findAllUsingQueryWithPostProcessing(setName, targetClass, query); } - @SuppressWarnings("unchecked") @Override public Flux findInRange(long offset, long limit, Sort sort, Class entityClass) { - Assert.notNull(entityClass, "Class for count must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); - return (Flux) findAllUsingQueryWithPostProcessing(entityClass, null, sort, offset, limit, - null, (Qualifier[]) null); + return findInRange(offset, limit, sort, entityClass, getSetName(entityClass)); } - @SuppressWarnings("unchecked") @Override public Flux findInRange(long offset, long limit, Sort sort, Class entityClass, Class targetClass) { - Assert.notNull(entityClass, "Class for count must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); Assert.notNull(targetClass, "Target class must not be null!"); - return (Flux) findAllUsingQueryWithPostProcessing(entityClass, targetClass, sort, offset, limit, + return findInRange(offset, limit, sort, targetClass, getSetName(entityClass)); + } + + @Override + public Flux findInRange(long offset, long limit, Sort sort, Class targetClass, String setName) { + Assert.notNull(targetClass, "Target Class must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + + return findAllUsingQueryWithPostProcessing(setName, targetClass, sort, offset, limit, null, (Qualifier[]) null); } @@ -678,11 +784,17 @@ public Mono execute(Supplier supplier) { @Override public Mono exists(Object id, Class entityClass) { - Assert.notNull(id, "Id must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - Key key = getKey(id, entity); + return exists(id, getSetName(entityClass)); + } + + @Override + public Mono exists(Object id, String setName) { + Assert.notNull(id, "Id must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); + + Key key = getKey(id, setName); return reactorClient.exists(key) .map(Objects::nonNull) .defaultIfEmpty(false) @@ -690,36 +802,54 @@ public Mono exists(Object id, Class entityClass) { } @Override - public Mono delete(Class entityClass) { + public Mono deleteAll(Class entityClass) { Assert.notNull(entityClass, "Class must not be null!"); + return deleteAll(getSetName(entityClass)); + } + + @Override + public Mono deleteAll(String setName) { + Assert.notNull(setName, "Set name must not be null!"); + try { - String set = getSetName(entityClass); return Mono.fromRunnable( - () -> reactorClient.getAerospikeClient().truncate(null, this.namespace, set, null)); + () -> reactorClient.getAerospikeClient().truncate(null, this.namespace, setName, null)); } catch (AerospikeException e) { throw translateError(e); } } @Override - public Mono delete(Object id, Class entityClass) { + public Mono deleteById(Object id, Class entityClass) { Assert.notNull(id, "Id must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); + return deleteById(id, getSetName(entityClass)); + } + + @Override + public Mono deleteById(Object id, String setName) { + Assert.notNull(id, "Id must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); return reactorClient - .delete(ignoreGenerationDeletePolicy(), getKey(id, entity)) + .delete(ignoreGenerationDeletePolicy(), getKey(id, setName)) .map(k -> true) .onErrorMap(this::translateError); } @Override public Mono delete(T document) { + return delete(document, getSetName(document)); + } + + @Override + public Mono delete(T document, String setName) { Assert.notNull(document, "Document must not be null!"); + Assert.notNull(document, "Set name must not be null!"); - AerospikeWriteData data = writeData(document); + AerospikeWriteData data = writeData(document, setName); return reactorClient .delete(ignoreGenerationDeletePolicy(), data.getKey()) @@ -729,13 +859,18 @@ public Mono delete(T document) { @Override public Mono deleteByIds(Iterable ids, Class entityClass) { - Assert.notNull(ids, "List of ids must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); - AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); + return deleteByIds(ids, getSetName(entityClass)); + } + + @Override + public Mono deleteByIds(Iterable ids, String setName) { + Assert.notNull(ids, "List of ids must not be null!"); + Assert.notNull(setName, "Set name must not be null!"); Key[] keys = StreamSupport.stream(ids.spliterator(), false) - .map(id -> getKey(id, entity)) + .map(id -> getKey(id, setName)) .toArray(Key[]::new); return batchDeleteAndCheckForErrors(reactorClient, keys); @@ -792,14 +927,32 @@ public Mono createIndex(Class entityClass, String indexName, public Mono createIndex(Class entityClass, String indexName, String binName, IndexType indexType, IndexCollectionType indexCollectionType, CTX... ctx) { - Assert.notNull(entityClass, "Class must not be null!"); + return createIndex(getSetName(entityClass), indexName, binName, indexType, indexCollectionType, ctx); + } + + @Override + public Mono createIndex(String setName, String indexName, + String binName, IndexType indexType) { + return createIndex(setName, indexName, binName, indexType, IndexCollectionType.DEFAULT); + } + + @Override + public Mono createIndex(String setName, String indexName, + String binName, IndexType indexType, IndexCollectionType indexCollectionType) { + return createIndex(setName, indexName, binName, indexType, indexCollectionType, new CTX[0]); + } + + @Override + public Mono createIndex(String setName, String indexName, + String binName, IndexType indexType, IndexCollectionType indexCollectionType, + CTX... ctx) { + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(indexName, "Index name must not be null!"); Assert.notNull(binName, "Bin name must not be null!"); Assert.notNull(indexType, "Index type must not be null!"); Assert.notNull(indexCollectionType, "Index collection type must not be null!"); Assert.notNull(ctx, "Ctx must not be null!"); - String setName = getSetName(entityClass); return reactorClient.createIndex(null, this.namespace, setName, indexName, binName, indexType, indexCollectionType, ctx) .then(reactorIndexRefresher.refreshIndexes()) @@ -809,9 +962,14 @@ public Mono createIndex(Class entityClass, String indexName, @Override public Mono deleteIndex(Class entityClass, String indexName) { Assert.notNull(entityClass, "Class must not be null!"); + return deleteIndex(getSetName(entityClass), indexName); + } + + @Override + public Mono deleteIndex(String setName, String indexName) { + Assert.notNull(setName, "Set name must not be null!"); Assert.notNull(indexName, "Index name must not be null!"); - String setName = getSetName(entityClass); return reactorClient.dropIndex(null, this.namespace, setName, indexName) .then(reactorIndexRefresher.refreshIndexes()) .onErrorMap(this::translateError); @@ -929,21 +1087,21 @@ private Throwable translateError(Throwable e) { return e; } - Flux findAllUsingQueryWithPostProcessing(Class entityClass, Class targetClass, Query query) { + public Flux findAllUsingQueryWithPostProcessing(String setName, Class targetClass, Query query) { verifyUnsortedWithOffset(query.getSort(), query.getOffset()); Qualifier qualifier = query.getCriteria().getCriteriaObject(); - Flux results = findAllUsingQueryWithDistinctPredicate(entityClass, targetClass, + Flux results = findAllUsingQueryWithDistinctPredicate(setName, targetClass, getDistinctPredicate(query), qualifier); results = applyPostProcessingOnResults(results, query); return results; } @SuppressWarnings("SameParameterValue") - Flux findAllUsingQueryWithPostProcessing(Class entityClass, Class targetClass, Sort sort, + public Flux findAllUsingQueryWithPostProcessing(String setName, Class targetClass, Sort sort, long offset, long limit, Filter filter, Qualifier... qualifiers) { verifyUnsortedWithOffset(sort, offset); - Flux results = findAllUsingQuery(entityClass, targetClass, filter, qualifiers); + Flux results = findAllUsingQuery(setName, targetClass, filter, qualifiers); results = applyPostProcessingOnResults(results, sort, offset, limit); return results; } @@ -987,57 +1145,48 @@ private Flux applyPostProcessingOnResults(Flux results, Sort sort, lon return results; } - Object mapToEntity(KeyRecord keyRecord, Class entityClass, Class targetClass) { - if (targetClass != null) { - return mapToEntity(keyRecord.key, targetClass, keyRecord.record); - } - return mapToEntity(keyRecord.key, entityClass, keyRecord.record); - } - @Override public Flux findAllUsingQuery(Class entityClass, Filter filter, Qualifier qualifier) { - return findAllRecordsUsingQuery(entityClass, null, filter, qualifier) + return findAllRecordsUsingQuery(getSetName(entityClass), entityClass, filter, qualifier) .map(keyRecord -> mapToEntity(keyRecord.key, entityClass, keyRecord.record)); } - Flux findAllUsingQuery(Class entityClass, Class targetClass, Filter filter, - Qualifier... qualifiers) { - return findAllRecordsUsingQuery(entityClass, targetClass, filter, qualifiers) - .map(keyRecord -> mapToEntity(keyRecord, entityClass, targetClass)); + private Flux findAllUsingQuery(String setName, Class targetClass, Filter filter, + Qualifier... qualifiers) { + return findAllRecordsUsingQuery(setName, targetClass, filter, qualifiers) + .map(keyRecord -> mapToEntity(keyRecord, targetClass)); } - Flux findAllUsingQueryWithDistinctPredicate(Class entityClass, Class targetClass, - Predicate distinctPredicate, - Qualifier... qualifiers) { - return findAllRecordsUsingQuery(entityClass, targetClass, null, qualifiers) + private Flux findAllUsingQueryWithDistinctPredicate(String setName, Class targetClass, + Predicate distinctPredicate, + Qualifier... qualifiers) { + return findAllRecordsUsingQuery(setName, targetClass, null, qualifiers) .filter(distinctPredicate) - .map(keyRecord -> mapToEntity(keyRecord, entityClass, targetClass)); + .map(keyRecord -> mapToEntity(keyRecord, targetClass)); } - Flux findAllRecordsUsingQuery(Class entityClass, Query query) { + private Flux findAllRecordsUsingQuery(Class entityClass, Query query) { Assert.notNull(query, "Query must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); Qualifier qualifier = query.getCriteria().getCriteriaObject(); - return findAllRecordsUsingQuery(entityClass, null, null, qualifier); + return findAllRecordsUsingQuery(getSetName(entityClass), entityClass, null, qualifier); } - Flux findAllRecordsUsingQuery(Class entityClass, Class targetClass, Filter filter, - Qualifier... qualifiers) { + private Flux findAllRecordsUsingQuery(String setName, Class targetClass, Filter filter, + Qualifier... qualifiers) { if (qualifiers != null && qualifiers.length > 0 && !allArrayElementsAreNull(qualifiers)) { validateQualifiers(qualifiers); Qualifier idQualifier = getOneIdQualifier(qualifiers); if (idQualifier != null) { // a special flow if there is id given - return findByIdsInternalWithoutMapping(getIdValue(idQualifier), entityClass, targetClass, + return findByIdsInternalWithoutMapping(getIdValue(idQualifier), setName, targetClass, excludeIdQualifier(qualifiers)); } } - String setName = getSetName(entityClass); - if (targetClass != null) { String[] binNames = getBinNamesFromTargetClass(targetClass); return this.reactorQueryEngine.select(this.namespace, setName, binNames, filter, qualifiers); diff --git a/src/main/java/org/springframework/data/aerospike/repository/support/SimpleAerospikeRepository.java b/src/main/java/org/springframework/data/aerospike/repository/support/SimpleAerospikeRepository.java index b87888470..e18b292c9 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/support/SimpleAerospikeRepository.java +++ b/src/main/java/org/springframework/data/aerospike/repository/support/SimpleAerospikeRepository.java @@ -123,7 +123,7 @@ public long count() { @Override public void deleteById(ID id) { Assert.notNull(id, "The given id must not be null!"); - operations.delete(id, entityInformation.getJavaType()); + operations.deleteById(id, entityInformation.getJavaType()); } @Override @@ -136,7 +136,7 @@ public void deleteAll(Iterable entities) { @Override public void deleteAll() { - operations.delete(entityInformation.getJavaType()); + operations.deleteAll(entityInformation.getJavaType()); } @Override diff --git a/src/main/java/org/springframework/data/aerospike/repository/support/SimpleReactiveAerospikeRepository.java b/src/main/java/org/springframework/data/aerospike/repository/support/SimpleReactiveAerospikeRepository.java index e4f5ce46e..516425172 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/support/SimpleReactiveAerospikeRepository.java +++ b/src/main/java/org/springframework/data/aerospike/repository/support/SimpleReactiveAerospikeRepository.java @@ -105,7 +105,7 @@ public Mono count() { @Override public Mono deleteById(ID id) { Assert.notNull(id, "The given id must not be null!"); - return operations.delete(id, entityInformation.getJavaType()).then(); + return operations.deleteById(id, entityInformation.getJavaType()).then(); } @Override @@ -142,7 +142,7 @@ public Mono deleteAll(Publisher entityStream) { @Override public Mono deleteAll() { - return operations.delete(entityInformation.getJavaType()); + return operations.deleteAll(entityInformation.getJavaType()); } public void createIndex(Class domainType, String indexName, String binName, IndexType indexType) { diff --git a/src/test/java/org/springframework/data/aerospike/BaseBlockingIntegrationTests.java b/src/test/java/org/springframework/data/aerospike/BaseBlockingIntegrationTests.java index e4e5a5012..98f02fa73 100644 --- a/src/test/java/org/springframework/data/aerospike/BaseBlockingIntegrationTests.java +++ b/src/test/java/org/springframework/data/aerospike/BaseBlockingIntegrationTests.java @@ -36,4 +36,8 @@ public abstract class BaseBlockingIntegrationTests extends BaseIntegrationTests protected void deleteOneByOne(Collection collection) { collection.forEach(item -> template.delete(item)); } + + protected void deleteOneByOne(Collection collection, String setName) { + collection.forEach(item -> template.delete(item, setName)); + } } diff --git a/src/test/java/org/springframework/data/aerospike/BaseIntegrationTests.java b/src/test/java/org/springframework/data/aerospike/BaseIntegrationTests.java index e1b4748f2..ec2633c24 100644 --- a/src/test/java/org/springframework/data/aerospike/BaseIntegrationTests.java +++ b/src/test/java/org/springframework/data/aerospike/BaseIntegrationTests.java @@ -10,6 +10,7 @@ public abstract class BaseIntegrationTests { public static final String DEFAULT_SET_NAME = "aerospike"; + public static final String OVERRIDE_SET_NAME = "testSet1"; @Value("${embedded.aerospike.namespace}") protected String namespace; diff --git a/src/test/java/org/springframework/data/aerospike/BaseReactiveIntegrationTests.java b/src/test/java/org/springframework/data/aerospike/BaseReactiveIntegrationTests.java index 5aa5201ed..06355fa98 100644 --- a/src/test/java/org/springframework/data/aerospike/BaseReactiveIntegrationTests.java +++ b/src/test/java/org/springframework/data/aerospike/BaseReactiveIntegrationTests.java @@ -34,7 +34,15 @@ protected T findById(Serializable id, Class type) { return reactiveTemplate.findById(id, type).block(); } + protected T findById(Serializable id, Class type, String setName) { + return reactiveTemplate.findById(id, type, setName).block(); + } + protected void deleteAll(Collection persons) { Flux.fromIterable(persons).flatMap(person -> reactiveTemplate.delete(person)).blockLast(); } + + protected void deleteAll(Collection persons, String setName) { + Flux.fromIterable(persons).flatMap(person -> reactiveTemplate.delete(person, setName)).blockLast(); + } } diff --git a/src/test/java/org/springframework/data/aerospike/BlockingAerospikeTestOperations.java b/src/test/java/org/springframework/data/aerospike/BlockingAerospikeTestOperations.java index 9aff094d6..345e34262 100644 --- a/src/test/java/org/springframework/data/aerospike/BlockingAerospikeTestOperations.java +++ b/src/test/java/org/springframework/data/aerospike/BlockingAerospikeTestOperations.java @@ -33,7 +33,17 @@ protected boolean isEntityClassSetEmpty(Class clazz) { @Override protected void truncateSetOfEntityClass(Class clazz) { - template.delete(clazz); + template.deleteAll(clazz); + } + + @Override + protected boolean isSetEmpty(Class clazz, String setName) { + return template.findAll(clazz, setName).findAny().isEmpty(); + } + + @Override + protected void truncateSet(String setName) { + template.deleteAll(setName); } @Override diff --git a/src/test/java/org/springframework/data/aerospike/ReactiveBlockingAerospikeTestOperations.java b/src/test/java/org/springframework/data/aerospike/ReactiveBlockingAerospikeTestOperations.java index 3762b1452..87905a093 100644 --- a/src/test/java/org/springframework/data/aerospike/ReactiveBlockingAerospikeTestOperations.java +++ b/src/test/java/org/springframework/data/aerospike/ReactiveBlockingAerospikeTestOperations.java @@ -36,7 +36,17 @@ protected boolean isEntityClassSetEmpty(Class clazz) { @Override protected void truncateSetOfEntityClass(Class clazz) { - template.delete(clazz).block(); + template.deleteAll(clazz).block(); + } + + @Override + protected boolean isSetEmpty(Class clazz, String setName) { + return Boolean.FALSE.equals(template.findAll(clazz, setName).hasElements().block()); + } + + @Override + protected void truncateSet(String setName) { + template.deleteAll(setName).block(); } @Override diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateAddTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateAddTests.java index b6965bd95..771cb433b 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateAddTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateAddTests.java @@ -55,4 +55,34 @@ public void add_incrementsMultipleValues() { assertThat(result).isEqualTo(updated); template.delete(result); } + + @Test + public void add_incrementWithSetName() { + Person one = Person.builder().id(id).age(25).build(); + template.insert(one, OVERRIDE_SET_NAME); + + Person updated = template.add(one, OVERRIDE_SET_NAME, "age", 1); + + assertThat(updated.getAge()).isEqualTo(26); + Person result = template.findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(result).isEqualTo(updated); + template.delete(result, OVERRIDE_SET_NAME); + } + + @Test + public void add_incrementsMultipleValuesWithSetName() { + Person person = Person.builder().id(id).age(45).waist(90).build(); + template.insert(person, OVERRIDE_SET_NAME); + + Map values = new HashMap<>(); + values.put("age", 10L); + values.put("waist", 4L); + Person updated = template.add(person, OVERRIDE_SET_NAME, values); + + assertThat(updated.getAge()).isEqualTo(55); + assertThat(updated.getWaist()).isEqualTo(94); + Person result = template.findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(result).isEqualTo(updated); + template.delete(result, OVERRIDE_SET_NAME); + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateAppendTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateAppendTests.java index adddb44dc..3ecf134d6 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateAppendTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateAppendTests.java @@ -57,4 +57,18 @@ public void shouldAppendMultipleFields() { assertThat(actual.getEmailAddress()).isEqualTo("nastya@gmail.com"); template.delete(actual); // cleanup } + + @Test + public void shouldAppendWithSetName() { + Person one = Person.builder().id(id).firstName("Nas").build(); + template.insert(one, OVERRIDE_SET_NAME); + + Person appended = template.append(one, OVERRIDE_SET_NAME, "firstName", "tya"); + + assertThat(appended).isEqualTo(Person.builder().id(id).firstName("Nastya").build()); + assertThat(appended.getFirstName()).isEqualTo("Nastya"); + Person result = template.findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(result.getFirstName()).isEqualTo("Nastya"); + template.delete(result, OVERRIDE_SET_NAME); // cleanup + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateCompositeKeyTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateCompositeKeyTests.java index f6574490b..2f9bafb9f 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateCompositeKeyTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateCompositeKeyTests.java @@ -43,7 +43,7 @@ public void findByIds() { @Test public void delete() { - boolean deleted = template.delete(document.getId(), DocumentWithCompositeKey.class); + boolean deleted = template.deleteById(document.getId(), DocumentWithCompositeKey.class); assertThat(deleted).isTrue(); } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateCountTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateCountTests.java index 0867d637e..616c8c314 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateCountTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateCountTests.java @@ -47,6 +47,7 @@ public void beforeAll() { public void setUp() { super.setUp(); additionalAerospikeTestOperations.deleteAllAndVerify(Person.class); + additionalAerospikeTestOperations.deleteAllAndVerify(Person.class, OVERRIDE_SET_NAME); } @Test @@ -185,8 +186,33 @@ void countForObjects() { template.delete(template.findById(id4, Person.class)); } + @Test + void countForObjectsWithSetName() { + template.insert(new Person(id, "vasili", 50), OVERRIDE_SET_NAME); + String id2 = nextId(); + template.insert(new Person(id2, "vasili", 51), OVERRIDE_SET_NAME); + String id3 = nextId(); + template.insert(new Person(id3, "vasili", 52), OVERRIDE_SET_NAME); + String id4 = nextId(); + template.insert(new Person(id4, "petya", 52), OVERRIDE_SET_NAME); + + Awaitility.await() + .atMost(Duration.ofSeconds(15)) + .until(() -> isCountExactlyNumWithSetName(4L, OVERRIDE_SET_NAME)); + + template.delete(template.findById(id, Person.class, OVERRIDE_SET_NAME), OVERRIDE_SET_NAME); + template.delete(template.findById(id2, Person.class, OVERRIDE_SET_NAME), OVERRIDE_SET_NAME); + template.delete(template.findById(id3, Person.class, OVERRIDE_SET_NAME), OVERRIDE_SET_NAME); + template.delete(template.findById(id4, Person.class, OVERRIDE_SET_NAME), OVERRIDE_SET_NAME); + } + @SuppressWarnings("SameParameterValue") private boolean isCountExactlyNum(Long num) { return Objects.equals(template.count(Person.class), num); } + + @SuppressWarnings("SameParameterValue") + private boolean isCountExactlyNumWithSetName(Long num, String setName) { + return Objects.equals(template.count(setName), num); + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateDeleteTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateDeleteTests.java index a17c51d65..6c064a2fc 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateDeleteTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateDeleteTests.java @@ -84,21 +84,45 @@ public void deleteByObject_deletesDocument() { assertThat(result).isNull(); } + @Test + public void deleteByObject_deletesDocumentWithSetName() { + Person document = new Person(id, "QLastName", 21); + template.insert(document, OVERRIDE_SET_NAME); + + boolean deleted = template.delete(document, OVERRIDE_SET_NAME); + assertThat(deleted).isTrue(); + + Person result = template.findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(result).isNull(); + } + @Test public void deleteById_deletesDocument() { Person document = new Person(id, "QLastName", 21); template.insert(document); - boolean deleted = template.delete(id, Person.class); + boolean deleted = template.deleteById(id, Person.class); assertThat(deleted).isTrue(); Person result = template.findById(id, Person.class); assertThat(result).isNull(); } + @Test + public void deleteById_deletesDocumentWithSetName() { + Person document = new Person(id, "QLastName", 21); + template.insert(document, OVERRIDE_SET_NAME); + + boolean deleted = template.deleteById(id, OVERRIDE_SET_NAME); + assertThat(deleted).isTrue(); + + Person result = template.findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(result).isNull(); + } + @Test public void deleteById_returnsFalseIfValueIsAbsent() { - assertThat(template.delete(id, Person.class)).isFalse(); + assertThat(template.deleteById(id, Person.class)).isFalse(); } @Test @@ -146,7 +170,7 @@ public void deleteByType_ShouldDeleteAllDocumentsWithCustomSetName() { assertThat(template.findByIds(Arrays.asList(id1, id2), CustomCollectionClassToDelete.class)).hasSize(2); - template.delete(CustomCollectionClassToDelete.class); + template.deleteAll(CustomCollectionClassToDelete.class); // truncate is async operation that is why we need to wait until // it completes @@ -162,7 +186,7 @@ public void deleteByType_ShouldDeleteAllDocumentsWithDefaultSetName() { template.save(new DocumentWithExpiration(id1)); template.save(new DocumentWithExpiration(id2)); - template.delete(DocumentWithExpiration.class); + template.deleteAll(DocumentWithExpiration.class); // truncate is async operation that is why we need to wait until // it completes @@ -175,7 +199,7 @@ public void deleteByType_ShouldDeleteAllDocumentsWithDefaultSetName() { @Test public void deleteByType_NullTypeThrowsException() { - assertThatThrownBy(() -> template.delete(null)) + assertThatThrownBy(() -> template.deleteAll((Class) null)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Class must not be null!"); } @@ -212,4 +236,20 @@ public void deleteAll_ShouldDeleteAllDocuments() { assertThat(template.findByIds(ids, DocumentWithExpiration.class)).isEmpty(); } } + + @Test + public void deleteAll_ShouldDeleteAllDocumentsWithSetName() { + // batch delete operations are supported starting with Server version 6.0+ + if (ServerVersionUtils.isBatchWriteSupported(client)) { + String id1 = nextId(); + String id2 = nextId(); + template.save(new DocumentWithExpiration(id1), OVERRIDE_SET_NAME); + template.save(new DocumentWithExpiration(id2), OVERRIDE_SET_NAME); + + List ids = List.of(id1, id2); + template.deleteByIds(ids, OVERRIDE_SET_NAME); + + assertThat(template.findByIds(ids, DocumentWithExpiration.class, OVERRIDE_SET_NAME)).isEmpty(); + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateExistsTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateExistsTests.java index 1ba8e4e4d..5e773d3fc 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateExistsTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateExistsTests.java @@ -32,8 +32,22 @@ public void exists_shouldReturnTrueIfValueIsPresent() { template.delete(one); } + @Test + public void existsWithSetName_shouldReturnTrueIfValueIsPresent() { + Person one = Person.builder().id(id).firstName("tya").emailAddress("gmail.com").build(); + template.insert(one, OVERRIDE_SET_NAME); + + assertThat(template.exists(id, OVERRIDE_SET_NAME)).isTrue(); + template.delete(one, OVERRIDE_SET_NAME); + } + @Test public void exists_shouldReturnFalseIfValueIsAbsent() { assertThat(template.exists(id, Person.class)).isFalse(); } + + @Test + public void existsWithSetName_shouldReturnFalseIfValueIsAbsent() { + assertThat(template.exists(id, OVERRIDE_SET_NAME)).isFalse(); + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindProjectionTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdProjectionTests.java similarity index 65% rename from src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindProjectionTests.java rename to src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdProjectionTests.java index e0214f3a4..d1e3cb275 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindProjectionTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdProjectionTests.java @@ -13,7 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class AerospikeTemplateFindProjectionTests extends BaseBlockingIntegrationTests { +public class AerospikeTemplateFindByIdProjectionTests extends BaseBlockingIntegrationTests { @Test public void findByIdWithProjection() { @@ -98,6 +98,34 @@ public void findByIdWithProjectionPersonWithMissingFieldsIncludingTouchOnRead() template.delete(secondPerson); //cleanup } + @Test + public void findByIdWithProjectionPersonWithMissingFieldsIncludingTouchOnReadAndSetName() { + PersonTouchOnRead firstPerson = PersonTouchOnRead.builder() + .id(nextId()) + .firstName("first") + .lastName("lastName1") + .emailAddress("gmail.com") + .build(); + PersonTouchOnRead secondPerson = PersonTouchOnRead.builder() + .id(nextId()) + .firstName("second") + .lastName("lastName2") + .emailAddress("gmail.com") + .build(); + template.save(firstPerson, OVERRIDE_SET_NAME); + template.save(secondPerson, OVERRIDE_SET_NAME); + + PersonMissingAndRedundantFields result = template.findById(firstPerson.getId(), PersonTouchOnRead.class, + PersonMissingAndRedundantFields.class, OVERRIDE_SET_NAME); + + assertThat(result.getFirstName()).isEqualTo("first"); + assertThat(result.getLastName()).isEqualTo("lastName1"); + assertThat(result.getMissingField()).isNull(); + assertThat(result.getEmailAddress()).isNull(); // Not annotated with @Field("email"). + template.delete(firstPerson, OVERRIDE_SET_NAME); // cleanup + template.delete(secondPerson, OVERRIDE_SET_NAME); //cleanup + } + @Test public void findByIdsWithTargetClass_shouldFindExisting() { Person firstPerson = Person.builder().id(nextId()).firstName("first").emailAddress("gmail.com").age(40).build(); @@ -114,10 +142,33 @@ public void findByIdsWithTargetClass_shouldFindExisting() { template.delete(secondPerson); //cleanup } + @Test + public void findByIdsWithTargetClassAndSetName_shouldFindExisting() { + Person firstPerson = Person.builder().id(nextId()).firstName("first").emailAddress("gmail.com").age(40).build(); + Person secondPerson = Person.builder().id(nextId()).firstName("second").emailAddress("gmail.com").age(50) + .build(); + template.save(firstPerson, OVERRIDE_SET_NAME); + template.save(secondPerson, OVERRIDE_SET_NAME); + + List ids = Arrays.asList(nextId(), firstPerson.getId(), secondPerson.getId()); + List actual = template.findByIds(ids, Person.class, PersonSomeFields.class, OVERRIDE_SET_NAME); + + assertThat(actual).containsExactly(firstPerson.toPersonSomeFields(), secondPerson.toPersonSomeFields()); + template.delete(firstPerson, OVERRIDE_SET_NAME); // cleanup + template.delete(secondPerson, OVERRIDE_SET_NAME); //cleanup + } + @Test public void findByIdsWithTargetClass_shouldReturnEmptyList() { List actual = template.findByIds(Collections.emptyList(), Person.class, PersonSomeFields.class); assertThat(actual).isEmpty(); } + + @Test + public void findByIdsWithTargetClassAndSetName_shouldReturnEmptyList() { + List actual = template.findByIds(Collections.emptyList(), Person.class, + PersonSomeFields.class, OVERRIDE_SET_NAME); + assertThat(actual).isEmpty(); + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdTests.java similarity index 76% rename from src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindTests.java rename to src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdTests.java index 75d887281..bb30928e0 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdTests.java @@ -37,7 +37,7 @@ import static org.springframework.data.aerospike.SampleClasses.DocumentWithTouchOnReadAndExpirationProperty; import static org.springframework.data.aerospike.SampleClasses.EXPIRATION_ONE_MINUTE; -public class AerospikeTemplateFindTests extends BaseBlockingIntegrationTests { +public class AerospikeTemplateFindByIdTests extends BaseBlockingIntegrationTests { @Test public void findById_shouldReadVersionedClassWithAllArgsConstructor() { @@ -50,6 +50,17 @@ public void findById_shouldReadVersionedClassWithAllArgsConstructor() { template.delete(result); // cleanup } + @Test + public void findById_shouldReadVersionedClassWithAllArgsConstructorAndSetName() { + VersionedClassWithAllArgsConstructor inserted = new VersionedClassWithAllArgsConstructor(id, "foobar", 0L); + template.insert(inserted, OVERRIDE_SET_NAME); + assertThat(template.findById(id, VersionedClassWithAllArgsConstructor.class, OVERRIDE_SET_NAME).version).isEqualTo(1L); + template.update(new VersionedClassWithAllArgsConstructor(id, "foobar1", inserted.version), OVERRIDE_SET_NAME); + VersionedClassWithAllArgsConstructor result = template.findById(id, VersionedClassWithAllArgsConstructor.class, OVERRIDE_SET_NAME); + assertThat(result.version).isEqualTo(2L); + template.delete(result, OVERRIDE_SET_NAME); // cleanup + } + @Test public void findById_shouldReturnNullForNonExistingKey() { Person one = template.findById("person-non-existing-key", Person.class); @@ -97,6 +108,19 @@ public void findByIds_shouldFindExisting() { template.delete(secondPerson); //cleanup } + @Test + public void findByIdsWithSetName_shouldFindExisting() { + Person firstPerson = Person.builder().id(nextId()).firstName("first").emailAddress("gmail.com").build(); + Person secondPerson = Person.builder().id(nextId()).firstName("second").emailAddress("gmail.com").build(); + template.save(firstPerson, OVERRIDE_SET_NAME); + template.save(secondPerson, OVERRIDE_SET_NAME); + + List ids = Arrays.asList(nextId(), firstPerson.getId(), secondPerson.getId()); + List actual = template.findByIds(ids, Person.class, OVERRIDE_SET_NAME); + assertThat(actual).containsExactly(firstPerson, secondPerson); + template.delete(firstPerson, OVERRIDE_SET_NAME); // cleanup + template.delete(secondPerson, OVERRIDE_SET_NAME); //cleanup + } @Test public void findByIds_shouldReturnEmptyList() { @@ -104,6 +128,12 @@ public void findByIds_shouldReturnEmptyList() { assertThat(actual).isEmpty(); } + @Test + public void findByIdsWithSetName_shouldReturnEmptyList() { + List actual = template.findByIds(Collections.emptyList(), Person.class, OVERRIDE_SET_NAME); + assertThat(actual).isEmpty(); + } + @Test public void findById_shouldFailOnTouchOnReadWithExpirationProperty() { template.insert(new DocumentWithTouchOnReadAndExpirationProperty(id, EXPIRATION_ONE_MINUTE)); diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByQueryProjectionTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByQueryProjectionTests.java index 65370f3be..caddbf57b 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByQueryProjectionTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByQueryProjectionTests.java @@ -51,12 +51,17 @@ public class AerospikeTemplateFindByQueryProjectionTests extends BaseBlockingInt @BeforeAll public void beforeAllSetUp() { deleteOneByOne(allPersons); + deleteOneByOne(allPersons, OVERRIDE_SET_NAME); // batch write operations are supported starting with Server version 6.0+ if (ServerVersionUtils.isBatchWriteSupported(client)) { template.insertAll(allPersons); + template.insertAll(allPersons, OVERRIDE_SET_NAME); } else { - allPersons.forEach(person -> template.insert(person)); + allPersons.forEach(person -> { + template.insert(person); + template.insert(person, OVERRIDE_SET_NAME); + }); } additionalAerospikeTestOperations.createIndex(Person.class, "person_age_index", "age", @@ -65,6 +70,12 @@ public void beforeAllSetUp() { , IndexType.STRING); additionalAerospikeTestOperations.createIndex(Person.class, "person_last_name_index", "lastName", IndexType.STRING); + additionalAerospikeTestOperations.createIndex(OVERRIDE_SET_NAME, "person_set_age_index", "age", + IndexType.NUMERIC); + additionalAerospikeTestOperations.createIndex(OVERRIDE_SET_NAME, "person_set_first_name_index", "firstName" + , IndexType.STRING); + additionalAerospikeTestOperations.createIndex(OVERRIDE_SET_NAME, "person_set_last_name_index", "lastName", + IndexType.STRING); } @Override @@ -76,9 +87,13 @@ public void setUp() { @AfterAll public void afterAll() { deleteOneByOne(allPersons); + deleteOneByOne(allPersons, OVERRIDE_SET_NAME); additionalAerospikeTestOperations.dropIndex(Person.class, "person_age_index"); additionalAerospikeTestOperations.dropIndex(Person.class, "person_first_name_index"); additionalAerospikeTestOperations.dropIndex(Person.class, "person_last_name_index"); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, "person_set_age_index"); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, "person_set_first_name_index"); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, "person_set_last_name_index"); } @Test @@ -94,6 +109,19 @@ public void findWithFilterEqualProjection() { .build()); } + @Test + public void findWithFilterEqualProjectionWithSetName() { + Query query = QueryUtils.createQueryForMethodWithArgs("findByFirstName", "Dave"); + + Stream result = template.find(query, PersonSomeFields.class, OVERRIDE_SET_NAME); + + assertThat(result).containsOnly(PersonSomeFields.builder() + .firstName("Dave") + .lastName("Matthews") + .emailAddress("dave@gmail.com") + .build()); + } + @Test public void findWithFilterEqualOrderByAscProjection() { Query query = QueryUtils.createQueryForMethodWithArgs("findByLastNameOrderByFirstNameAsc", "Matthews"); @@ -125,4 +153,11 @@ public void findAll_findsAllExistingDocumentsProjection() { assertThat(result).containsAll(allPersons.stream().map(Person::toPersonSomeFields).collect(Collectors.toList())); } + + @Test + public void findAll_findsAllExistingDocumentsProjectionWithSetName() { + Stream result = template.findAll(PersonSomeFields.class, OVERRIDE_SET_NAME); + + assertThat(result).containsAll(allPersons.stream().map(Person::toPersonSomeFields).collect(Collectors.toList())); + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByQueryTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByQueryTests.java index 1096ee715..db4957e9f 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByQueryTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByQueryTests.java @@ -87,8 +87,12 @@ public void beforeAllSetUp() { // batch write operations are supported starting with Server version 6.0+ if (ServerVersionUtils.isBatchWriteSupported(client)) { template.insertAll(allPersons); + template.insertAll(allPersons, OVERRIDE_SET_NAME); } else { - allPersons.forEach(person -> template.insert(person)); + allPersons.forEach(person -> { + template.insert(person); + template.insert(person, OVERRIDE_SET_NAME); + }); } additionalAerospikeTestOperations.createIndex(Person.class, "person_age_index", "age", @@ -97,6 +101,12 @@ public void beforeAllSetUp() { IndexType.STRING); additionalAerospikeTestOperations.createIndex(Person.class, "person_last_name_index", "lastName", IndexType.STRING); + additionalAerospikeTestOperations.createIndex(OVERRIDE_SET_NAME, "person_set_age_index", "age", + IndexType.NUMERIC); + additionalAerospikeTestOperations.createIndex(OVERRIDE_SET_NAME, "person_set_first_name_index", "firstName" + , IndexType.STRING); + additionalAerospikeTestOperations.createIndex(OVERRIDE_SET_NAME, "person_set_last_name_index", "lastName", + IndexType.STRING); } @Override @@ -108,9 +118,13 @@ public void setUp() { @AfterAll public void afterAll() { deleteOneByOne(allPersons); + deleteOneByOne(allPersons, OVERRIDE_SET_NAME); additionalAerospikeTestOperations.dropIndex(Person.class, "person_age_index"); additionalAerospikeTestOperations.dropIndex(Person.class, "person_first_name_index"); additionalAerospikeTestOperations.dropIndex(Person.class, "person_last_name_index"); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, "person_set_age_index"); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, "person_set_first_name_index"); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, "person_set_last_name_index"); } @Test @@ -120,6 +134,13 @@ public void findWithFilterEqual() { assertThat(result).containsOnly(dave); } + @Test + public void findWithFilterEqualWithSetName() { + Query query = QueryUtils.createQueryForMethodWithArgs("findByFirstName", "Dave"); + Stream result = template.find(query, Person.class, OVERRIDE_SET_NAME); + assertThat(result).containsOnly(dave); + } + @Test public void findWithFilterEqualOrderByAsc() { Query query = QueryUtils.createQueryForMethodWithArgs("findByLastNameOrderByFirstNameAsc", "Matthews"); @@ -159,6 +180,14 @@ public void findWithFilterRange() { assertThat(result).hasSize(6); } + @Test + public void findWithFilterRangeWithSetName() { + Query query = QueryUtils.createQueryForMethodWithArgs("findCustomerByAgeBetween", 25, 31); + + Stream result = template.find(query, Person.class, OVERRIDE_SET_NAME); + assertThat(result).hasSize(6); + } + @Test public void findWithFilterRangeNonExisting() { Query query = QueryUtils.createQueryForMethodWithArgs("findCustomerByAgeBetween", 100, 150); @@ -192,6 +221,14 @@ public void findInRange_shouldFindLimitedNumberOfDocuments() { assertThat(stream).hasSize(5); } + @Test + public void findInRangeWithSetName_shouldFindLimitedNumberOfDocuments() { + int skip = 0; + int limit = 5; + Stream stream = template.findInRange(skip, limit, Sort.unsorted(), Person.class, OVERRIDE_SET_NAME); + assertThat(stream).hasSize(5); + } + @Test public void findInRange_shouldFailOnUnsortedQueryWithOffsetValue() { int skip = 3; @@ -201,6 +238,15 @@ public void findInRange_shouldFailOnUnsortedQueryWithOffsetValue() { .hasMessage("Unsorted query must not have offset value. For retrieving paged results use sorted query."); } + @Test + public void findInRangeWithSetName_shouldFailOnUnsortedQueryWithOffsetValue() { + int skip = 3; + int limit = 5; + assertThatThrownBy(() -> template.findInRange(skip, limit, Sort.unsorted(), Person.class, OVERRIDE_SET_NAME)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unsorted query must not have offset value. For retrieving paged results use sorted query."); + } + @Test public void findInRange_shouldFindLimitedNumberOfDocumentsWithOrderBy() { int skip = 0; @@ -225,6 +271,17 @@ public void findAll_OrderByFirstName() { .containsExactly(aabbot, alister, ashley, beatrice, dave, jean, knowlen, mitch, xylophone, zaipper); } + @Test + public void findAll_OrderByFirstNameWithSetName() { + Sort sort = Sort.by(asc("firstName")); + List result = template.findAll(sort, 0, 0, Person.class, OVERRIDE_SET_NAME) + .collect(Collectors.toList()); + + assertThat(result) + .hasSize(10) + .containsExactly(aabbot, alister, ashley, beatrice, dave, jean, knowlen, mitch, xylophone, zaipper); + } + @Test public void findAll_findAllExistingDocuments() { Stream result = template.findAll(Person.class); @@ -256,6 +313,16 @@ public void findByListContainingInteger() { .containsExactlyInAnyOrder(jean); } + @Test + public void findByListContainingIntegerWithSetName() { + Query query = QueryUtils.createQueryForMethodWithArgs("findByIntsContaining", 100); + Stream result = template.find(query, Person.class, OVERRIDE_SET_NAME); + + assertThat(result) + .hasSize(1) + .containsExactlyInAnyOrder(jean); + } + @Test public void findByListContainingString() { Query query = QueryUtils.createQueryForMethodWithArgs("findByStringsContaining", "str2"); @@ -417,6 +484,16 @@ public void findPersonsByFriendAgeLessThanOrEqual() { .containsExactlyInAnyOrder(jean, ashley, beatrice, dave); } + @Test + public void findPersonsByFriendAgeLessThanOrEqualWithSetName() { + Query query = QueryUtils.createQueryForMethodWithArgs("findByFriendAgeLessThanEqual", 54); + Stream result = template.find(query, Person.class, OVERRIDE_SET_NAME); + + assertThat(result) + .hasSize(4) + .containsExactlyInAnyOrder(jean, ashley, beatrice, dave); + } + @Test public void findPersonsByFriendAgeRange() { Query query = QueryUtils.createQueryForMethodWithArgs("findByFriendAgeBetween", 50, 55); diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateIndexTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateIndexTests.java index 10be2e8ac..ab74e64ef 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateIndexTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateIndexTests.java @@ -28,13 +28,17 @@ public class AerospikeTemplateIndexTests extends BaseBlockingIntegrationTests { private static final String INDEX_TEST_1 = "index-test-77777"; + private static final String INDEX_TEST_1_WITH_SET = "index-test-77777" + OVERRIDE_SET_NAME; private static final String INDEX_TEST_2 = "index-test-88888"; + private static final String INDEX_TEST_2_WITH_SET = "index-test-88888" + OVERRIDE_SET_NAME; @Override @BeforeEach public void setUp() { additionalAerospikeTestOperations.dropIndex(IndexedDocument.class, INDEX_TEST_1); additionalAerospikeTestOperations.dropIndex(IndexedDocument.class, INDEX_TEST_2); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, INDEX_TEST_1_WITH_SET); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, INDEX_TEST_2_WITH_SET); } @Test @@ -85,6 +89,18 @@ public void createIndex_createsIndex() { ); } + @Test + public void createIndexWithSetName_createsIndex() { + template.createIndex(OVERRIDE_SET_NAME, INDEX_TEST_1_WITH_SET, "stringField", IndexType.STRING); + assertThat(template.indexExists(INDEX_TEST_1_WITH_SET)).isTrue(); + + awaitTenSecondsUntil(() -> + assertThat(additionalAerospikeTestOperations.getIndexes(OVERRIDE_SET_NAME)) + .contains(Index.builder().name(INDEX_TEST_1_WITH_SET).namespace(namespace).set(OVERRIDE_SET_NAME).bin("stringField") + .indexType(IndexType.STRING).build()) + ); + } + // for Aerospike Server ver. >= 6.1.0.1 @Test public void createIndex_shouldNotThrowExceptionIfIndexAlreadyExists() { @@ -111,6 +127,18 @@ public void createIndex_createsListIndex() { ); } + @Test + public void createIndexWithSetName_createsListIndex() { + template.createIndex(OVERRIDE_SET_NAME, INDEX_TEST_1_WITH_SET, "listField", IndexType.STRING, + IndexCollectionType.LIST); + + awaitTenSecondsUntil(() -> + assertThat(additionalAerospikeTestOperations.getIndexes(OVERRIDE_SET_NAME)) + .contains(Index.builder().name(INDEX_TEST_1_WITH_SET).namespace(namespace).set(OVERRIDE_SET_NAME).bin("listField") + .indexType(IndexType.STRING).indexCollectionType(IndexCollectionType.LIST).build()) + ); + } + @Test public void createIndex_createsMapIndex() { template.createIndex(IndexedDocument.class, INDEX_TEST_1, "mapField", IndexType.STRING, @@ -124,6 +152,19 @@ public void createIndex_createsMapIndex() { }); } + @Test + public void createIndexWithSetName_createsMapIndex() { + template.createIndex(OVERRIDE_SET_NAME, INDEX_TEST_1_WITH_SET, "mapField", IndexType.STRING, + IndexCollectionType.MAPKEYS); + template.createIndex(OVERRIDE_SET_NAME, INDEX_TEST_2_WITH_SET, "mapField", IndexType.STRING, + IndexCollectionType.MAPVALUES); + + awaitTenSecondsUntil(() -> { + assertThat(template.indexExists(INDEX_TEST_1_WITH_SET)).isTrue(); + assertThat(template.indexExists(INDEX_TEST_2_WITH_SET)).isTrue(); + }); + } + @Test public void createIndex_createsIndexForDifferentTypes() { template.createIndex(IndexedDocument.class, INDEX_TEST_1, "mapField", IndexType.STRING); @@ -144,6 +185,15 @@ public void deleteIndex_doesNotThrowExceptionIfIndexDoesNotExist() { } } + // for Aerospike Server ver. >= 6.1.0.1 + @Test + public void deleteIndexWithSetName_doesNotThrowExceptionIfIndexDoesNotExist() { + if (ServerVersionUtils.isDropCreateBehaviorUpdated(client)) { + assertThatCode(() -> template.deleteIndex(OVERRIDE_SET_NAME, "not-existing-index")) + .doesNotThrowAnyException(); + } + } + // for Aerospike Server ver. >= 6.1.0.1 @Test public void createIndex_createsIndexOnNestedList() { diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateInsertTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateInsertTests.java index 90fc1d8ba..daad1929b 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateInsertTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateInsertTests.java @@ -80,6 +80,29 @@ public void insertsDocumentWithListMapDateStringLongValues() { template.delete(actual); // cleanup } + @Test + public void insertsDocumentWithListMapDateStringLongValuesAndSetName() { + Person customer = Person.builder() + .id(id) + .firstName("Dave") + .lastName("Grohl") + .age(45) + .waist(90) + .emailAddress("dave@gmail.com") + .stringMap(Collections.singletonMap("k", "v")) + .strings(Arrays.asList("a", "b", "c")) + .friend(new Person(null, "Anna", 43)) + .isActive(true) + .sex(Person.Sex.MALE) + .dateOfBirth(new Date()) + .build(); + template.insert(customer, OVERRIDE_SET_NAME); + + Person actual = template.findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(actual).isEqualTo(customer); + template.delete(actual, OVERRIDE_SET_NAME); // cleanup + } + @Test public void insertsAndFindsDocumentWithByteArrayField() { DocumentWithByteArray document = new DocumentWithByteArray(id, new byte[]{1, 0, 0, 1, 1, 1, 0, 0}); @@ -203,6 +226,30 @@ public void insertAll_insertsAllDocuments() { } } + @Test + public void insertAllWithSetName_insertsAllDocuments() { + List persons = IntStream.range(1, 10) + .mapToObj(age -> Person.builder().id(nextId()) + .firstName("Gregor") + .age(age).build()) + .collect(Collectors.toList()); + + // batch write operations are supported starting with Server version 6.0+ + if (ServerVersionUtils.isBatchWriteSupported(client)) { + template.insertAll(persons, OVERRIDE_SET_NAME); + } else { + persons.forEach(person -> template.insert(person, OVERRIDE_SET_NAME)); + } + + List result = template.findByIds(persons.stream().map(Person::getId) + .collect(Collectors.toList()), Person.class, OVERRIDE_SET_NAME); + + assertThat(result).hasSameElementsAs(persons); + for (Person person : result) { + template.delete(person, OVERRIDE_SET_NAME); // cleanup + } + } + @Test public void insertAll_rejectsDuplicateIds() { // batch write operations are supported starting with Server version 6.0+ diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplatePersistTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplatePersistTests.java index f62187316..45f5e898f 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplatePersistTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplatePersistTests.java @@ -41,6 +41,20 @@ public void shouldPersistWithCustomWritePolicy() { assertThat(actual).isEqualTo(initial); } + @Test + public void shouldPersistWithCustomWritePolicyWithSetName() { + CustomCollectionClass initial = new CustomCollectionClass(id, "data"); + + WritePolicy writePolicy = WritePolicyBuilder.builder(client.getWritePolicyDefault()) + .recordExistsAction(RecordExistsAction.CREATE_ONLY) + .build(); + + template.persist(initial, writePolicy, OVERRIDE_SET_NAME); + + CustomCollectionClass actual = template.findById(id, CustomCollectionClass.class, OVERRIDE_SET_NAME); + assertThat(actual).isEqualTo(initial); + } + @Test public void shouldNotPersistWithCustomWritePolicy() { CustomCollectionClass initial = new CustomCollectionClass(id, "data"); @@ -52,4 +66,16 @@ public void shouldNotPersistWithCustomWritePolicy() { assertThatThrownBy(() -> template.persist(initial, writePolicy)) .isInstanceOf(DataRetrievalFailureException.class); } + + @Test + public void shouldNotPersistWithCustomWritePolicyWithSetName() { + CustomCollectionClass initial = new CustomCollectionClass(id, "data"); + + WritePolicy writePolicy = WritePolicyBuilder.builder(client.getWritePolicyDefault()) + .recordExistsAction(RecordExistsAction.UPDATE_ONLY) + .build(); + + assertThatThrownBy(() -> template.persist(initial, writePolicy, OVERRIDE_SET_NAME)) + .isInstanceOf(DataRetrievalFailureException.class); + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplatePrependTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplatePrependTests.java index 44d20c624..dc3890e3e 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplatePrependTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplatePrependTests.java @@ -38,6 +38,18 @@ public void shouldPrepend() { template.delete(result); // cleanup } + @Test + public void shouldPrependWithSetName() { + Person one = Person.builder().id(id).firstName("tya").build(); + template.insert(one, OVERRIDE_SET_NAME); + Person appended = template.prepend(one, OVERRIDE_SET_NAME, "firstName", "Nas"); + + assertThat(appended.getFirstName()).isEqualTo("Nastya"); + Person result = template.findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(result.getFirstName()).isEqualTo("Nastya"); + template.delete(result, OVERRIDE_SET_NAME); // cleanup + } + @Test public void shouldPrependMultipleFields() { Person one = Person.builder().id(id).firstName("tya").emailAddress("gmail.com").build(); @@ -55,4 +67,22 @@ public void shouldPrependMultipleFields() { assertThat(actual.getEmailAddress()).isEqualTo("nastya@gmail.com"); template.delete(actual); } + + @Test + public void shouldPrependMultipleFieldsWithSetName() { + Person one = Person.builder().id(id).firstName("tya").emailAddress("gmail.com").build(); + template.insert(one, OVERRIDE_SET_NAME); + + Map toBeUpdated = new HashMap<>(); + toBeUpdated.put("firstName", "Nas"); + toBeUpdated.put("email", "nastya@"); + Person appended = template.prepend(one, OVERRIDE_SET_NAME, toBeUpdated); + + assertThat(appended.getFirstName()).isEqualTo("Nastya"); + assertThat(appended.getEmailAddress()).isEqualTo("nastya@gmail.com"); + Person actual = template.findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(actual.getFirstName()).isEqualTo("Nastya"); + assertThat(actual.getEmailAddress()).isEqualTo("nastya@gmail.com"); + template.delete(actual, OVERRIDE_SET_NAME); + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateSaveTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateSaveTests.java index 50a75652f..7a6b519c0 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateSaveTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateSaveTests.java @@ -48,12 +48,13 @@ public class AerospikeTemplateSaveTests extends BaseBlockingIntegrationTests { @AfterAll public void afterAll() { - template.delete(VersionedClass.class); - template.delete(SampleClasses.DocumentWithArray.class); - template.delete(SampleClasses.DocumentWithBigIntegerAndNestedArray.class); - template.delete(Person.class); - template.delete(CustomCollectionClass.class); - template.delete(DocumentWithTouchOnRead.class); + template.deleteAll(VersionedClass.class); + template.deleteAll(SampleClasses.DocumentWithArray.class); + template.deleteAll(SampleClasses.DocumentWithBigIntegerAndNestedArray.class); + template.deleteAll(Person.class); + template.deleteAll(CustomCollectionClass.class); + template.deleteAll(DocumentWithTouchOnRead.class); + template.deleteAll(OVERRIDE_SET_NAME); } // test for RecordExistsAction.REPLACE_ONLY policy @@ -341,6 +342,24 @@ public void shouldSaveAllAndSetVersion() { template.delete(second); // cleanup } + @Test + public void shouldSaveAllAndSetVersionWithSetName() { + VersionedClass first = new VersionedClass(id, "foo"); + VersionedClass second = new VersionedClass(nextId(), "foo"); + // batch write operations are supported starting with Server version 6.0+ + if (ServerVersionUtils.isBatchWriteSupported(client)) { + template.saveAll(List.of(first, second), OVERRIDE_SET_NAME); + } else { + List.of(first, second).forEach(person -> template.save(person, OVERRIDE_SET_NAME)); + } + + assertThat(first.version).isEqualTo(1); + assertThat(second.version).isEqualTo(1); + assertThat(template.findById(id, VersionedClass.class, OVERRIDE_SET_NAME).version).isEqualTo(1); + template.delete(first, OVERRIDE_SET_NAME); // cleanup + template.delete(second, OVERRIDE_SET_NAME); // cleanup + } + @Test public void shouldSaveAllVersionedDocumentsAndSetVersionAndThrowExceptionIfAlreadyExist() { // batch write operations are supported starting with Server version 6.0+ diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateUpdateTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateUpdateTests.java index 98fc172a6..7479512a8 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateUpdateTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateUpdateTests.java @@ -123,6 +123,26 @@ public void updateSpecificFieldsWithFieldAnnotatedProperty() { template.delete(template.findById(id, Person.class)); // cleanup } + @Test + public void updateSpecificFieldsWithFieldAnnotatedPropertyAndSetName() { + Person person = Person.builder().id(id).firstName("Andrew").lastName("Yo").age(40).waist(20) + .emailAddress("andrew@gmail.com").build(); + template.insert(person, OVERRIDE_SET_NAME); + + List fields = new ArrayList<>(); + fields.add("age"); + fields.add("emailAddress"); + template.update(Person.builder().id(id).age(41).emailAddress("andrew2@gmail.com").build(), OVERRIDE_SET_NAME, fields); + + assertThat(template.findById(id, Person.class, OVERRIDE_SET_NAME)).satisfies(doc -> { + assertThat(doc.getFirstName()).isEqualTo("Andrew"); + assertThat(doc.getAge()).isEqualTo(41); + assertThat(doc.getWaist()).isEqualTo(20); + assertThat(doc.getEmailAddress()).isEqualTo("andrew2@gmail.com"); + }); + template.delete(template.findById(id, Person.class, OVERRIDE_SET_NAME), OVERRIDE_SET_NAME); // cleanup + } + @Test public void updateSpecificFieldsWithFieldAnnotatedPropertyActualValue() { Person person = Person.builder().id(id).firstName("Andrew").lastName("Yo").age(40).waist(20) @@ -415,4 +435,24 @@ public void updateAllIfDocumentsNotChanged() { template.delete(result2); // cleanup } } + + @Test + public void updateAllIfDocumentsNotChangedWithSetName() { + // batch write operations are supported starting with Server version 6.0+ + if (ServerVersionUtils.isBatchWriteSupported(client)) { + int age1 = 140335200; + int age2 = 177652800; + Person person1 = new Person(id, "Wolfgang", age1); + Person person2 = new Person(nextId(), "Johann", age2); + template.insertAll(List.of(person1, person2), OVERRIDE_SET_NAME); + template.updateAll(List.of(person1, person2), OVERRIDE_SET_NAME); + + Person result1 = template.findById(person1.getId(), Person.class, OVERRIDE_SET_NAME); + Person result2 = template.findById(person2.getId(), Person.class, OVERRIDE_SET_NAME); + assertThat(result1.getAge()).isEqualTo(age1); + assertThat(result2.getAge()).isEqualTo(age2); + template.delete(result1, OVERRIDE_SET_NAME); // cleanup + template.delete(result2, OVERRIDE_SET_NAME); // cleanup + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateCompositeKeyTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateCompositeKeyTests.java index 621266da3..35963b61a 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateCompositeKeyTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateCompositeKeyTests.java @@ -45,7 +45,7 @@ public void findByIds() { @Test public void delete() { - StepVerifier.create(reactiveTemplate.delete(document.getId(), DocumentWithCompositeKey.class)) + StepVerifier.create(reactiveTemplate.deleteById(document.getId(), DocumentWithCompositeKey.class)) .expectNext(true) .verifyComplete(); } diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateDeleteRelatedTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateDeleteRelatedTests.java index 11fd71e38..99d135818 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateDeleteRelatedTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateDeleteRelatedTests.java @@ -69,7 +69,7 @@ public void simpleDeleteById() { StepVerifier.create(created).expectNext(person).verifyComplete(); // when - Mono deleted = reactiveTemplate.delete(id, Person.class).subscribeOn(Schedulers.parallel()); + Mono deleted = reactiveTemplate.deleteById(id, Person.class).subscribeOn(Schedulers.parallel()); StepVerifier.create(deleted).expectNext(true).verifyComplete(); // then @@ -94,10 +94,36 @@ public void simpleDeleteByObject() { StepVerifier.create(result).expectComplete().verify(); } + @Test + public void simpleDeleteByObjectWithSetName() { + // given + Person person = new Person(id, "QLastName", 21); + + Mono created = reactiveTemplate.insert(person, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()); + StepVerifier.create(created).expectNext(person).verifyComplete(); + + // when + Mono deleted = reactiveTemplate.delete(person, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()); + StepVerifier.create(deleted).expectNext(true).verifyComplete(); + + // then + Mono result = reactiveTemplate.findById(id, Person.class, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()); + StepVerifier.create(result).expectComplete().verify(); + } + @Test public void deleteById_shouldReturnFalseIfValueIsAbsent() { // when - Mono deleted = reactiveTemplate.delete(id, Person.class).subscribeOn(Schedulers.parallel()); + Mono deleted = reactiveTemplate.deleteById(id, Person.class).subscribeOn(Schedulers.parallel()); + + // then + StepVerifier.create(deleted).expectComplete().verify(); + } + + @Test + public void deleteByIdWithSetName_shouldReturnFalseIfValueIsAbsent() { + // when + Mono deleted = reactiveTemplate.deleteById(id, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()); // then StepVerifier.create(deleted).expectComplete().verify(); @@ -133,6 +159,25 @@ public void deleteAll_ShouldDeleteAllDocuments() { } } + @Test + public void deleteAllWithSetName_ShouldDeleteAllDocuments() { + // batch delete operations are supported starting with Server version 6.0+ + if (ServerVersionUtils.isBatchWriteSupported(reactorClient.getAerospikeClient())) { + String id1 = nextId(); + String id2 = nextId(); + reactiveTemplate.save(new SampleClasses.DocumentWithExpiration(id1), OVERRIDE_SET_NAME); + reactiveTemplate.save(new SampleClasses.DocumentWithExpiration(id2), OVERRIDE_SET_NAME); + + List ids = List.of(id1, id2); + reactiveTemplate.deleteByIds(ids, OVERRIDE_SET_NAME); + + List list = reactiveTemplate.findByIds(ids, + SampleClasses.DocumentWithExpiration.class, OVERRIDE_SET_NAME) + .subscribeOn(Schedulers.parallel()).collectList().block(); + assertThat(list).isEmpty(); + } + } + @Test public void deleteAllFromDifferentSets_ShouldDeleteAllDocuments() { // batch delete operations are supported starting with Server version 6.0+ diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindProjectionTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByIdProjectionTests.java similarity index 79% rename from src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindProjectionTests.java rename to src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByIdProjectionTests.java index 90780dfc5..dd483805a 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindProjectionTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByIdProjectionTests.java @@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class ReactiveAerospikeTemplateFindProjectionTests extends BaseReactiveIntegrationTests { +public class ReactiveAerospikeTemplateFindByIdProjectionTests extends BaseReactiveIntegrationTests { @Test public void findByIdWithProjection() { @@ -75,6 +75,35 @@ public void findByIdWithProjectionPersonWithMissingFields() { reactiveTemplate.delete(secondPerson).block(); //cleanup } + @Test + public void findByIdWithProjectionPersonWithMissingFieldsWithSetName() { + Person firstPerson = Person.builder() + .id(nextId()) + .firstName("first") + .lastName("lastName1") + .emailAddress("gmail.com") + .build(); + Person secondPerson = Person.builder() + .id(nextId()) + .firstName("second") + .lastName("lastName2") + .emailAddress("gmail.com") + .build(); + reactiveTemplate.save(firstPerson, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + reactiveTemplate.save(secondPerson, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + + PersonMissingAndRedundantFields result = reactiveTemplate.findById(firstPerson.getId(), Person.class, + PersonMissingAndRedundantFields.class, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + + assert result != null; + assertThat(result.getFirstName()).isEqualTo("first"); + assertThat(result.getLastName()).isEqualTo("lastName1"); + assertThat(result.getMissingField()).isNull(); + assertThat(result.getEmailAddress()).isNull(); // Not annotated with @Field("email"). + reactiveTemplate.delete(firstPerson, OVERRIDE_SET_NAME).block(); // cleanup + reactiveTemplate.delete(secondPerson, OVERRIDE_SET_NAME).block(); //cleanup + } + @Test public void findByIdWithProjectionPersonWithMissingFieldsIncludingTouchOnRead() { PersonTouchOnRead firstPerson = PersonTouchOnRead.builder() diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByIdTests.java similarity index 98% rename from src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindTests.java rename to src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByIdTests.java index 39aba5f21..2bdde407f 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByIdTests.java @@ -22,7 +22,7 @@ * * @author Igor Ermolenko */ -public class ReactiveAerospikeTemplateFindTests extends BaseReactiveIntegrationTests { +public class ReactiveAerospikeTemplateFindByIdTests extends BaseReactiveIntegrationTests { @Test public void findById_shouldReturnValueForExistingKey() { diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByQueryProjectionTest.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByQueryProjectionTest.java index 4031d4029..c71d6b8a9 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByQueryProjectionTest.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByQueryProjectionTest.java @@ -32,6 +32,12 @@ public void beforeAllSetUp() { Person.class, "person_last_name_index", "lastName", IndexType.STRING); additionalAerospikeTestOperations.createIndex( Person.class, "person_first_name_index", "firstName", IndexType.STRING); + additionalAerospikeTestOperations.createIndex( + OVERRIDE_SET_NAME, "person_age_index" + OVERRIDE_SET_NAME, "age", IndexType.NUMERIC); + additionalAerospikeTestOperations.createIndex( + OVERRIDE_SET_NAME, "person_last_name_index" + OVERRIDE_SET_NAME, "lastName", IndexType.STRING); + additionalAerospikeTestOperations.createIndex( + OVERRIDE_SET_NAME, "person_first_name_index" + OVERRIDE_SET_NAME, "firstName", IndexType.STRING); } @Override @@ -45,6 +51,9 @@ public void afterAll() { additionalAerospikeTestOperations.dropIndex(Person.class, "person_age_index"); additionalAerospikeTestOperations.dropIndex(Person.class, "person_last_name_index"); additionalAerospikeTestOperations.dropIndex(Person.class, "person_first_name_index"); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, "person_age_index" + OVERRIDE_SET_NAME); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, "person_last_name_index" + OVERRIDE_SET_NAME); + additionalAerospikeTestOperations.dropIndex(OVERRIDE_SET_NAME, "person_first_name_index"+ OVERRIDE_SET_NAME); } @Test @@ -62,6 +71,21 @@ public void findAll_findsAllExistingDocumentsProjection() { deleteAll(persons); // cleanup } + @Test + public void findAllWithSetName_findsAllExistingDocumentsProjection() { + List persons = IntStream.rangeClosed(1, 10) + .mapToObj(age -> Person.builder().id(nextId()).firstName("Dave").lastName("Matthews").age(age).build()) + .collect(Collectors.toList()); + reactiveTemplate.insertAll(persons, OVERRIDE_SET_NAME).blockLast(); + + List result = reactiveTemplate.findAll(PersonSomeFields.class, OVERRIDE_SET_NAME) + .subscribeOn(Schedulers.parallel()) + .collectList().block(); + assertThat(result) + .hasSameElementsAs(persons.stream().map(Person::toPersonSomeFields).collect(Collectors.toList())); + deleteAll(persons, OVERRIDE_SET_NAME); // cleanup + } + @Test public void findInRange_shouldFindLimitedNumberOfDocumentsProjection() { List allUsers = IntStream.range(20, 27) @@ -133,6 +157,27 @@ public void findByFilterRangeProjection() { deleteAll(allUsers); // cleanup } + @Test + public void findByFilterRangeProjectionWithSetName() { + List allUsers = IntStream.rangeClosed(21, 30) + .mapToObj(age -> Person.builder().id(nextId()).firstName("Dave" + age).lastName("Matthews").age(age) + .build()) + .collect(Collectors.toList()); + reactiveTemplate.insertAll(allUsers, OVERRIDE_SET_NAME).blockLast(); + + Query query = QueryUtils.createQueryForMethodWithArgs("findCustomerByAgeBetween", 25, 30); + + List actual = reactiveTemplate.find(query, PersonSomeFields.class, OVERRIDE_SET_NAME) + .subscribeOn(Schedulers.parallel()) + .collectList().block(); + + assertThat(actual) + .hasSize(6) + .containsExactlyInAnyOrderElementsOf( + allUsers.stream().map(Person::toPersonSomeFields).collect(Collectors.toList()).subList(4, 10)); + deleteAll(allUsers, OVERRIDE_SET_NAME); // cleanup + } + @Test public void findByFilterRangeNonExistingProjection() { Query query = QueryUtils.createQueryForMethodWithArgs("findCustomerByAgeBetween", 100, 150); diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByQueryTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByQueryTests.java index 1e0e0348b..864daf4d7 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByQueryTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateFindByQueryTests.java @@ -70,6 +70,21 @@ public void findAll_findAllExistingDocuments() { deleteAll(persons); // cleanup } + @Test + public void findAllWithSetName_findAllExistingDocuments() { + List persons = IntStream.rangeClosed(1, 10) + .mapToObj(age -> Person.builder().id(nextId()).firstName("Dave").lastName("Matthews").age(age).build()) + .collect(Collectors.toList()); + reactiveTemplate.insertAll(persons, OVERRIDE_SET_NAME).blockLast(); + + List result = reactiveTemplate.findAll(Person.class, OVERRIDE_SET_NAME) + .subscribeOn(Schedulers.parallel()) + .collectList().block(); + assertThat(result).hasSameElementsAs(persons); + + deleteAll(persons, OVERRIDE_SET_NAME); // cleanup + } + @Test public void findAll_findNothing() { List actual = reactiveTemplate.findAll(Person.class) @@ -112,6 +127,23 @@ public void findInRange_shouldFindLimitedNumberOfDocumentsAndSkip() { deleteAll(allUsers); // cleanup } + @Test + public void findInRangeWithSetName_shouldFindLimitedNumberOfDocumentsAndSkip() { + List allUsers = IntStream.range(20, 27) + .mapToObj(id -> new Person(nextId(), "Firstname", "Lastname")).collect(Collectors.toList()); + reactiveTemplate.insertAll(allUsers, OVERRIDE_SET_NAME).blockLast(); + + List actual = reactiveTemplate.findInRange(0, 5, Sort.unsorted(), Person.class, OVERRIDE_SET_NAME) + .subscribeOn(Schedulers.parallel()) + .collectList().block(); + + assertThat(actual) + .hasSize(5) + .containsAnyElementsOf(allUsers); + + deleteAll(allUsers, OVERRIDE_SET_NAME); // cleanup + } + @Test public void findInRange_shouldFindLimitedNumberOfDocumentsWithOrderBy() { List persons = new ArrayList<>(); @@ -230,6 +262,27 @@ public void findByFilterEqualOrderByDesc() { deleteAll(allUsers); // cleanup } + @Test + public void findByFilterEqualOrderByDescWithSetName() { + List allUsers = IntStream.rangeClosed(1, 10) + .mapToObj(id -> new Person(nextId(), "Dave" + id, "Matthews")).collect(Collectors.toList()); + Collections.shuffle(allUsers); // Shuffle user list + reactiveTemplate.insertAll(allUsers, OVERRIDE_SET_NAME).blockLast(); + allUsers.sort((o1, o2) -> o2.getFirstName() + .compareTo(o1.getFirstName())); // Order user list by firstname descending + + Query query = QueryUtils.createQueryForMethodWithArgs("findByLastNameOrderByFirstNameDesc", "Matthews"); + + List actual = reactiveTemplate.find(query, Person.class, OVERRIDE_SET_NAME) + .subscribeOn(Schedulers.parallel()) + .collectList().block(); + assertThat(actual) + .hasSize(10) + .containsExactlyElementsOf(allUsers); + + deleteAll(allUsers, OVERRIDE_SET_NAME); // cleanup + } + @Test public void findByFilterRange() { List allUsers = IntStream.rangeClosed(21, 30) diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateInsertTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateInsertTests.java index 9a2e9ace4..f9986fd71 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateInsertTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateInsertTests.java @@ -65,6 +65,32 @@ public void insertsDocumentWithListMapDateStringLongValues() { reactiveTemplate.delete(actual).block(); // cleanup } + @Test + public void insertsDocumentWithListMapDateStringLongValuesAndSetName() { + Person customer = Person.builder() + .id(id) + .firstName("Dave") + .lastName("Grohl") + .age(45) + .waist(90) + .emailAddress("dave@gmail.com") + .stringMap(Collections.singletonMap("k", "v")) + .strings(Arrays.asList("a", "b", "c")) + .friend(new Person(null, "Anna", 43)) + .isActive(true) + .sex(Person.Sex.MALE) + .dateOfBirth(new Date()) + .build(); + + StepVerifier.create(reactiveTemplate.insert(customer, OVERRIDE_SET_NAME)) + .expectNext(customer) + .verifyComplete(); + + Person actual = findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(actual).isEqualTo(customer); + reactiveTemplate.delete(actual, OVERRIDE_SET_NAME).block(); // cleanup + } + @Test public void insertsAndFindsDocumentWithByteArrayField() { DocumentWithByteArray document = new DocumentWithByteArray(id, new byte[]{1, 0, 0, 1, 1, 1, 0, 0}); @@ -103,6 +129,16 @@ public void insertsDocumentWithVersionGreaterThanZeroIfThereIsNoDocumentWithSame reactiveTemplate.delete(findById(id, VersionedClass.class)).block(); // cleanup } + @Test + public void insertsDocumentWithVersionGreaterThanZeroIfThereIsNoDocumentWithSameKeyAndSetName() { + VersionedClass document = new VersionedClass(id, "any", 5L); + reactiveTemplate.insert(document, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + + assertThat(document.getVersion()).isEqualTo(1); + reactiveTemplate.delete(findById(id, VersionedClass.class, OVERRIDE_SET_NAME), OVERRIDE_SET_NAME).block(); // cleanup + } + + @Test public void throwsExceptionForDuplicateId() { Person person = new Person(id, "Amol", 28); @@ -114,6 +150,17 @@ public void throwsExceptionForDuplicateId() { reactiveTemplate.delete(findById(id, Person.class)).block(); // cleanup } + @Test + public void throwsExceptionForDuplicateIdAndSetName() { + Person person = new Person(id, "Amol", 28); + + reactiveTemplate.insert(person, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + StepVerifier.create(reactiveTemplate.insert(person, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel())) + .expectError(DuplicateKeyException.class) + .verify(); + reactiveTemplate.delete(findById(id, Person.class, OVERRIDE_SET_NAME), OVERRIDE_SET_NAME).block(); // cleanup + } + @Test public void throwsExceptionForDuplicateIdForVersionedDocument() { VersionedClass document = new VersionedClass(id, "any", 5L); @@ -185,6 +232,22 @@ public void insertAll_shouldInsertAllDocuments() { } } + @Test + public void insertAllWithSetName_shouldInsertAllDocuments() { + if (ServerVersionUtils.isBatchWriteSupported(reactorClient.getAerospikeClient())) { + Person customer1 = new Person(nextId(), "Dave"); + Person customer2 = new Person(nextId(), "James"); + reactiveTemplate.insertAll(List.of(customer1, customer2), OVERRIDE_SET_NAME).blockLast(); + + Person result1 = findById(customer1.getId(), Person.class, OVERRIDE_SET_NAME); + Person result2 = findById(customer2.getId(), Person.class, OVERRIDE_SET_NAME); + assertThat(result1).isEqualTo(customer1); + assertThat(result2).isEqualTo(customer2); + reactiveTemplate.delete(result1, OVERRIDE_SET_NAME).block(); // cleanup + reactiveTemplate.delete(result2, OVERRIDE_SET_NAME).block(); // cleanup + } + } + @Test public void insertAll_rejectsDuplicateId() { if (ServerVersionUtils.isBatchWriteSupported(reactorClient.getAerospikeClient())) { @@ -197,4 +260,17 @@ public void insertAll_rejectsDuplicateId() { reactiveTemplate.delete(findById(id, Person.class)).block(); // cleanup } } + + @Test + public void insertAllWithSetName_rejectsDuplicateId() { + if (ServerVersionUtils.isBatchWriteSupported(reactorClient.getAerospikeClient())) { + Person person = new Person(id, "Amol"); + person.setAge(28); + + StepVerifier.create(reactiveTemplate.insertAll(List.of(person, person), OVERRIDE_SET_NAME)) + .expectError(AerospikeException.BatchRecordArray.class) + .verify(); + reactiveTemplate.delete(findById(id, Person.class, OVERRIDE_SET_NAME), OVERRIDE_SET_NAME).block(); // cleanup + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateMiscTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateMiscTests.java index bf4926ea5..c93f98fb8 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateMiscTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateMiscTests.java @@ -49,10 +49,28 @@ public void exists_shouldReturnTrueIfValueIsPresent() { reactiveTemplate.delete(one).block(); } + @Test + public void existsWithSetName_shouldReturnTrueIfValueIsPresent() { + Person one = Person.builder().id(id).firstName("tya").emailAddress("gmail.com").build(); + reactiveTemplate.insert(one, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + + StepVerifier.create(reactiveTemplate.exists(id, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel())) + .expectNext(true) + .verifyComplete(); + reactiveTemplate.delete(one, OVERRIDE_SET_NAME).block(); + } + @Test public void exists_shouldReturnFalseIfValueIsAbsent() { StepVerifier.create(reactiveTemplate.exists(id, Person.class).subscribeOn(Schedulers.parallel())) .expectNext(false) .verifyComplete(); } + + @Test + public void existsWithSetName_shouldReturnFalseIfValueIsAbsent() { + StepVerifier.create(reactiveTemplate.exists(id, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel())) + .expectNext(false) + .verifyComplete(); + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateModificationRelatedTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateModificationRelatedTests.java index 463bf9388..6c6269bf7 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateModificationRelatedTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateModificationRelatedTests.java @@ -56,6 +56,26 @@ public void shouldAppend() { reactiveTemplate.delete(storedPerson.block()).block(); } + @Test + public void shouldAppendWithSetName() { + // given + Person one = Person.builder().id(id).firstName("Nas").build(); + Mono created = reactiveTemplate.insert(one, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()); + StepVerifier.create(created).expectNext(one).verifyComplete(); + + // when + Mono appended = reactiveTemplate.append(one, OVERRIDE_SET_NAME, "firstName", "tya") + .subscribeOn(Schedulers.parallel()); + + // then + Person expected = Person.builder().id(id).firstName("Nastya").build(); + StepVerifier.create(appended).expectNext(expected).verifyComplete(); + + Mono storedPerson = reactiveTemplate.findById(id, Person.class, OVERRIDE_SET_NAME); + StepVerifier.create(storedPerson).expectNext(expected).verifyComplete(); + reactiveTemplate.delete(storedPerson.block(), OVERRIDE_SET_NAME).block(); + } + @Test public void shouldAppendMultipleFields() { // given diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateSaveRelatedTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateSaveRelatedTests.java index 554a4c93a..db41efe72 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateSaveRelatedTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateSaveRelatedTests.java @@ -40,6 +40,15 @@ public void save_shouldSaveAndSetVersion() { assertThat(findById(id, VersionedClass.class).version).isEqualTo(1); } + @Test + public void saveWithSetName_shouldSaveAndSetVersion() { + VersionedClass first = new VersionedClass(id, "foo"); + reactiveTemplate.save(first, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + + assertThat(first.version).isEqualTo(1); + assertThat(findById(id, VersionedClass.class, OVERRIDE_SET_NAME).version).isEqualTo(1); + } + @Test public void save_shouldNotSaveDocumentIfItAlreadyExistsWithZeroVersion() { reactiveTemplate.save(new VersionedClass(id, "foo", 0L)) @@ -74,6 +83,13 @@ public void save_shouldUpdateNullField() { reactiveTemplate.save(saved).subscribeOn(Schedulers.parallel()).block(); } + @Test + public void saveWithSetName_shouldUpdateNullField() { + VersionedClass versionedClass = new VersionedClass(id, null); + VersionedClass saved = reactiveTemplate.save(versionedClass, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + reactiveTemplate.save(saved, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + } + @Test public void save_shouldUpdateNullFieldForClassWithVersionField() { VersionedClass versionedClass = new VersionedClass(id, "field"); @@ -102,6 +118,21 @@ public void save_shouldUpdateNullFieldForClassWithoutVersionField() { reactiveTemplate.delete(result).block(); // cleanup } + @Test + public void saveWithSetName_shouldUpdateNullFieldForClassWithoutVersionField() { + Person person = new Person(id, "Oliver"); + reactiveTemplate.save(person, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + + assertThat(findById(id, Person.class, OVERRIDE_SET_NAME).getFirstName()).isEqualTo("Oliver"); + + person.setFirstName(null); + reactiveTemplate.save(person, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); + + Person result = findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(result.getFirstName()).isNull(); + reactiveTemplate.delete(result, OVERRIDE_SET_NAME).block(); // cleanup + } + @Test public void save_shouldUpdateExistingDocument() { VersionedClass one = new VersionedClass(id, "foo"); @@ -238,6 +269,23 @@ public void saveAll_shouldSaveAllDocuments() { } } + @Test + public void saveAllWithSetName_shouldSaveAllDocuments() { + // batch delete operations are supported starting with Server version 6.0+ + if (ServerVersionUtils.isBatchWriteSupported(reactorClient.getAerospikeClient())) { + Person customer1 = new Person(nextId(), "Dave"); + Person customer2 = new Person(nextId(), "James"); + reactiveTemplate.saveAll(List.of(customer1, customer2), OVERRIDE_SET_NAME).blockLast(); + + Person result1 = findById(customer1.getId(), Person.class, OVERRIDE_SET_NAME); + Person result2 = findById(customer2.getId(), Person.class, OVERRIDE_SET_NAME); + assertThat(result1).isEqualTo(customer1); + assertThat(result2).isEqualTo(customer2); + reactiveTemplate.delete(result1, OVERRIDE_SET_NAME).block(); // cleanup + reactiveTemplate.delete(result2, OVERRIDE_SET_NAME).block(); // cleanup + } + } + @Test public void saveAll_rejectsDuplicateId() { // batch delete operations are supported starting with Server version 6.0+ @@ -250,4 +298,17 @@ public void saveAll_rejectsDuplicateId() { reactiveTemplate.delete(findById(id, VersionedClass.class)).block(); // cleanup } } + + @Test + public void saveAllWithSetName_rejectsDuplicateId() { + // batch delete operations are supported starting with Server version 6.0+ + if (ServerVersionUtils.isBatchWriteSupported(reactorClient.getAerospikeClient())) { + VersionedClass first = new VersionedClass(id, "foo"); + + StepVerifier.create(reactiveTemplate.saveAll(List.of(first, first), OVERRIDE_SET_NAME)) + .expectError(AerospikeException.BatchRecordArray.class) + .verify(); + reactiveTemplate.delete(findById(id, VersionedClass.class, OVERRIDE_SET_NAME), OVERRIDE_SET_NAME).block(); // cleanup + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateUpdateTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateUpdateTests.java index ddaf29508..e06ce9c19 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateUpdateTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateUpdateTests.java @@ -220,6 +220,21 @@ public void setsVersionEqualToNumberOfModifications() { reactiveTemplate.delete(actual).block(); // cleanup } + @Test + public void setsVersionEqualToNumberOfModificationsWithSetName() { + VersionedClass document = new VersionedClass(id, "foobar"); + reactiveTemplate.insert(document, OVERRIDE_SET_NAME).block(); + reactiveTemplate.update(document, OVERRIDE_SET_NAME).block(); + reactiveTemplate.update(document, OVERRIDE_SET_NAME).block(); + + StepVerifier.create(reactorClient.get(new Policy(), new Key(getNameSpace(), OVERRIDE_SET_NAME, id))) + .assertNext(keyRecord -> assertThat(keyRecord.record.generation).isEqualTo(3)) + .verifyComplete(); + VersionedClass actual = findById(id, VersionedClass.class, OVERRIDE_SET_NAME); + assertThat(actual.version).isEqualTo(3); + reactiveTemplate.delete(actual, OVERRIDE_SET_NAME).block(); // cleanup + } + @Test public void onlyFirstUpdateSucceedsAndNextAttemptsShouldFailWithOptimisticLockingFailureExceptionForVersionedDocument() { VersionedClass document = new VersionedClass(id, "foobar"); @@ -329,6 +344,39 @@ public void TestAddToMapSpecifyingMapFieldOnly() { reactiveTemplate.delete(findById(id, Person.class)).block(); // cleanup } + @Test + public void TestAddToMapSpecifyingMapFieldOnlyWithSetName() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", "value2"); + map.put("key3", "value3"); + List list = new ArrayList<>(); + list.add("string1"); + list.add("string2"); + list.add("string3"); + Person person = Person.builder().id(id).firstName("QLastName").age(50) + .stringMap(map) + .strings(list) + .build(); + reactiveTemplate.insert(person, OVERRIDE_SET_NAME).block(); + + Person personWithList = Person.builder().id(id).firstName("QLastName").age(50) + .stringMap(map) + .strings(list) + .build(); + personWithList.getStringMap().put("key4", "Added something new"); + + List fields = new ArrayList<>(); + fields.add("stringMap"); + reactiveTemplate.update(personWithList, OVERRIDE_SET_NAME, fields).block(); + + Person personWithList2 = findById(id, Person.class, OVERRIDE_SET_NAME); + assertThat(personWithList2).isEqualTo(personWithList); + assertThat(personWithList2.getStringMap()).hasSize(4); + assertThat(personWithList2.getStringMap().get("key4")).isEqualTo("Added something new"); + reactiveTemplate.delete(findById(id, Person.class, OVERRIDE_SET_NAME), OVERRIDE_SET_NAME).block(); // cleanup + } + @Test public void updateAllShouldThrowExceptionOnUpdateForNonExistingKey() { // batch write operations are supported starting with Server version 6.0+ diff --git a/src/test/java/org/springframework/data/aerospike/repository/support/SimpleAerospikeRepositoryTest.java b/src/test/java/org/springframework/data/aerospike/repository/support/SimpleAerospikeRepositoryTest.java index ba8fb419d..1e8f57039 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/support/SimpleAerospikeRepositoryTest.java +++ b/src/test/java/org/springframework/data/aerospike/repository/support/SimpleAerospikeRepositoryTest.java @@ -169,7 +169,7 @@ public void findAllIterableOfID() { public void deleteID() { aerospikeRepository.deleteById("one"); - verify(operations).delete("one", Person.class); + verify(operations).deleteById("one", Person.class); } @Test @@ -193,7 +193,7 @@ public void deleteIterableOfQExtendsT() { public void deleteAll() { aerospikeRepository.deleteAll(); - verify(operations).delete(Person.class); + verify(operations).deleteAll(Person.class); } @Test diff --git a/src/test/java/org/springframework/data/aerospike/repository/support/SimpleReactiveAerospikeRepositoryTest.java b/src/test/java/org/springframework/data/aerospike/repository/support/SimpleReactiveAerospikeRepositoryTest.java index 80d35cb5f..a51f8c1d4 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/support/SimpleReactiveAerospikeRepositoryTest.java +++ b/src/test/java/org/springframework/data/aerospike/repository/support/SimpleReactiveAerospikeRepositoryTest.java @@ -178,19 +178,19 @@ public void testExistsByIdPublisher() { @Test public void testDeleteById() { when(metadata.getJavaType()).thenReturn(Customer.class); - when(operations.delete("77", Customer.class)).thenReturn(Mono.just(true)); + when(operations.deleteById("77", Customer.class)).thenReturn(Mono.just(true)); repository.deleteById("77").block(); - verify(operations, times(1)).delete("77", Customer.class); + verify(operations, times(1)).deleteById("77", Customer.class); } @Test public void testDeleteByIdPublisher() { when(metadata.getJavaType()).thenReturn(Customer.class); - when(operations.delete("77", Customer.class)).thenReturn(Mono.just(true)); + when(operations.deleteById("77", Customer.class)).thenReturn(Mono.just(true)); repository.deleteById(Flux.just("77", "88", "99")).block(); - verify(operations, times(1)).delete("77", Customer.class); + verify(operations, times(1)).deleteById("77", Customer.class); } @Test diff --git a/src/test/java/org/springframework/data/aerospike/utility/AdditionalAerospikeTestOperations.java b/src/test/java/org/springframework/data/aerospike/utility/AdditionalAerospikeTestOperations.java index 61e699e20..53d73fa42 100644 --- a/src/test/java/org/springframework/data/aerospike/utility/AdditionalAerospikeTestOperations.java +++ b/src/test/java/org/springframework/data/aerospike/utility/AdditionalAerospikeTestOperations.java @@ -95,12 +95,23 @@ public void deleteAllAndVerify(Class... entityClasses) { Arrays.asList(entityClasses).forEach(this::awaitUntilSetIsEmpty); } + public void deleteAllAndVerify(Class entityClass, String setName) { + truncateSet(setName); + awaitUntilSetIsEmpty(entityClass, setName); + } + private void awaitUntilSetIsEmpty(Class entityClass) { Awaitility.await() .atMost(Duration.ofSeconds(10)) .until(() -> isEntityClassSetEmpty(entityClass)); } + private void awaitUntilSetIsEmpty(Class entityClass, String setName) { + Awaitility.await() + .atMost(Duration.ofSeconds(10)) + .until(() -> isSetEmpty(entityClass, setName)); + } + public void createIndex(Class entityClass, String indexName, String binName, IndexType indexType) { createIndex(entityClass, indexName, binName, indexType, null, (CTX[]) null); @@ -227,6 +238,10 @@ public void addNewFieldToSavedDataInAerospike(Key key) { protected abstract void truncateSetOfEntityClass(Class clazz); + protected abstract boolean isSetEmpty(Class clazz, String setName); + + protected abstract void truncateSet(String setName); + protected abstract String getNamespace(); protected abstract String getSetName(Class clazz); diff --git a/src/test/java/org/springframework/data/aerospike/utility/QueryUtils.java b/src/test/java/org/springframework/data/aerospike/utility/QueryUtils.java index c8b19f069..f22fecb2c 100644 --- a/src/test/java/org/springframework/data/aerospike/utility/QueryUtils.java +++ b/src/test/java/org/springframework/data/aerospike/utility/QueryUtils.java @@ -38,7 +38,7 @@ private static Class unwrap(Class c) { return c.isPrimitive() ? (Class) WRAPPERS_TO_PRIMITIVES.get(c) : c; } - public static Query createQueryForMethodWithArgs(String methodName, Object... args) { + public static Query createQueryForMethodWithArgs(String methodName, Object... args) { //noinspection rawtypes Class[] argTypes = Stream.of(args).map(Object::getClass).toArray(Class[]::new); Method method = ReflectionUtils.findMethod(PersonRepository.class, methodName, argTypes);