From dd1e1bdee9447732d57f75ea2dbdf257dfc28644 Mon Sep 17 00:00:00 2001 From: agrgr Date: Tue, 7 Nov 2023 17:18:47 +0200 Subject: [PATCH] align public "find" APIs to use Query instead of Qualifiers --- .../aerospike/core/AerospikeOperations.java | 61 ++++++++-------- .../aerospike/core/AerospikeTemplate.java | 63 ++++++++--------- .../core/ReactiveAerospikeOperations.java | 69 +++++++++++++++---- .../core/ReactiveAerospikeTemplate.java | 49 +++++++++---- .../data/aerospike/core/TemplateUtils.java | 24 +++++++ .../data/aerospike/query/Qualifier.java | 4 +- .../data/aerospike/query/QualifierUtils.java | 16 +++++ .../query/AerospikePartTreeQuery.java | 24 +++---- .../query/BaseAerospikePartTreeQuery.java | 15 +--- .../query/ReactiveAerospikePartTreeQuery.java | 28 ++++---- .../AerospikeTemplateFindByQueryTests.java | 8 +-- .../PersonRepositoryQueryTests.java | 49 +++++++++---- .../aerospike/sample/PersonRepository.java | 28 +++++++- 13 files changed, 284 insertions(+), 154 deletions(-) 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 4ec19c688..6a91c72a6 100644 --- a/src/main/java/org/springframework/data/aerospike/core/AerospikeOperations.java +++ b/src/main/java/org/springframework/data/aerospike/core/AerospikeOperations.java @@ -712,11 +712,11 @@ public interface AerospikeOperations { * @param id The id of the document 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. - * @param qualifiers {@link Qualifier}s provided to build a filter Expression for the query. Optional argument. + * @param query {@link Query} provided to build a filter expression. Optional argument. * @return The document from Aerospike, returned document will be mapped to targetClass's type. */ - Object findByIdUsingQualifiers(Object id, Class entityClass, Class targetClass, - Qualifier... qualifiers); + Object findByIdUsingQuery(Object id, Class entityClass, Class targetClass, + Query query); /** * Find document by providing id with a given set name. @@ -728,11 +728,11 @@ Object findByIdUsingQualifiers(Object id, Class entityClass, Class * {@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. + * @param query {@link Query} provided to build a filter expression. Optional argument. * @return The document from Aerospike, returned document will be mapped to targetClass's type. */ - Object findByIdUsingQualifiers(Object id, Class entityClass, Class targetClass, String setName, - Qualifier... qualifiers); + Object findByIdUsingQuery(Object id, Class entityClass, Class targetClass, String setName, + Query query); /** * Find documents by providing multiple ids, set name will be determined by the given entityClass. @@ -742,12 +742,11 @@ Object findByIdUsingQualifiers(Object id, Class entityClass, Class * @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. - * @param qualifiers {@link Qualifier}s provided to build a filter Expression for the query. Optional argument. + * @param query {@link Query} provided to build a filter expression. 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 findByIdsUsingQualifiers(Collection ids, Class entityClass, Class targetClass, - Qualifier... qualifiers); + List findByIdsUsingQuery(Collection ids, Class entityClass, Class targetClass, Query query); /** * Find documents by providing multiple ids with a given set name. @@ -759,12 +758,12 @@ List findByIdsUsingQualifiers(Collection ids, Class entityClass, * {@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. + * @param query {@link Query} provided to build a filter expression. 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 findByIdsUsingQualifiers(Collection ids, Class entityClass, Class targetClass, - String setName, Qualifier... qualifiers); + List findByIdsUsingQuery(Collection ids, Class entityClass, Class targetClass, String setName, + Query query); /** * Find documents in the given entityClass's set using a query and map them to the given class type. @@ -797,49 +796,47 @@ List findByIdsUsingQualifiers(Collection ids, Class entityClass, Stream find(Query query, Class targetClass, String setName); /** - * Find all documents in the given entityClass's set using provided {@link Qualifier}. + * Find all documents in the given entityClass's set using provided {@link Query}. * + * @param query Query to build filter expression from. Constructed using a {@link Qualifier} that 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. * @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 - * {@link Qualifier#getExcludeFilter()} == false, secondary index filter is built based on the - * first processed qualifier. * @return Stream of entities. */ - Stream findUsingQualifier(Class entityClass, @Nullable Filter filter, Qualifier qualifier); + Stream find(Query query, Class entityClass, @Nullable Filter filter); /** - * Find all documents in the given entityClass's set using provided {@link Qualifier}. + * Find all documents in the given entityClass's set using provided {@link Query}. * + * @param query Query to build filter expression from. Constructed using a {@link Qualifier} that 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. * @param entityClass The class to extract the Aerospike set from and to map the entity to. Must not be * {@literal null}. * @param targetClass The class 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 - * {@link Qualifier#getExcludeFilter()} == false, secondary index filter is built based on the - * first processed qualifier. * @return Stream of entities. */ - Stream findUsingQualifier(Class entityClass, Class targetClass, @Nullable Filter filter, - Qualifier qualifier); + Stream find(Query query, Class entityClass, Class targetClass, @Nullable Filter filter); /** - * Find all documents in the given set using provided {@link Qualifier}. + * Find all documents in the given set using provided {@link Query}. * + * @param query Query to build filter expression from. Constructed using a {@link Qualifier} that 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. * @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 findUsingQualifier(Class targetClass, String setName, @Nullable Filter filter, - Qualifier qualifier); + Stream find(Query query, Class targetClass, String setName, @Nullable Filter filter); /** * Find all documents in the given entityClass's set and map them to the given class type. 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 eb110fbe5..69acce004 100644 --- a/src/main/java/org/springframework/data/aerospike/core/AerospikeTemplate.java +++ b/src/main/java/org/springframework/data/aerospike/core/AerospikeTemplate.java @@ -71,6 +71,7 @@ import static org.springframework.data.aerospike.core.CoreUtils.verifyUnsortedWithOffset; import static org.springframework.data.aerospike.core.TemplateUtils.excludeIdQualifier; import static org.springframework.data.aerospike.core.TemplateUtils.getIdValue; +import static org.springframework.data.aerospike.core.TemplateUtils.queryCriteriaIsNotNull; import static org.springframework.data.aerospike.query.QualifierUtils.getOneIdQualifier; import static org.springframework.data.aerospike.query.QualifierUtils.validateQualifiers; import static org.springframework.data.aerospike.utility.Utils.allArrayElementsAreNull; @@ -664,7 +665,7 @@ public S findById(Object id, Class entityClass, Class targetClass) 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) findByIdUsingQualifiers(id, entityClass, targetClass, setName); + return (S) findByIdUsingQuery(id, entityClass, targetClass, setName, null); } private Record getRecord(AerospikePersistentEntity entity, Key key, Qualifier... qualifiers) { @@ -779,7 +780,7 @@ public List findByIds(Iterable ids, Class entityClass, Class Assert.notNull(entityClass, "Entity class must not be null!"); Assert.notNull(setName, "Set name must not be null!"); - return (List) findByIdsUsingQualifiers(IterableConverter.toList(ids), entityClass, targetClass, setName); + return (List) findByIdsUsingQuery(IterableConverter.toList(ids), entityClass, targetClass, setName, null); } @Override @@ -801,44 +802,44 @@ private GroupedEntities findGroupedEntitiesByGroupedKeys(GroupedKeys groupedKeys } @Override - public Object findByIdUsingQualifiers(Object id, Class entityClass, Class targetClass, - Qualifier... qualifiers) { + public Object findByIdUsingQuery(Object id, Class entityClass, Class targetClass, + Query query) { Assert.notNull(id, "Id must not be null!"); Assert.notNull(entityClass, "Class must not be null!"); - return findByIdUsingQualifiers(id, entityClass, targetClass, getSetName(entityClass), qualifiers); + return findByIdUsingQuery(id, entityClass, targetClass, getSetName(entityClass), query); } @Override - public Object findByIdUsingQualifiers(Object id, Class entityClass, Class targetClass, String setName, - Qualifier... qualifiers) { + public Object findByIdUsingQuery(Object id, Class entityClass, Class targetClass, String setName, + Query query) { 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!"); + Qualifier criteria = queryCriteriaIsNotNull(query) ? query.getCriteria().getCriteriaObject() : null; try { AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); Key key = getKey(id, setName); if (targetClass != null && targetClass != entityClass) { - return getRecordMapToTargetClass(entity, key, targetClass, qualifiers); + return getRecordMapToTargetClass(entity, key, targetClass, criteria); } - return mapToEntity(key, entityClass, getRecord(entity, key, qualifiers)); + return mapToEntity(key, entityClass, getRecord(entity, key, criteria)); } catch (AerospikeException e) { throw translateError(e); } } @Override - public List findByIdsUsingQualifiers(Collection ids, Class entityClass, Class targetClass, - Qualifier... qualifiers) { - Assert.notNull(entityClass, "Class must not be null!"); - return findByIdsUsingQualifiers(ids, entityClass, targetClass, getSetName(entityClass), qualifiers); + public List findByIdsUsingQuery(Collection ids, Class entityClass, Class targetClass, + Query query) { + return findByIdsUsingQuery(ids, entityClass, targetClass, getSetName(entityClass), query); } @Override - public List findByIdsUsingQualifiers(Collection ids, Class entityClass, Class targetClass, - String setName, Qualifier... qualifiers) { - Assert.notNull(ids, "List of ids must not be null!"); + public List findByIdsUsingQuery(Collection ids, Class entityClass, Class targetClass, + String setName, Query query) { + Assert.notNull(ids, "Ids must not be null!"); Assert.notNull(entityClass, "Entity class must not be null!"); Assert.notNull(setName, "Set name must not be null!"); @@ -851,6 +852,8 @@ public List findByIdsUsingQualifiers(Collection ids, Class entit .map(id -> getKey(id, setName)) .toArray(Key[]::new); + Qualifier[] qualifiers = queryCriteriaIsNotNull(query) && query.getCriteria() != null ? + new Qualifier[]{query.getCriteria().getCriteriaObject()} : null; BatchPolicy policy = getBatchPolicyFilterExp(qualifiers); Class target; @@ -893,22 +896,20 @@ public Stream find(Query query, Class targetClass, String setName) { } @Override - public Stream findUsingQualifier(Class entityClass, Filter filter, - Qualifier qualifier) { - return findUsingQualifier(entityClass, getSetName(entityClass), filter, qualifier); + public Stream find(Query query, Class entityClass, Filter filter) { + return find(query, entityClass, getSetName(entityClass), filter); } @Override - public Stream findUsingQualifier(Class entityClass, Class targetClass, Filter filter, - Qualifier qualifier) { - return findRecordsUsingQualifiers(getSetName(entityClass), targetClass, filter, qualifier) + public Stream find(Query query, Class entityClass, Class targetClass, Filter filter) { + return findRecordsUsingQualifiers(getSetName(entityClass), targetClass, filter) .map(keyRecord -> mapToEntity(keyRecord, targetClass)); } @Override - public Stream findUsingQualifier(Class targetClass, String setName, Filter filter, - Qualifier qualifier) { - return findRecordsUsingQualifiers(setName, targetClass, filter, qualifier) + public Stream find(Query query, Class targetClass, String setName, Filter filter) { + Qualifier criteria = queryCriteriaIsNotNull(query) ? query.getCriteria().getCriteriaObject() : null; + return findRecordsUsingQualifiers(setName, targetClass, filter, criteria) .map(keyRecord -> mapToEntity(keyRecord, targetClass)); } @@ -932,7 +933,7 @@ 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 findUsingQualifier(targetClass, setName, null, null); + return find(null, targetClass, setName, null); } @Override @@ -956,16 +957,15 @@ public Stream findAll(Sort sort, long offset, long limit, Class target private Stream findUsingQueryWithPostProcessing(String setName, Class targetClass, Query query) { verifyUnsortedWithOffset(query.getSort(), query.getOffset()); - Qualifier qualifier = query.getCriteria().getCriteriaObject(); Stream results = findUsingQualifiersWithDistinctPredicate(setName, targetClass, - getDistinctPredicate(query), qualifier); + getDistinctPredicate(query), query); return applyPostProcessingOnResults(results, query); } private Stream findUsingQualifiersWithDistinctPredicate(String setName, Class targetClass, Predicate distinctPredicate, - Qualifier... qualifiers) { - return findRecordsUsingQualifiers(setName, targetClass, null, qualifiers) + Query query) { + return findRecordsUsingQualifiers(setName, targetClass, null, query.getCriteria().getCriteriaObject()) .filter(distinctPredicate) .map(keyRecord -> mapToEntity(keyRecord, targetClass)); } @@ -1258,7 +1258,8 @@ private Stream findUsingQualifierWithPostProcessing(String setName, Class long offset, long limit, Filter filter, Qualifier qualifier) { verifyUnsortedWithOffset(sort, offset); - Stream results = findUsingQualifier(targetClass, setName, filter, qualifier); + Query query = qualifier != null ? new Query(qualifier) : null; + Stream results = find(query, targetClass, setName, filter); return applyPostProcessingOnResults(results, sort, offset, limit); } 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 925dbfd8d..3d7e8cc0a 100644 --- a/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeOperations.java +++ b/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeOperations.java @@ -18,6 +18,7 @@ import com.aerospike.client.AerospikeException; import com.aerospike.client.cdt.CTX; import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.query.Filter; import com.aerospike.client.query.IndexCollectionType; import com.aerospike.client.query.IndexType; import com.aerospike.client.reactor.IAerospikeReactorClient; @@ -27,6 +28,7 @@ import org.springframework.data.aerospike.repository.query.Query; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.context.MappingContext; +import org.springframework.lang.Nullable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -679,11 +681,11 @@ public interface ReactiveAerospikeOperations { * @param id The id of the document 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. - * @param qualifiers {@link Qualifier}s provided to build a filter Expression for the query. Optional argument. + * @param query {@link Query} provided to build a filter expression. Optional argument. * @return The document from Aerospike, returned document will be mapped to targetClass's type. */ - Mono findByIdUsingQualifiers(Object id, Class entityClass, Class targetClass, - Qualifier... qualifiers); + Mono findByIdUsingQuery(Object id, Class entityClass, Class targetClass, + Query query); /** * Find document by providing id with a given set name. @@ -695,11 +697,11 @@ Mono findByIdUsingQualifiers(Object id, Class entityClass, Class * {@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. + * @param query {@link Query} provided to build a filter expression. Optional argument. * @return The document from Aerospike, returned document will be mapped to targetClass's type. */ - Mono findByIdUsingQualifiers(Object id, Class entityClass, Class targetClass, String setName, - Qualifier... qualifiers); + Mono findByIdUsingQuery(Object id, Class entityClass, Class targetClass, String setName, + Query query); /** * Find documents by providing multiple ids, set name will be determined by the given entityClass. @@ -709,12 +711,11 @@ Mono findByIdUsingQualifiers(Object id, Class entityClass, Class * @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. - * @param qualifiers {@link Qualifier}s provided to build a filter Expression for the query. Optional argument. + * @param query {@link Query} provided to build a filter expression. 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 findByIdsUsingQualifiers(Collection ids, Class entityClass, Class targetClass, - Qualifier... qualifiers); + Flux findByIdsUsingQuery(Collection ids, Class entityClass, Class targetClass, Query query); /** * Find documents by providing multiple ids with a given set name. @@ -726,13 +727,12 @@ Flux findByIdsUsingQualifiers(Collection ids, Class entityClass, * {@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. + * @param query {@link Query} provided to build a filter expression. 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 findByIdsUsingQualifiers(Collection ids, Class entityClass, Class targetClass, - String setName, - Qualifier... qualifiers); + Flux findByIdsUsingQuery(Collection ids, Class entityClass, Class targetClass, String setName, + Query query); /** * Reactively find documents in the given entityClass's set using a query and map them to the given class type. @@ -765,6 +765,49 @@ Flux findByIdsUsingQualifiers(Collection ids, Class entityClass, */ Flux find(Query query, Class targetClass, String setName); + /** + * Find all documents in the given entityClass's set using provided {@link Query}. + * + * @param query Query to build filter expression from. Constructed using a {@link Qualifier} that 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. + * @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. + * @return Stream of entities. + */ + Flux find(Query query, Class entityClass, @Nullable Filter filter); + + /** + * Find all documents in the given entityClass's set using provided {@link Query}. + * + * @param query Query to build filter expression from. Constructed using a {@link Qualifier} that 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. + * @param entityClass The class to extract the Aerospike set from and to map the entity to. Must not be + * {@literal null}. + * @param targetClass The class to map the entity to. Must not be {@literal null}. + * @param filter Secondary index filter. + * @return Stream of entities. + */ + Flux find(Query query, Class entityClass, Class targetClass, @Nullable Filter filter); + + /** + * Find all documents in the given set using provided {@link Query}. + * + * @param query Query to build filter expression from. Constructed using a {@link Qualifier} that 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. + * @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. + * @return Stream of entities. + */ + Flux find(Query query, Class targetClass, String setName, @Nullable Filter filter); + /** * Reactively find all documents in the given entityClass's set and map them to the given class type. * 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 bbdf487d5..32d85b096 100644 --- a/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeTemplate.java +++ b/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeTemplate.java @@ -68,6 +68,7 @@ import static org.springframework.data.aerospike.core.CoreUtils.operations; import static org.springframework.data.aerospike.core.TemplateUtils.excludeIdQualifier; import static org.springframework.data.aerospike.core.TemplateUtils.getIdValue; +import static org.springframework.data.aerospike.core.TemplateUtils.queryCriteriaIsNotNull; import static org.springframework.data.aerospike.query.QualifierUtils.getOneIdQualifier; import static org.springframework.data.aerospike.query.QualifierUtils.validateQualifiers; import static org.springframework.data.aerospike.utility.Utils.allArrayElementsAreNull; @@ -714,14 +715,14 @@ private Mono findGroupedEntitiesByGroupedKeys(GroupedKeys group } @Override - public Mono findByIdUsingQualifiers(Object id, Class entityClass, Class targetClass, - Qualifier... qualifiers) { - return findByIdUsingQualifiers(id, entityClass, targetClass, getSetName(entityClass), qualifiers); + public Mono findByIdUsingQuery(Object id, Class entityClass, Class targetClass, + Query query) { + return findByIdUsingQuery(id, entityClass, targetClass, getSetName(entityClass), query); } @Override - public Mono findByIdUsingQualifiers(Object id, Class entityClass, Class targetClass, String setName, - Qualifier... qualifiers) { + public Mono findByIdUsingQuery(Object id, Class entityClass, Class targetClass, String setName, + Query query) { AerospikePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); Key key = getKey(id, setName); @@ -734,6 +735,8 @@ public Mono findByIdUsingQualifiers(Object id, Class entityClass, C target = entityClass; } + Qualifier[] qualifiers = queryCriteriaIsNotNull(query) ? + new Qualifier[]{query.getCriteria().getCriteriaObject()} : null; if (entity.isTouchOnRead()) { Assert.state(!entity.hasExpirationProperty(), "Touch on read is not supported for entity without expiration property"); @@ -759,22 +762,24 @@ public Mono findByIdUsingQualifiers(Object id, Class entityClass, C } @Override - public Flux findByIdsUsingQualifiers(Collection ids, Class entityClass, Class targetClass, - Qualifier... qualifiers) { - return findByIdsUsingQualifiers(ids, entityClass, targetClass, getSetName(entityClass), qualifiers); + public Flux findByIdsUsingQuery(Collection ids, Class entityClass, Class targetClass, + Query query) { + return findByIdsUsingQuery(ids, entityClass, targetClass, getSetName(entityClass), query); } @Override - public Flux findByIdsUsingQualifiers(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!"); + public Flux findByIdsUsingQuery(Collection ids, Class entityClass, Class targetClass, + String setName, Query query) { + Assert.notNull(ids, "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 Flux.empty(); } + Qualifier[] qualifiers = queryCriteriaIsNotNull(query) && query.getCriteria() != null ? + new Qualifier[]{query.getCriteria().getCriteriaObject()} : null; BatchPolicy policy = getBatchPolicyFilterExp(qualifiers); Class target; @@ -812,6 +817,24 @@ public Flux find(Query query, Class targetClass, String setName) { return findUsingQueryWithPostProcessing(setName, targetClass, query); } + @Override + public Flux find(Query query, Class entityClass, Filter filter) { + return find(query, entityClass, getSetName(entityClass), filter); + } + + @Override + public Flux find(Query query, Class entityClass, Class targetClass, Filter filter) { + return findRecordsUsingQualifiers(getSetName(entityClass), targetClass, filter) + .map(keyRecord -> mapToEntity(keyRecord, targetClass)); + } + + @Override + public Flux find(Query query, Class targetClass, String setName, Filter filter) { + Qualifier criteria = queryCriteriaIsNotNull(query) ? query.getCriteria().getCriteriaObject() : null; + return findRecordsUsingQualifiers(setName, targetClass, filter, criteria) + .map(keyRecord -> mapToEntity(keyRecord, targetClass)); + } + @Override public Flux findAll(Class entityClass) { Assert.notNull(entityClass, "Entity class must not be null!"); diff --git a/src/main/java/org/springframework/data/aerospike/core/TemplateUtils.java b/src/main/java/org/springframework/data/aerospike/core/TemplateUtils.java index c9111d150..ab133f0ee 100644 --- a/src/main/java/org/springframework/data/aerospike/core/TemplateUtils.java +++ b/src/main/java/org/springframework/data/aerospike/core/TemplateUtils.java @@ -3,6 +3,7 @@ import lombok.experimental.UtilityClass; import org.springframework.data.aerospike.query.FilterOperation; import org.springframework.data.aerospike.query.Qualifier; +import org.springframework.data.aerospike.repository.query.Query; import org.springframework.util.Assert; import java.util.ArrayList; @@ -58,6 +59,25 @@ public static Qualifier[] excludeIdQualifier(Qualifier[] qualifiers) { return null; } + public static Qualifier excludeIdQualifier(Qualifier criteria) { + List qualifiersWithoutId = new ArrayList<>(); + if (criteria != null && criteria.hasQualifiers()) { + for (Qualifier qualifier : criteria.getQualifiers()) { + if (qualifier.hasQualifiers()) { + Qualifier[] internalQuals = excludeIdQualifier(qualifier.getQualifiers()); + qualifiersWithoutId.add(combineMultipleQualifiers(qualifier.getOperation(), internalQuals)); + } else if (!qualifier.hasId()) { + qualifiersWithoutId.add(qualifier); + } + } + return combineMultipleQualifiers(criteria.getOperation() != null ? criteria.getOperation() : + FilterOperation.AND, qualifiersWithoutId.toArray(Qualifier[]::new)); + } else if (criteria.hasId()) { + return null; + } + return criteria; + } + private static Qualifier combineMultipleQualifiers(FilterOperation operation, Qualifier[] qualifiers) { if (operation == FilterOperation.OR) { return or(qualifiers); @@ -67,4 +87,8 @@ private static Qualifier combineMultipleQualifiers(FilterOperation operation, Qu throw new UnsupportedOperationException("Only OR / AND operations are supported"); } } + + static boolean queryCriteriaIsNotNull(Query query) { + return query != null && query.getCriteria() != null; + } } diff --git a/src/main/java/org/springframework/data/aerospike/query/Qualifier.java b/src/main/java/org/springframework/data/aerospike/query/Qualifier.java index dc717c30b..4a0739b0e 100644 --- a/src/main/java/org/springframework/data/aerospike/query/Qualifier.java +++ b/src/main/java/org/springframework/data/aerospike/query/Qualifier.java @@ -441,7 +441,7 @@ protected void validate() { } /** - * Create a qualifier for the condition when the primary key is equal to the given string. + * Create a qualifier for the condition when the primary key equals the given string * * @param id String value * @return Single id qualifier @@ -453,7 +453,7 @@ public static Qualifier idEquals(String id) { } /** - * Create a qualifier for the condition when the primary key is equal to one of the given strings (logical OR). + * Create a qualifier for the condition when the primary key equals one of the given strings (logical OR) * * @param ids String values * @return Multiple ids qualifier with OR condition diff --git a/src/main/java/org/springframework/data/aerospike/query/QualifierUtils.java b/src/main/java/org/springframework/data/aerospike/query/QualifierUtils.java index 5fa7b40db..2a80b7cf5 100644 --- a/src/main/java/org/springframework/data/aerospike/query/QualifierUtils.java +++ b/src/main/java/org/springframework/data/aerospike/query/QualifierUtils.java @@ -9,11 +9,18 @@ @UtilityClass public class QualifierUtils { + @Deprecated(since = "4.6.0", forRemoval = true) public static Qualifier getIdQualifier(AerospikeCriteria criteria) { Object qualifiers = getQualifiers(criteria); return getOneIdQualifier((Qualifier[]) qualifiers); } + public static Qualifier getIdQualifier(Qualifier criteria) { + Object qualifiers = getQualifiers(criteria); + return getOneIdQualifier((Qualifier[]) qualifiers); + } + + @Deprecated(since = "4.6.0", forRemoval = true) public static Qualifier[] getQualifiers(AerospikeCriteria criteria) { if (criteria == null) { return null; @@ -23,6 +30,15 @@ public static Qualifier[] getQualifiers(AerospikeCriteria criteria) { return criteria.getQualifiers(); } + public static Qualifier[] getQualifiers(Qualifier criteria) { + if (criteria == null) { + return null; + } else if (criteria.getQualifiers() == null) { + return new Qualifier[]{(criteria)}; + } + return criteria.getQualifiers(); + } + public static void validateQualifiers(Qualifier... qualifiers) { boolean haveInternalQualifiers = qualifiers.length > 1; for (Qualifier qualifier : qualifiers) { diff --git a/src/main/java/org/springframework/data/aerospike/repository/query/AerospikePartTreeQuery.java b/src/main/java/org/springframework/data/aerospike/repository/query/AerospikePartTreeQuery.java index 3af0b8d3d..d936fac6d 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/query/AerospikePartTreeQuery.java +++ b/src/main/java/org/springframework/data/aerospike/repository/query/AerospikePartTreeQuery.java @@ -26,7 +26,6 @@ import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.parser.AbstractQueryCreator; -import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -34,8 +33,6 @@ import static org.springframework.data.aerospike.core.TemplateUtils.excludeIdQualifier; import static org.springframework.data.aerospike.core.TemplateUtils.getIdValue; import static org.springframework.data.aerospike.query.QualifierUtils.getIdQualifier; -import static org.springframework.data.aerospike.query.QualifierUtils.getQualifiers; -import static org.springframework.data.aerospike.repository.query.AerospikeCriteria.isSingleIdQuery; /** * @author Peter Milne @@ -63,15 +60,17 @@ public Object execute(Object[] parameters) { // queries that include id have their own processing flow if (parameters != null && parameters.length > 0) { - AerospikeCriteria criteria = query.getAerospikeCriteria(); - Qualifier[] qualifiers = getQualifiers(criteria); - Qualifier idQualifier; - if (isSingleIdQuery(criteria)) { - return runIdQuery(entityClass, targetClass, getIdValue(qualifiers[0])); + Qualifier criteria = query.getCriteria().getCriteriaObject(); + List ids; + if (criteria.hasSingleId()) { + ids = getIdValue(criteria); + return operations.findByIdsUsingQuery(ids, entityClass, targetClass, (Query) null); } else { + Qualifier idQualifier; if ((idQualifier = getIdQualifier(criteria)) != null) { - return runIdQuery(entityClass, targetClass, getIdValue(idQualifier), - excludeIdQualifier(qualifiers)); + ids = getIdValue(idQualifier); + return operations.findByIdsUsingQuery(ids, entityClass, targetClass, + new Query(excludeIdQualifier(criteria))); } } } @@ -100,11 +99,6 @@ public Object execute(Object[] parameters) { "supported"); } - protected Object findByIds(Collection ids, Class entityClass, Class targetClass, - Qualifier... qualifiers) { - return operations.findByIdsUsingQualifiers(ids, entityClass, targetClass, qualifiers); - } - private Stream findByQuery(Query query, Class targetClass) { // Run query and map to different target class. if (targetClass != null && targetClass != entityClass) { diff --git a/src/main/java/org/springframework/data/aerospike/repository/query/BaseAerospikePartTreeQuery.java b/src/main/java/org/springframework/data/aerospike/repository/query/BaseAerospikePartTreeQuery.java index 4e7f3278d..669fc7222 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/query/BaseAerospikePartTreeQuery.java +++ b/src/main/java/org/springframework/data/aerospike/repository/query/BaseAerospikePartTreeQuery.java @@ -26,11 +26,9 @@ import org.springframework.data.repository.query.parser.PartTree; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpression; -import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import java.lang.reflect.Constructor; -import java.util.Collection; /** * @author Peter Milne @@ -62,8 +60,8 @@ protected Query prepareQuery(Object[] parameters, ParametersParameterAccessor ac PartTree tree = new PartTree(queryMethod.getName(), entityClass); Query baseQuery = createQuery(accessor, tree); - AerospikeCriteria criteria = baseQuery.getAerospikeCriteria(); - Query query = new Query(criteria); + Qualifier qualifiers = baseQuery.getCriteria().getCriteriaObject(); + Query query = new Query(qualifiers); if (accessor.getPageable().isPaged()) { query.setOffset(accessor.getPageable().getOffset()); @@ -112,13 +110,4 @@ public Query createQuery(ParametersParameterAccessor accessor, PartTree tree) { .getConstructorIfAvailable(queryCreator, PartTree.class, ParameterAccessor.class); return (Query) BeanUtils.instantiateClass(constructor, tree, accessor).createQuery(); } - - protected Object runIdQuery(Class sourceClass, Class targetClass, Collection ids, - Qualifier... qualifiers) { - Assert.notNull(ids, "Ids must not be null"); - return findByIds(ids, sourceClass, targetClass, qualifiers); - } - - abstract Object findByIds(Collection ids, Class sourceClass, Class targetClass, - Qualifier... qualifiers); } diff --git a/src/main/java/org/springframework/data/aerospike/repository/query/ReactiveAerospikePartTreeQuery.java b/src/main/java/org/springframework/data/aerospike/repository/query/ReactiveAerospikePartTreeQuery.java index 54ad01dc0..4b7b9ef5b 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/query/ReactiveAerospikePartTreeQuery.java +++ b/src/main/java/org/springframework/data/aerospike/repository/query/ReactiveAerospikePartTreeQuery.java @@ -24,12 +24,11 @@ import org.springframework.data.repository.query.parser.AbstractQueryCreator; import reactor.core.publisher.Flux; -import java.util.Collection; +import java.util.List; import static org.springframework.data.aerospike.core.TemplateUtils.excludeIdQualifier; import static org.springframework.data.aerospike.core.TemplateUtils.getIdValue; import static org.springframework.data.aerospike.query.QualifierUtils.getIdQualifier; -import static org.springframework.data.aerospike.query.QualifierUtils.getQualifiers; /** * @author Igor Ermolenko @@ -54,14 +53,18 @@ public Object execute(Object[] parameters) { // queries that include id have their own processing flow if (parameters != null && parameters.length > 0) { - AerospikeCriteria criteria = query.getAerospikeCriteria(); - Qualifier[] qualifiers = getQualifiers(criteria); - Qualifier idQualifier; - if (AerospikeCriteria.isSingleIdQuery(criteria)) { - return runIdQuery(entityClass, targetClass, getIdValue(qualifiers[0])); - } else if ((idQualifier = getIdQualifier(criteria)) != null) { - return runIdQuery(entityClass, targetClass, getIdValue(idQualifier), - excludeIdQualifier(qualifiers)); + Qualifier criteria = query.getCriteria().getCriteriaObject(); + List ids; + if (criteria.hasSingleId()) { + ids = getIdValue(criteria); + return operations.findByIdsUsingQuery(ids, entityClass, targetClass, null); + } else { + Qualifier idQualifier; + if ((idQualifier = getIdQualifier(criteria)) != null) { + ids = getIdValue(idQualifier); + return operations.findByIdsUsingQuery(ids, entityClass, targetClass, + new Query(excludeIdQualifier(criteria))); + } } } return findByQuery(query, targetClass); @@ -75,9 +78,4 @@ private Flux findByQuery(Query query, Class targetClass) { // Run query and map to entity class type. return operations.find(query, entityClass); } - - protected Object findByIds(Collection ids, Class sourceClass, Class targetClass, - Qualifier... qualifiers) { - return operations.findByIdsUsingQualifiers(ids, sourceClass, targetClass, qualifiers); - } } 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 d65f585f6..46dace238 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByQueryTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByQueryTests.java @@ -543,13 +543,13 @@ public void findAllUsingQuery_shouldRunWithDifferentArgumentsCombinations() { .setValue1(Value.get(fieldValue1)) .build(); Stream result1 = - template.findUsingQualifier(SampleClasses.CustomCollectionClass.class, null, qualifier); + template.find(new Query(qualifier), SampleClasses.CustomCollectionClass.class, (Filter) null); assertThat(result1).containsOnly(doc1); // find by a predefined secondary index filter, no qualifiers Filter filter = Filter.equal(fieldName, fieldValue1); Stream result2 = - template.findUsingQualifier(SampleClasses.CustomCollectionClass.class, filter, null); + template.find(null, SampleClasses.CustomCollectionClass.class, filter); assertThat(result2).containsOnly(doc1); // find by a complex qualifier @@ -565,12 +565,12 @@ public void findAllUsingQuery_shouldRunWithDifferentArgumentsCombinations() { .build(); Qualifier qualifierOr = Qualifier.or(dataEqFieldValue1, dataEqFieldValue2); Stream result3 = - template.findUsingQualifier(SampleClasses.CustomCollectionClass.class, null, qualifierOr); + template.find(new Query(qualifierOr), SampleClasses.CustomCollectionClass.class, (Filter) null); assertThat(result3).containsOnly(doc1, doc2); // no secondary index filter and no qualifiers Stream result4 = - template.findUsingQualifier(SampleClasses.CustomCollectionClass.class, null, null); + template.find(null, SampleClasses.CustomCollectionClass.class, (Filter) null); assertThat(result4).contains(doc1, doc2); additionalAerospikeTestOperations.dropIndex(SampleClasses.CustomCollectionClass.class, diff --git a/src/test/java/org/springframework/data/aerospike/repository/PersonRepositoryQueryTests.java b/src/test/java/org/springframework/data/aerospike/repository/PersonRepositoryQueryTests.java index 8931b258c..605c0f93a 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/PersonRepositoryQueryTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/PersonRepositoryQueryTests.java @@ -1202,21 +1202,35 @@ public void deleteAllPersonsFromList() { } @Test - public void findPersonByIdAndFirstName() { - List persons = repository.findByIdAndFirstName(dave.getId(), dave.getFirstName()); + public void findPersonByIdsAndFields() { + List persons = repository.findByIdAndFirstName(List.of(boyd.getId(), dave.getId(), carter.getId()), + dave.getFirstName()); assertThat(persons).containsOnly(dave); - } - @Test - public void findPersonByIdAndFirstNameEmptyResult() { - List persons = repository.findByIdAndFirstName(dave.getId(), carter.getFirstName()); - assertThat(persons).isEmpty(); + List persons2 = repository.findByIdAndFirstNameAndAge(List.of(leroi.getId(), leroi2.getId(), + carter.getId()), + leroi.getFirstName(), leroi2.getAge()); + assertThat(persons2).containsOnly(leroi2); + + // "findByIdOr..." will return empty list because primary keys are used to firstly narrow down the results.parts + // In a combined id query "OR" can be put only between the non-id fields like shown below, + // and the results are limited by the given ids + List persons3 = repository.findByIdAndFirstNameOrAge(List.of(leroi.getId(), leroi2.getId(), + carter.getId()), + leroi.getFirstName(), leroi2.getAge()); + assertThat(persons3).containsOnly(leroi, leroi2); + + List persons4 = repository.findByIdAndFirstNameOrAge(List.of(leroi.getId(), leroi2.getId(), + carter.getId()), + leroi.getFirstName(), stefan.getAge()); + assertThat(persons4).containsOnly(leroi, leroi2); } @Test - public void findPersonByFirstNameAndId() { - List persons = repository.findByFirstNameAndId(dave.getFirstName(), dave.getId()); - assertThat(persons).containsOnly(dave); + public void findPersonByIdsAndFirstNameEmptyResult() { + List persons = repository.findByIdAndFirstName(List.of(dave.getId(), boyd.getId()), + carter.getFirstName()); + assertThat(persons).isEmpty(); } @Test @@ -1349,10 +1363,17 @@ public void findPersonsByQuery() { assertThat(result).containsOnly(carter, boyd); // metadata and id qualifiers combined with AND - // not more than one id qualifier is allowed, otherwise the conditions will not overlap + // not more than one id qualifier is allowed, otherwise the conditions will not overlap because of uniqueness result = repository.findUsingQuery(new Query(Qualifier.and(sinceUpdateTimeGt1, keyEqCartersId))); + // if a query contains id qualifier the results are firstly narrowed down to satisfy the given id(s) + // that's why queries with qualifier like Qualifier.or(Qualifier.idEquals(...), ageGt49)) return empty result assertThat(result).containsOnly(carter); + // if a query contains id qualifier the results are firstly narrowed down to satisfy the given id(s) + result = repository.findUsingQuery(new Query(Qualifier.and(sinceUpdateTimeGt1, Qualifier.idIn(carter.getId(), + dave.getId(), boyd.getId())))); + assertThat(result).containsOnly(carter, dave, boyd); + // the same qualifiers in different order result = repository.findUsingQuery(new Query(Qualifier.and(keyEqCartersId, sinceUpdateTimeGt1))); assertThat(result).containsOnly(carter); @@ -1492,14 +1513,16 @@ public void findByIdDynamicProjection() { @Test public void findByIdAndLastNameDynamicProjection() { - List result = repository.findByIdAndLastName(carter.getId(), carter.getLastName(), + List result = repository.findByIdAndLastName(List.of(carter.getId(), leroi.getId(), + leroi2.getId()), carter.getLastName(), PersonSomeFields.class); assertThat(result).containsOnly(carter.toPersonSomeFields()); } @Test public void findByIdAndLastNameDynamicProjectionNullResult() { - List result = repository.findByIdAndLastName(carter.getId(), dave.getLastName(), + List result = repository.findByIdAndLastName(List.of(carter.getId(), boyd.getId()), + dave.getLastName(), PersonSomeFields.class); assertThat(result).isEmpty(); } diff --git a/src/test/java/org/springframework/data/aerospike/sample/PersonRepository.java b/src/test/java/org/springframework/data/aerospike/sample/PersonRepository.java index d385f3995..9af28fda7 100644 --- a/src/test/java/org/springframework/data/aerospike/sample/PersonRepository.java +++ b/src/test/java/org/springframework/data/aerospike/sample/PersonRepository.java @@ -56,7 +56,7 @@ public interface PersonRepository

extends AerospikeRepository< List findById(String id, Class type); // Dynamic Projection - List findByIdAndLastName(String id, String lastName, Class type); + List findByIdAndLastName(List ids, String lastName, Class type); // Dynamic Projection List findByLastNameAndId(String lastName, String id, Class type); @@ -64,11 +64,33 @@ public interface PersonRepository

extends AerospikeRepository< // Dynamic Projection List findByFirstNameAndLastName(String firstName, String lastName, Class type); + /** + * Find all entities that have primary key in the given list. + * + * @param ids List of primary keys + */ List

findById(List ids); - List

findByIdAndFirstName(String id, String firstName); + /** + * Find all entities that satisfy the condition "have primary key in the given list and first name equal to the + * specified string". + * + * @param ids List of primary keys + * @param firstName String to compare with + */ + List

findByIdAndFirstName(List ids, String firstName); + + /** + * Find all entities that satisfy the condition "have primary key in the given list and either first name equal to + * the specified string or age equal to the specified integer". + * + * @param ids List of primary keys + * @param firstName String to compare firstName with + * @param age integer to compare age with + */ + List

findByIdAndFirstNameAndAge(List ids, String firstName, int age); - List

findByFirstNameAndId(String firstName, String id); + List

findByIdAndFirstNameOrAge(List ids, String firstName, int age); Page

findByLastNameStartsWithOrderByAgeAsc(String prefix, Pageable pageable);