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 c4910b010..e9ba3ad99 100644 --- a/src/main/java/org/springframework/data/aerospike/core/AerospikeOperations.java +++ b/src/main/java/org/springframework/data/aerospike/core/AerospikeOperations.java @@ -557,5 +557,13 @@ void createIndex(Class entityClass, String indexName, String binName, */ boolean indexExists(String indexName); + /** + * Find all documents in the given entityClass's set using provided qualifiers. + * + * @param entityClass The class to extract the Aerospike set from. Must not be {@literal null}. + * @param filter Secondary index filter. + * @param qualifiers Qualifiers to build filter expressions from. + * @return Stream of entities + */ Stream findAllUsingQuery(Class entityClass, Filter filter, 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 3b69c2901..40f07fb3e 100644 --- a/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeOperations.java +++ b/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeOperations.java @@ -17,11 +17,13 @@ import com.aerospike.client.AerospikeException; import com.aerospike.client.cdt.CTX; +import com.aerospike.client.query.Filter; import com.aerospike.client.query.IndexCollectionType; import com.aerospike.client.query.IndexType; import com.aerospike.client.reactor.IAerospikeReactorClient; import org.springframework.data.aerospike.core.model.GroupedEntities; import org.springframework.data.aerospike.core.model.GroupedKeys; +import org.springframework.data.aerospike.query.Qualifier; import org.springframework.data.aerospike.repository.query.Query; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.context.MappingContext; @@ -517,4 +519,14 @@ Mono createIndex(Class entityClass, String indexName, String binNam * @return true if exists. */ Mono indexExists(String indexName); + + /** + * Find all documents in the given entityClass's set using provided {@link Qualifier}s. + * + * @param entityClass The class to extract the Aerospike set from. Must not be {@literal null}. + * @param filter Secondary index filter. + * @param qualifiers Qualifiers to build filter expressions from. + * @return Flux of entities. + */ + Flux findAllUsingQuery(Class entityClass, Filter filter, Qualifier... qualifiers); } 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 e4ef0c107..d7c6537ce 100644 --- a/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeTemplate.java +++ b/src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeTemplate.java @@ -964,6 +964,13 @@ Object mapToEntity(KeyRecord keyRecord, Class entityClass, Class ta return mapToEntity(keyRecord.key, entityClass, keyRecord.record); } + @Override + public Flux findAllUsingQuery(Class entityClass, Filter filter, + Qualifier... qualifiers) { + return findAllRecordsUsingQuery(entityClass, null, filter, qualifiers) + .map(keyRecord -> mapToEntity(keyRecord.key, entityClass, keyRecord.record)); + } + Flux findAllUsingQuery(Class entityClass, Class targetClass, Filter filter, Qualifier... qualifiers) { return findAllRecordsUsingQuery(entityClass, targetClass, filter, qualifiers) diff --git a/src/main/java/org/springframework/data/aerospike/query/FilterExpressionsBuilder.java b/src/main/java/org/springframework/data/aerospike/query/FilterExpressionsBuilder.java index 06169074e..e18b39711 100644 --- a/src/main/java/org/springframework/data/aerospike/query/FilterExpressionsBuilder.java +++ b/src/main/java/org/springframework/data/aerospike/query/FilterExpressionsBuilder.java @@ -34,7 +34,14 @@ public Expression build(Qualifier[] qualifiers) { * sIndexFilter and FilterExpression. The filter is irrelevant for AND operation (nested qualifiers) */ private boolean excludeIrrelevantFilters(Qualifier qualifier) { - return !qualifier.queryAsFilter() || - (qualifier.queryAsFilter() && FilterOperation.dualFilterOperations.contains(qualifier.getOperation())); + if (!qualifier.queryAsFilter()) { + return true; + } else if (qualifier.queryAsFilter() && FilterOperation.dualFilterOperations.contains(qualifier.getOperation())) { + qualifier.setQueryAsFilter(false); // clear the flag in case if the same Qualifier is going to be reused + return true; + } else { + qualifier.setQueryAsFilter(false); // clear the flag in case if the same Qualifier is going to be reused + return false; + } } } 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 4fa892ec9..45d1edcff 100644 --- a/src/main/java/org/springframework/data/aerospike/query/Qualifier.java +++ b/src/main/java/org/springframework/data/aerospike/query/Qualifier.java @@ -53,6 +53,7 @@ public class Qualifier implements Map, Serializable { protected static final String QUALIFIERS = "qualifiers"; protected static final String OPERATION = "operation"; protected static final String AS_FILTER = "queryAsFilter"; + protected static final String EXCLUDE_FILTER = "excludeFilter"; @Serial private static final long serialVersionUID = -2689196529952712849L; protected final Map internalMap; @@ -77,7 +78,7 @@ public CriteriaDefinition.AerospikeMetadata getMetadataField() { return (CriteriaDefinition.AerospikeMetadata) internalMap.get(METADATA_FIELD); } - public void asFilter(Boolean queryAsFilter) { + public void setQueryAsFilter(Boolean queryAsFilter) { internalMap.put(AS_FILTER, queryAsFilter); } @@ -85,6 +86,14 @@ public Boolean queryAsFilter() { return internalMap.containsKey(AS_FILTER) && (Boolean) internalMap.get(AS_FILTER); } + public boolean getExcludeFilter() { + return internalMap.containsKey(EXCLUDE_FILTER) && (Boolean) internalMap.get(EXCLUDE_FILTER); + } + + public void setExcludeFilter(boolean excludeFilter) { + internalMap.put(EXCLUDE_FILTER, excludeFilter); + } + public Qualifier[] getQualifiers() { return (Qualifier[]) internalMap.get(QUALIFIERS); } @@ -109,7 +118,7 @@ public String getDotPath() { return (String) internalMap.get(DOT_PATH); } - public Filter asFilter() { + public Filter setQueryAsFilter() { return FilterOperation.valueOf(getOperation().toString()).sIndexFilter(internalMap); } @@ -309,6 +318,11 @@ public QualifierBuilder setQualifiers(Qualifier... qualifiers) { return this; } + public QualifierBuilder setExcludeFilter(boolean excludeFilter) { + this.map.put(EXCLUDE_FILTER, excludeFilter); + return this; + } + public QualifierBuilder setValue1(Value value1) { this.map.put(VALUE1, value1); return this; diff --git a/src/main/java/org/springframework/data/aerospike/query/StatementBuilder.java b/src/main/java/org/springframework/data/aerospike/query/StatementBuilder.java index 96a3f53cd..f8c57c6b8 100644 --- a/src/main/java/org/springframework/data/aerospike/query/StatementBuilder.java +++ b/src/main/java/org/springframework/data/aerospike/query/StatementBuilder.java @@ -49,14 +49,15 @@ public Statement build(String namespace, String set, Filter filter, Qualifier[] } if (filter != null) { stmt.setFilter(filter); - } - if (qualifiers != null && qualifiers.length != 0) { - updateStatement(stmt, qualifiers); + } else if (qualifiers != null && qualifiers.length != 0) { + // statement's filter is set based on the first processed qualifier's filter + // if the qualifier doesn't have EXCLUDE_FILTER set to true + setStatementFilterFromQualifiers(stmt, qualifiers); } return stmt; } - private void updateStatement(Statement stmt, Qualifier[] qualifiers) { + private void setStatementFilterFromQualifiers(Statement stmt, Qualifier[] qualifiers) { /* * query with filters */ @@ -65,28 +66,29 @@ private void updateStatement(Statement stmt, Qualifier[] qualifiers) { if (qualifier.getOperation() == FilterOperation.AND) { // no sense to use secondary index in case of OR // as it requires to enlarge selection to more than 1 field - for (Qualifier q : qualifier.getQualifiers()) { - if (q != null && isIndexedBin(stmt, q)) { - Filter filter = q.asFilter(); + for (Qualifier innerQualifier : qualifier.getQualifiers()) { + if (innerQualifier != null && !innerQualifier.getExcludeFilter() + && isIndexedBin(stmt, innerQualifier)) { + Filter filter = innerQualifier.setQueryAsFilter(); if (filter != null) { stmt.setFilter(filter); - q.asFilter(true); - break; + innerQualifier.setQueryAsFilter(true); + break; // the first processed filter becomes statement filter } } } - } else if (isIndexedBin(stmt, qualifier)) { - Filter filter = qualifier.asFilter(); + } else if (!qualifier.getExcludeFilter() && isIndexedBin(stmt, qualifier)) { + Filter filter = qualifier.setQueryAsFilter(); if (filter != null) { stmt.setFilter(filter); - qualifier.asFilter(true); + qualifier.setQueryAsFilter(true); /* If this was the only qualifier, we do not need to do anymore work, just return * the query iterator. */ if (qualifiers.length == 1) { return; } - break; + break; // the first processed filter becomes statement filter } } } diff --git a/src/main/java/org/springframework/data/aerospike/repository/AerospikeRepository.java b/src/main/java/org/springframework/data/aerospike/repository/AerospikeRepository.java index 9c63ea46a..458cd7089 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/AerospikeRepository.java +++ b/src/main/java/org/springframework/data/aerospike/repository/AerospikeRepository.java @@ -34,10 +34,10 @@ public interface AerospikeRepository extends PagingAndSortingRepository void createIndex(Class domainType, String indexName, String binName, IndexType indexType); @@ -53,20 +53,20 @@ public interface AerospikeRepository extends PagingAndSortingRepository * If multiple qualifiers are given, they are combined using AND. *

- * Each qualifier itself might contain internal qualifiers and combine them using either {@link FilterOperation#AND} + * Each qualifier itself may contain internal qualifiers and combine them using either {@link FilterOperation#AND} * or {@link FilterOperation#OR}. * - * @param qualifiers One or more qualifiers representing expressions - * @return Iterable of entities + * @param qualifiers One or more qualifiers representing expressions. + * @return Iterable of entities. */ Iterable findByQualifiers(Qualifier... qualifiers); } diff --git a/src/main/java/org/springframework/data/aerospike/repository/ReactiveAerospikeRepository.java b/src/main/java/org/springframework/data/aerospike/repository/ReactiveAerospikeRepository.java index 89a5d5ca9..91fd8b9c9 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/ReactiveAerospikeRepository.java +++ b/src/main/java/org/springframework/data/aerospike/repository/ReactiveAerospikeRepository.java @@ -15,8 +15,11 @@ */ package org.springframework.data.aerospike.repository; +import org.springframework.data.aerospike.query.FilterOperation; +import org.springframework.data.aerospike.query.Qualifier; import org.springframework.data.repository.Repository; import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import reactor.core.publisher.Flux; /** * Aerospike specific {@link Repository} interface with reactive support. @@ -24,4 +27,17 @@ * @author Igor Ermolenko */ public interface ReactiveAerospikeRepository extends ReactiveCrudRepository { + + /** + * Run a query to find entities by providing {@link Qualifier}s. + *

+ * If multiple qualifiers are given, they are combined using AND. + *

+ * Each qualifier itself may contain internal qualifiers and combine them using either {@link FilterOperation#AND} + * or {@link FilterOperation#OR}. + * + * @param qualifiers One or more qualifiers representing expressions. + * @return Flux of entities. + */ + Flux findByQualifiers(Qualifier... 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 031d22d9d..aafc0c723 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 @@ -163,7 +163,10 @@ public boolean indexExists(String indexName) { @Override public Iterable findByQualifiers(Qualifier... qualifiers) { - Arrays.stream(qualifiers).forEach(Qualifier::validateQualifier); + Arrays.stream(qualifiers).forEach(qualifier -> { + qualifier.setExcludeFilter(true); + Qualifier.validateQualifier(qualifier); + }); return (Iterable) operations.findAllUsingQuery(entityInformation.getJavaType(), null, qualifiers).toList(); } } 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 1ac3bc45a..923e84d57 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 @@ -19,12 +19,15 @@ import lombok.RequiredArgsConstructor; import org.reactivestreams.Publisher; import org.springframework.data.aerospike.core.ReactiveAerospikeOperations; +import org.springframework.data.aerospike.query.Qualifier; import org.springframework.data.aerospike.repository.ReactiveAerospikeRepository; import org.springframework.data.repository.core.EntityInformation; import org.springframework.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.Arrays; + /** * Stub implementation of {@link ReactiveAerospikeRepository}. * @@ -151,4 +154,10 @@ public void createIndex(Class domainType, String indexName, String binName, I public void deleteIndex(Class domainType, String indexName) { operations.deleteIndex(domainType, indexName); } + + @Override + public Flux findByQualifiers(Qualifier... qualifiers) { + Arrays.stream(qualifiers).forEach(Qualifier::validateQualifier); + return operations.findAllUsingQuery(entityInformation.getJavaType(), null, qualifiers); + } } diff --git a/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveIndexedPersonRepositoryQueryTests.java b/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveIndexedPersonRepositoryQueryTests.java index 423a71d72..78c716948 100644 --- a/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveIndexedPersonRepositoryQueryTests.java +++ b/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveIndexedPersonRepositoryQueryTests.java @@ -1,5 +1,6 @@ package org.springframework.data.aerospike.query.reactive; +import com.aerospike.client.Value; import com.aerospike.client.query.IndexCollectionType; import com.aerospike.client.query.IndexType; import org.junit.jupiter.api.AfterAll; @@ -9,6 +10,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.aerospike.BaseReactiveIntegrationTests; import org.springframework.data.aerospike.ReactiveBlockingAerospikeTestOperations; +import org.springframework.data.aerospike.query.FilterOperation; +import org.springframework.data.aerospike.query.Qualifier; import org.springframework.data.aerospike.repository.query.CriteriaDefinition; import org.springframework.data.aerospike.sample.Address; import org.springframework.data.aerospike.sample.IndexedPerson; @@ -23,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.springframework.data.aerospike.AsCollections.of; import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeMapCriteria.VALUE; +import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeMetadata.SINCE_UPDATE_TIME; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class ReactiveIndexedPersonRepositoryQueryTests extends BaseReactiveIntegrationTests { @@ -287,4 +291,139 @@ public void findByMapKeyValueBetween() { assertThat(results).containsExactlyInAnyOrder(lilly, emilien); } + + @Test + public void findPersonsByMetadata() { + // creating a condition "since_update_time metadata value is less than 50 seconds" + Qualifier sinceUpdateTimeLt10Seconds = new Qualifier.QualifierBuilder() + .setMetadataField(SINCE_UPDATE_TIME) + .setFilterOperation(FilterOperation.LT) + .setValue1AsObj(50000L) + .build(); + assertThat(reactiveRepository.findByQualifiers(sinceUpdateTimeLt10Seconds).collectList().block()) + .containsAll(allIndexedPersons); + + // creating a condition "since_update_time metadata value is between 1 millisecond and 50 seconds" + Qualifier sinceUpdateTimeBetween1And50000 = new Qualifier.QualifierBuilder() + .setMetadataField(SINCE_UPDATE_TIME) + .setFilterOperation(FilterOperation.BETWEEN) + .setValue1AsObj(1L) + .setValue2AsObj(50000L) + .build(); + assertThat(reactiveRepository.findByQualifiers(sinceUpdateTimeBetween1And50000).collectList().block()) + .containsAll(reactiveRepository.findByQualifiers(sinceUpdateTimeLt10Seconds).collectList().block()); + } + + @Test + public void findPersonsByQualifiers() { + Iterable result; + + // creating a condition "since_update_time metadata value is greater than 1 millisecond" + Qualifier sinceUpdateTimeGt1 = new Qualifier.QualifierBuilder() + .setMetadataField(SINCE_UPDATE_TIME) + .setFilterOperation(FilterOperation.GT) + .setValue1AsObj(1L) + .build(); + + // creating a condition "since_update_time metadata value is less than 50 seconds" + Qualifier sinceUpdateTimeLt50Seconds = new Qualifier.QualifierBuilder() + .setMetadataField(SINCE_UPDATE_TIME) + .setFilterOperation(FilterOperation.LT) + .setValue1AsObj(50000L) + .build(); + assertThat(reactiveRepository.findByQualifiers(sinceUpdateTimeLt50Seconds).collectList().block()) + .containsAll(allIndexedPersons); + + // creating a condition "since_update_time metadata value is between 1 and 50 seconds" + Qualifier sinceUpdateTimeBetween1And50000 = new Qualifier.QualifierBuilder() + .setMetadataField(SINCE_UPDATE_TIME) + .setFilterOperation(FilterOperation.BETWEEN) + .setValue1AsObj(1L) + .setValue2AsObj(50000L) + .build(); + + // creating a condition "firsName is equal to Petra" + Qualifier firstNameEqPetra = new Qualifier.QualifierBuilder() + .setField("firstName") + .setFilterOperation(FilterOperation.EQ) + .setValue1(Value.get("Petra")) + .build(); + + // creating a condition "age is equal to 34" + Qualifier ageEq34 = new Qualifier.QualifierBuilder() + .setField("age") + .setFilterOperation(FilterOperation.EQ) + .setValue1(Value.get(34)) + .setExcludeFilter(true) + .build(); + result = reactiveRepository.findByQualifiers(ageEq34).collectList().block(); + assertThat(result).containsOnly(petra); + + // creating a condition "age is greater than 34" + Qualifier ageGt34 = new Qualifier.QualifierBuilder() + .setFilterOperation(FilterOperation.GT) + .setField("age") + .setValue1(Value.get(34)) + .build(); + result = reactiveRepository.findByQualifiers(ageGt34).collectList().block(); + assertThat(result).doesNotContain(petra); + + // default conjunction for multiple qualifiers given to "findByMetadata" is AND + result = reactiveRepository.findByQualifiers(sinceUpdateTimeGt1, sinceUpdateTimeLt50Seconds, ageEq34, + firstNameEqPetra, + sinceUpdateTimeBetween1And50000).collectList().block(); + assertThat(result).containsOnly(petra); + + // conditions "age == 34", "firstName is Petra" and "since_update_time metadata value is less than 50 seconds" + // are combined with OR + Qualifier orWide = new Qualifier.QualifierBuilder() + .setFilterOperation(FilterOperation.OR) + .setQualifiers(ageEq34, firstNameEqPetra, sinceUpdateTimeLt50Seconds) + .build(); + result = reactiveRepository.findByQualifiers(orWide).collectList().block(); + assertThat(result).containsAll(allIndexedPersons); + + // conditions "age == 34" and "firstName is Petra" are combined with OR + Qualifier orNarrow = new Qualifier.QualifierBuilder() + .setFilterOperation(FilterOperation.OR) + .setQualifiers(ageEq34, firstNameEqPetra) + .build(); + result = reactiveRepository.findByQualifiers(orNarrow).collectList().block(); + assertThat(result).containsOnly(petra); + + result = reactiveRepository.findByQualifiers(new Qualifier.QualifierBuilder() + .setFilterOperation(FilterOperation.AND) + .setQualifiers(ageEq34, ageGt34) + .build()).collectList().block(); + assertThat(result).isEmpty(); + + // default conjunction for multiple qualifiers given to "findByMetadata" is AND + // conditions "age == 34" and "age > 34" are not overlapping + result = reactiveRepository.findByQualifiers(ageEq34, ageGt34).collectList().block(); + assertThat(result).isEmpty(); + + // conditions "age == 34" and "age > 34" are combined with OR + Qualifier ageEqOrGt34 = new Qualifier.QualifierBuilder() + .setFilterOperation(FilterOperation.OR) + .setQualifiers(ageEq34, ageGt34) + .build(); + + result = reactiveRepository.findByQualifiers(ageEqOrGt34).collectList().block(); + List personsWithAgeEqOrGt34 = allIndexedPersons.stream().filter(person -> person.getAge() >= 34) + .toList(); + assertThat(result).containsAll(personsWithAgeEqOrGt34); + + // a condition that returns all entities and a condition that returns one entity are combined using AND + result = reactiveRepository.findByQualifiers(orWide, orNarrow).collectList().block(); + assertThat(result).containsOnly(petra); + + // a condition that returns all entities and a condition that returns one entity are combined using AND + // another way of running the same query + Qualifier orCombinedWithAnd = new Qualifier.QualifierBuilder() + .setFilterOperation(FilterOperation.AND) + .setQualifiers(orWide, orNarrow) + .build(); + result = reactiveRepository.findByQualifiers(orCombinedWithAnd).collectList().block(); + assertThat(result).containsOnly(petra); + } } 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 5a8984d1d..66e01e7eb 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/PersonRepositoryQueryTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/PersonRepositoryQueryTests.java @@ -1217,7 +1217,7 @@ public void findPersonsByEmail() { @Test public void findPersonsByMetadata() { - // creating a condition "since_update_time metadata value is less than 10000" + // creating a condition "since_update_time metadata value is less than 50 seconds" Qualifier sinceUpdateTimeLt10Seconds = new Qualifier.QualifierBuilder() .setMetadataField(SINCE_UPDATE_TIME) .setFilterOperation(FilterOperation.LT) @@ -1225,7 +1225,7 @@ public void findPersonsByMetadata() { .build(); assertThat(repository.findByQualifiers(sinceUpdateTimeLt10Seconds)).containsAll(allPersons); - // creating a condition "since_update_time metadata value is between 1 and 50000" + // creating a condition "since_update_time metadata value is between 1 millisecond and 50 seconds" Qualifier sinceUpdateTimeBetween1And50000 = new Qualifier.QualifierBuilder() .setMetadataField(SINCE_UPDATE_TIME) .setFilterOperation(FilterOperation.BETWEEN) @@ -1240,22 +1240,22 @@ public void findPersonsByMetadata() { public void findPersonsByQualifiers() { Iterable result; - // creating a condition "since_update_time metadata value is greater than 1" + // creating a condition "since_update_time metadata value is greater than 1 millisecond" Qualifier sinceUpdateTimeGt1 = new Qualifier.QualifierBuilder() .setMetadataField(SINCE_UPDATE_TIME) .setFilterOperation(FilterOperation.GT) .setValue1AsObj(1L) .build(); - // creating a condition "since_update_time metadata value is less than 10000" - Qualifier sinceUpdateTimeLt10Seconds = new Qualifier.QualifierBuilder() + // creating a condition "since_update_time metadata value is less than 50 seconds" + Qualifier sinceUpdateTimeLt50Seconds = new Qualifier.QualifierBuilder() .setMetadataField(SINCE_UPDATE_TIME) .setFilterOperation(FilterOperation.LT) .setValue1AsObj(50000L) .build(); - assertThat(repository.findByQualifiers(sinceUpdateTimeLt10Seconds)).containsAll(allPersons); + assertThat(repository.findByQualifiers(sinceUpdateTimeLt50Seconds)).containsAll(allPersons); - // creating a condition "since_update_time metadata value is between 1 and 50000" + // creating a condition "since_update_time metadata value is between 1 millisecond and 50 seconds" Qualifier sinceUpdateTimeBetween1And50000 = new Qualifier.QualifierBuilder() .setMetadataField(SINCE_UPDATE_TIME) .setFilterOperation(FilterOperation.BETWEEN) @@ -1289,15 +1289,15 @@ public void findPersonsByQualifiers() { assertThat(result).doesNotContain(carter); // default conjunction for multiple qualifiers given to "findByMetadata" is AND - result = repository.findByQualifiers(sinceUpdateTimeGt1, sinceUpdateTimeLt10Seconds, ageEq49, firstNameEqCarter, + result = repository.findByQualifiers(sinceUpdateTimeGt1, sinceUpdateTimeLt50Seconds, ageEq49, firstNameEqCarter, sinceUpdateTimeBetween1And50000); assertThat(result).containsOnly(carter); - // conditions "age == 49", "firstName is Carter" and "since_update_time metadata value is less than 10 seconds" + // conditions "age == 49", "firstName is Carter" and "since_update_time metadata value is less than 50 seconds" // are combined with OR Qualifier orWide = new Qualifier.QualifierBuilder() .setFilterOperation(FilterOperation.OR) - .setQualifiers(ageEq49, firstNameEqCarter, sinceUpdateTimeLt10Seconds) + .setQualifiers(ageEq49, firstNameEqCarter, sinceUpdateTimeLt50Seconds) .build(); result = repository.findByQualifiers(orWide); assertThat(result).containsAll(allPersons); @@ -1326,8 +1326,8 @@ public void findPersonsByQualifiers() { assertThat(result).containsAll(personsWithAgeEqOrGt49); // a condition that returns all entities and a condition that returns one entity are combined using AND - Iterable result2 = repository.findByQualifiers(orWide, orNarrow); - assertThat(result2).isEqualTo(result); + result = repository.findByQualifiers(orWide, orNarrow); + assertThat(result).containsOnly(carter); // a condition that returns all entities and a condition that returns one entity are combined using AND // another way of running the same query