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 61729082b..95cdc03e4 100644 --- a/src/main/java/org/springframework/data/aerospike/core/AerospikeTemplate.java +++ b/src/main/java/org/springframework/data/aerospike/core/AerospikeTemplate.java @@ -38,7 +38,6 @@ import org.springframework.data.aerospike.index.IndexesCacheRefresher; import org.springframework.data.aerospike.mapping.AerospikeMappingContext; import org.springframework.data.aerospike.mapping.AerospikePersistentEntity; -import org.springframework.data.aerospike.mapping.AerospikePersistentProperty; import org.springframework.data.aerospike.query.KeyRecordIterator; import org.springframework.data.aerospike.query.QueryEngine; import org.springframework.data.aerospike.query.cache.IndexRefresher; @@ -47,7 +46,6 @@ import org.springframework.data.aerospike.server.version.ServerVersionSupport; import org.springframework.data.aerospike.util.Utils; import org.springframework.data.domain.Sort; -import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.util.StreamUtils; import org.springframework.util.Assert; @@ -76,6 +74,7 @@ import static org.springframework.data.aerospike.core.CoreUtils.operations; 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.getBinNamesFromTargetClass; 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.queryCriteriaIsNotNull; @@ -802,7 +801,7 @@ private Key[] getKeys(Collection ids, String setName) { private Object getRecordMapToTargetClass(AerospikePersistentEntity entity, Key key, Class targetClass, Query query) { Record aeroRecord; - String[] binNames = getBinNamesFromTargetClass(targetClass); + String[] binNames = getBinNamesFromTargetClass(targetClass, mappingContext); if (entity.isTouchOnRead()) { Assert.state(!entity.hasExpirationProperty(), "Touch on read is not supported for expiration property"); aeroRecord = getAndTouch(key, entity.getExpiration(), binNames, query); @@ -813,17 +812,6 @@ private Object getRecordMapToTargetClass(AerospikePersistentEntity entity return mapToEntity(key, targetClass, aeroRecord); } - private String[] getBinNamesFromTargetClass(Class targetClass) { - AerospikePersistentEntity targetEntity = mappingContext.getRequiredPersistentEntity(targetClass); - - List binNamesList = new ArrayList<>(); - - targetEntity.doWithProperties((PropertyHandler) property - -> binNamesList.add(property.getFieldName())); - - return binNamesList.toArray(new String[0]); - } - private Policy getPolicyFilterExp(Query query) { if (queryCriteriaIsNotNull(query)) { Policy policy = new Policy(getAerospikeClient().getReadPolicyDefault()); @@ -981,7 +969,7 @@ public List findByIdsUsingQuery(Collection ids, Class entityClas Class target; Record[] aeroRecords; if (targetClass != null && targetClass != entityClass) { - String[] binNames = getBinNamesFromTargetClass(targetClass); + String[] binNames = getBinNamesFromTargetClass(targetClass, mappingContext); aeroRecords = getAerospikeClient().get(policy, keys, binNames); target = targetClass; } else { @@ -1438,7 +1426,7 @@ private Stream findRecordsUsingQuery(String setName, Class tar KeyRecordIterator recIterator; if (targetClass != null) { - String[] binNames = getBinNamesFromTargetClass(targetClass); + String[] binNames = getBinNamesFromTargetClass(targetClass, mappingContext); recIterator = queryEngine.select(namespace, setName, binNames, query); } else { recIterator = queryEngine.select(namespace, setName, query); @@ -1468,7 +1456,7 @@ private List findByIdsWithoutMapping(Collection ids, String setNam Record[] aeroRecords; if (targetClass != null) { - String[] binNames = getBinNamesFromTargetClass(targetClass); + String[] binNames = getBinNamesFromTargetClass(targetClass, mappingContext); aeroRecords = getAerospikeClient().get(policy, keys, binNames); } else { aeroRecords = getAerospikeClient().get(policy, keys); 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 da9dc2c73..d2c4abc65 100644 --- a/src/main/java/org/springframework/data/aerospike/core/TemplateUtils.java +++ b/src/main/java/org/springframework/data/aerospike/core/TemplateUtils.java @@ -1,8 +1,13 @@ package org.springframework.data.aerospike.core; import lombok.experimental.UtilityClass; +import org.springframework.data.aerospike.mapping.AerospikePersistentEntity; +import org.springframework.data.aerospike.mapping.AerospikePersistentProperty; +import org.springframework.data.aerospike.mapping.BasicAerospikePersistentEntity; import org.springframework.data.aerospike.query.FilterOperation; import org.springframework.data.aerospike.query.qualifier.Qualifier; +import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.util.Assert; import java.util.ArrayList; @@ -93,4 +98,18 @@ private static Qualifier combineMultipleQualifiers(FilterOperation operation, Qu throw new UnsupportedOperationException("Only OR / AND operations are supported"); } } + + public static String[] getBinNamesFromTargetClass(Class targetClass, + MappingContext, + AerospikePersistentProperty> mappingContext) { + AerospikePersistentEntity targetEntity = mappingContext.getRequiredPersistentEntity(targetClass); + + List binNamesList = new ArrayList<>(); + + targetEntity.doWithProperties((PropertyHandler) property + -> binNamesList.add(property.getFieldName())); + + return binNamesList.toArray(new String[0]); + } + } 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 6311c4e60..72c86f4f0 100644 --- a/src/main/java/org/springframework/data/aerospike/query/FilterExpressionsBuilder.java +++ b/src/main/java/org/springframework/data/aerospike/query/FilterExpressionsBuilder.java @@ -20,30 +20,31 @@ import org.springframework.data.aerospike.query.qualifier.Qualifier; import org.springframework.data.aerospike.repository.query.Query; +import static org.springframework.data.aerospike.query.FilterOperation.dualFilterOperations; import static org.springframework.data.aerospike.query.QualifierUtils.queryCriteriaIsNotNull; public class FilterExpressionsBuilder { public Expression build(Query query) { Qualifier qualifier = queryCriteriaIsNotNull(query) ? query.getCriteriaObject() : null; - if (qualifier != null && excludeIrrelevantFilters(qualifier)) { - return Exp.build(qualifier.toFilterExp()); + if (qualifier != null && requiresFilterExp(qualifier)) { + return Exp.build(qualifier.getFilterExp()); } return null; } /** - * The filter allows only qualifiers without sIndexFilter and those with the dualFilterOperation that require both + * FilterExp is built only for a qualifier without sIndexFilter or for dualFilterOperation that requires both * sIndexFilter and FilterExpression. The filter is irrelevant for AND operation (nested qualifiers) */ - private boolean excludeIrrelevantFilters(Qualifier qualifier) { - if (!qualifier.queryAsFilter()) { + private boolean requiresFilterExp(Qualifier qualifier) { + if (!qualifier.hasSecIndexFilter()) { 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 + } else if (qualifier.hasSecIndexFilter() && dualFilterOperations.contains(qualifier.getOperation())) { + qualifier.setHasSecIndexFilter(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 + qualifier.setHasSecIndexFilter(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/FilterOperation.java b/src/main/java/org/springframework/data/aerospike/query/FilterOperation.java index de2fe215f..eb4cca68c 100644 --- a/src/main/java/org/springframework/data/aerospike/query/FilterOperation.java +++ b/src/main/java/org/springframework/data/aerospike/query/FilterOperation.java @@ -68,10 +68,10 @@ public Exp filterExp(Map qualifierMap) { } if (qs.length > 1) { for (int i = 0; i < qs.length; i++) { - childrenExp[i] = qs[i].toFilterExp(); + childrenExp[i] = qs[i].getFilterExp(); } } else { - return qs[0].toFilterExp(); + return qs[0].getFilterExp(); } return Exp.and(childrenExp); } @@ -93,10 +93,10 @@ public Exp filterExp(Map qualifierMap) { } if (qs.length > 1) { for (int i = 0; i < qs.length; i++) { - childrenExp[i] = qs[i].toFilterExp(); + childrenExp[i] = qs[i].getFilterExp(); } } else { - return qs[0].toFilterExp(); + return qs[0].getFilterExp(); } return Exp.or(childrenExp); } @@ -118,7 +118,7 @@ public Exp filterExp(Map qualifierMap) { .setFilterOperation(FilterOperation.EQ) .setValue(Value.get(item)) .build() - .toFilterExp() + .getFilterExp() ).toArray(Exp[]::new); return Exp.or(arrElementsExp); @@ -142,7 +142,7 @@ public Exp filterExp(Map qualifierMap) { .setFilterOperation(FilterOperation.NOTEQ) .setValue(Value.get(item)) .build() - .toFilterExp() + .getFilterExp() ).toArray(Exp[]::new); return Exp.and(arrElementsExp); @@ -184,15 +184,17 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { Value value = getValue(qualifierMap); - if (value.getType() == INTEGER) { - return Filter.equal(getField(qualifierMap), value.toLong()); - } else { - // There is no case-insensitive string comparison filter. - if (ignoreCase(qualifierMap)) { - return null; + return switch (value.getType()) { + case INTEGER -> Filter.equal(getField(qualifierMap), value.toLong()); + case STRING -> { + // There is no case-insensitive string comparison filter. + if (ignoreCase(qualifierMap)) { + yield null; + } + yield Filter.equal(getField(qualifierMap), value.toString()); } - return Filter.equal(getField(qualifierMap), value.toString()); - } + default -> null; + }; } }, NOTEQ { @@ -257,11 +259,11 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { // Long.MAX_VALUE shall not be given as + 1 will cause overflow - if (getKey(qualifierMap).getType() != INTEGER || getKey(qualifierMap).toLong() == Long.MAX_VALUE) { + if (getValue(qualifierMap).getType() != INTEGER || getValue(qualifierMap).toLong() == Long.MAX_VALUE) { return null; } - return Filter.range(getField(qualifierMap), getKey(qualifierMap).toLong() + 1, Long.MAX_VALUE); + return Filter.range(getField(qualifierMap), getValue(qualifierMap).toLong() + 1, Long.MAX_VALUE); } }, GTEQ { @@ -284,10 +286,10 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { - if (getKey(qualifierMap).getType() != INTEGER) { + if (getValue(qualifierMap).getType() != INTEGER) { return null; } - return Filter.range(getField(qualifierMap), getKey(qualifierMap).toLong(), Long.MAX_VALUE); + return Filter.range(getField(qualifierMap), getValue(qualifierMap).toLong(), Long.MAX_VALUE); } }, LT { @@ -311,10 +313,10 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { // Long.MIN_VALUE shall not be given as - 1 will cause overflow - if (getKey(qualifierMap).getType() != INTEGER || getKey(qualifierMap).toLong() == Long.MIN_VALUE) { + if (getValue(qualifierMap).getType() != INTEGER || getValue(qualifierMap).toLong() == Long.MIN_VALUE) { return null; } - return Filter.range(getField(qualifierMap), Long.MIN_VALUE, getKey(qualifierMap).toLong() - 1); + return Filter.range(getField(qualifierMap), Long.MIN_VALUE, getValue(qualifierMap).toLong() - 1); } }, LTEQ { @@ -337,10 +339,10 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { - if (getKey(qualifierMap).getType() != INTEGER) { + if (getValue(qualifierMap).getType() != INTEGER) { return null; } - return Filter.range(getField(qualifierMap), Long.MIN_VALUE, getKey(qualifierMap).toLong()); + return Filter.range(getField(qualifierMap), Long.MIN_VALUE, getValue(qualifierMap).toLong()); } }, BETWEEN { @@ -510,7 +512,7 @@ public Exp filterExp(Map qualifierMap) { .setKey(getKey(qualifierMap)) .setValue(Value.get(item)) .build() - .toFilterExp() + .getFilterExp() ).toArray(Exp[]::new); return Exp.or(arrElementsExp); @@ -533,7 +535,7 @@ public Exp filterExp(Map qualifierMap) { .setKey(getKey(qualifierMap)) .setValue(Value.get(item)) .build() - .toFilterExp() + .getFilterExp() ).toArray(Exp[]::new); return Exp.and(arrElementsExp); @@ -631,7 +633,7 @@ public Exp filterExp(Map qualifierMap) { */ @Override public Filter sIndexFilter(Map qualifierMap) { - if (getKey(qualifierMap).getType() != INTEGER) { + if (getValue(qualifierMap).getType() != INTEGER) { return null; } @@ -640,7 +642,7 @@ public Filter sIndexFilter(Map qualifierMap) { return null; // currently not supported } else { return Filter.range(getField(qualifierMap), IndexCollectionType.MAPVALUES, Long.MIN_VALUE, - getKey(qualifierMap).toLong()); + getValue(qualifierMap).toLong()); } } }, @@ -704,7 +706,7 @@ private static Exp mapValBetweenByKey(Map qualifierMap, St */ @Override public Filter sIndexFilter(Map qualifierMap) { - if (getKey(qualifierMap).getType() != INTEGER || getSecondValue(qualifierMap).getType() != INTEGER) { + if (getValue(qualifierMap).getType() != INTEGER || getSecondValue(qualifierMap).getType() != INTEGER) { return null; } @@ -713,7 +715,7 @@ public Filter sIndexFilter(Map qualifierMap) { return null; // currently not supported } else { return Filter.range(getField(qualifierMap), IndexCollectionType.MAPVALUES, - getKey(qualifierMap).toLong(), + getValue(qualifierMap).toLong(), getSecondValue(qualifierMap).toLong()); } } @@ -1127,26 +1129,26 @@ public Filter sIndexFilter(Map qualifierMap) { COLLECTION_VAL_GT { @Override public Exp filterExp(Map qualifierMap) { - if (getKey(qualifierMap).getType() == INTEGER) { - if (getKey(qualifierMap).toLong() == Long.MAX_VALUE) { + if (getValue(qualifierMap).getType() == INTEGER) { + if (getValue(qualifierMap).toLong() == Long.MAX_VALUE) { throw new IllegalArgumentException( "COLLECTION_VAL_GT FilterExpression unsupported value: expected [Long.MIN_VALUE.." + "Long.MAX_VALUE-1]"); } return Exp.gt( - ListExp.getByValueRange(ListReturnType.COUNT, Exp.val(getKey(qualifierMap).toLong() + 1L), + ListExp.getByValueRange(ListReturnType.COUNT, Exp.val(getValue(qualifierMap).toLong() + 1L), null, Exp.listBin(getField(qualifierMap))), Exp.val(0) ); } else { - Exp value = switch (getKey(qualifierMap).getType()) { - case STRING -> Exp.val(getKey(qualifierMap).toString()); - case LIST -> Exp.val((List) getKey(qualifierMap).getObject()); - case MAP -> Exp.val((Map) getKey(qualifierMap).getObject()); + Exp value = switch (getValue(qualifierMap).getType()) { + case STRING -> Exp.val(getValue(qualifierMap).toString()); + case LIST -> Exp.val((List) getValue(qualifierMap).getObject()); + case MAP -> Exp.val((Map) getValue(qualifierMap).getObject()); default -> throw new UnsupportedOperationException( "COLLECTION_VAL_GT FilterExpression unsupported type: got " - + getKey(qualifierMap).getClass().getSimpleName()); + + getValue(qualifierMap).getClass().getSimpleName()); }; Exp rangeIncludingValue = ListExp.getByValueRange(ListReturnType.COUNT, value, null, @@ -1159,25 +1161,25 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { // Long.MAX_VALUE shall not be given as + 1 will cause overflow - if (getKey(qualifierMap).getType() != INTEGER || getKey(qualifierMap).toLong() == Long.MAX_VALUE) { + if (getValue(qualifierMap).getType() != INTEGER || getValue(qualifierMap).toLong() == Long.MAX_VALUE) { return null; } return Filter.range(getField(qualifierMap), IndexCollectionType.LIST, - getKey(qualifierMap).toLong() + 1, Long.MAX_VALUE); + getValue(qualifierMap).toLong() + 1, Long.MAX_VALUE); } }, COLLECTION_VAL_GTEQ { @Override public Exp filterExp(Map qualifierMap) { - Exp value = switch (getKey(qualifierMap).getType()) { - case INTEGER -> Exp.val(getKey(qualifierMap).toLong()); - case STRING -> Exp.val(getKey(qualifierMap).toString()); - case LIST -> Exp.val((List) getKey(qualifierMap).getObject()); - case MAP -> Exp.val((Map) getKey(qualifierMap).getObject()); + Exp value = switch (getValue(qualifierMap).getType()) { + case INTEGER -> Exp.val(getValue(qualifierMap).toLong()); + case STRING -> Exp.val(getValue(qualifierMap).toString()); + case LIST -> Exp.val((List) getValue(qualifierMap).getObject()); + case MAP -> Exp.val((Map) getValue(qualifierMap).getObject()); default -> throw new UnsupportedOperationException( "COLLECTION_VAL_GTEQ FilterExpression unsupported type: got " - + getKey(qualifierMap).getClass().getSimpleName()); + + getValue(qualifierMap).getClass().getSimpleName()); }; return Exp.gt( @@ -1187,33 +1189,33 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { - if (getKey(qualifierMap).getType() != INTEGER) { + if (getValue(qualifierMap).getType() != INTEGER) { return null; } - return Filter.range(getField(qualifierMap), IndexCollectionType.LIST, getKey(qualifierMap).toLong(), + return Filter.range(getField(qualifierMap), IndexCollectionType.LIST, getValue(qualifierMap).toLong(), Long.MAX_VALUE); } }, COLLECTION_VAL_LT { @Override public Exp filterExp(Map qualifierMap) { - Exp value = switch (getKey(qualifierMap).getType()) { + Exp value = switch (getValue(qualifierMap).getType()) { case INTEGER -> { - if (getKey(qualifierMap).toLong() == Long.MIN_VALUE) { + if (getValue(qualifierMap).toLong() == Long.MIN_VALUE) { throw new UnsupportedOperationException( "COLLECTION_VAL_LT FilterExpression unsupported value: expected [Long.MIN_VALUE+1.." + "Long.MAX_VALUE]"); } - yield Exp.val(getKey(qualifierMap).toLong()); + yield Exp.val(getValue(qualifierMap).toLong()); } - case STRING -> Exp.val(getKey(qualifierMap).toString()); - case LIST -> Exp.val((List) getKey(qualifierMap).getObject()); - case MAP -> Exp.val((Map) getKey(qualifierMap).getObject()); + case STRING -> Exp.val(getValue(qualifierMap).toString()); + case LIST -> Exp.val((List) getValue(qualifierMap).getObject()); + case MAP -> Exp.val((Map) getValue(qualifierMap).getObject()); default -> throw new UnsupportedOperationException( "COLLECTION_VAL_GTEQ FilterExpression unsupported type: got " - + getKey(qualifierMap).getClass().getSimpleName()); + + getValue(qualifierMap).getClass().getSimpleName()); }; return Exp.gt( @@ -1224,23 +1226,23 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { // Long.MIN_VALUE shall not be given as - 1 will cause overflow - if (getKey(qualifierMap).getType() != INTEGER || getKey(qualifierMap).toLong() == Long.MIN_VALUE) { + if (getValue(qualifierMap).getType() != INTEGER || getValue(qualifierMap).toLong() == Long.MIN_VALUE) { return null; } return Filter.range(getField(qualifierMap), IndexCollectionType.LIST, Long.MIN_VALUE, - getKey(qualifierMap).toLong() - 1); + getValue(qualifierMap).toLong() - 1); } }, COLLECTION_VAL_LTEQ { @Override public Exp filterExp(Map qualifierMap) { - if (getKey(qualifierMap).getType() == INTEGER) { + if (getValue(qualifierMap).getType() == INTEGER) { Exp upperLimit; - if (getKey(qualifierMap).toLong() == Long.MAX_VALUE) { + if (getValue(qualifierMap).toLong() == Long.MAX_VALUE) { upperLimit = Exp.inf(); } else { - upperLimit = Exp.val(getKey(qualifierMap).toLong() + 1L); + upperLimit = Exp.val(getValue(qualifierMap).toLong() + 1L); } return Exp.gt( @@ -1248,13 +1250,13 @@ public Exp filterExp(Map qualifierMap) { upperLimit, Exp.listBin(getField(qualifierMap))), Exp.val(0)); } else { - Exp value = switch (getKey(qualifierMap).getType()) { - case STRING -> Exp.val(getKey(qualifierMap).toString()); - case LIST -> Exp.val((List) getKey(qualifierMap).getObject()); - case MAP -> Exp.val((Map) getKey(qualifierMap).getObject()); + Exp value = switch (getValue(qualifierMap).getType()) { + case STRING -> Exp.val(getValue(qualifierMap).toString()); + case LIST -> Exp.val((List) getValue(qualifierMap).getObject()); + case MAP -> Exp.val((Map) getValue(qualifierMap).getObject()); default -> throw new UnsupportedOperationException( "COLLECTION_VAL_LTEQ FilterExpression unsupported type: got " + - getKey(qualifierMap).getClass().getSimpleName()); + getValue(qualifierMap).getClass().getSimpleName()); }; Exp rangeIncludingValue = ListExp.getByValueRange(ListReturnType.COUNT, null, value, @@ -1266,12 +1268,12 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { - if (getKey(qualifierMap).getType() != INTEGER) { + if (getValue(qualifierMap).getType() != INTEGER) { return null; } return Filter.range(getField(qualifierMap), IndexCollectionType.LIST, Long.MIN_VALUE, - getKey(qualifierMap).toLong()); + getValue(qualifierMap).toLong()); } }, IS_NOT_NULL { @Override @@ -1332,7 +1334,7 @@ private static Exp processMetadataFieldInOrNot(Map qualifi .setFilterOperation(filterOperation) .setValueAsObj(item) .build() - .toFilterExp() + .getFilterExp() ).toArray(Exp[]::new); return notIn ? Exp.and(listElementsExp) : Exp.or(listElementsExp); diff --git a/src/main/java/org/springframework/data/aerospike/query/QueryEngine.java b/src/main/java/org/springframework/data/aerospike/query/QueryEngine.java index 4f32051af..5bf71225a 100644 --- a/src/main/java/org/springframework/data/aerospike/query/QueryEngine.java +++ b/src/main/java/org/springframework/data/aerospike/query/QueryEngine.java @@ -46,6 +46,7 @@ public class QueryEngine { "disabled by default in spring-data-aerospike. " + "If you still need to use them, enable them via `scans-enabled` property."; private final IAerospikeClient client; + @Getter private final StatementBuilder statementBuilder; @Getter private final FilterExpressionsBuilder filterExpressionsBuilder; diff --git a/src/main/java/org/springframework/data/aerospike/query/ReactorQueryEngine.java b/src/main/java/org/springframework/data/aerospike/query/ReactorQueryEngine.java index 54febf1e3..b47c3d023 100644 --- a/src/main/java/org/springframework/data/aerospike/query/ReactorQueryEngine.java +++ b/src/main/java/org/springframework/data/aerospike/query/ReactorQueryEngine.java @@ -44,6 +44,7 @@ public class ReactorQueryEngine { private final IAerospikeReactorClient client; + @Getter private final StatementBuilder statementBuilder; @Getter private final FilterExpressionsBuilder filterExpressionsBuilder; 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 d655c8ddd..fef06979b 100644 --- a/src/main/java/org/springframework/data/aerospike/query/StatementBuilder.java +++ b/src/main/java/org/springframework/data/aerospike/query/StatementBuilder.java @@ -101,10 +101,10 @@ private void setFilterFromMultipleQualifiers(Statement stmt, Qualifier qualifier } else { // No index with bin values ratio found, do not consider cardinality when setting a filter for (Qualifier innerQualifier : qualifier.getQualifiers()) { if (innerQualifier != null && isIndexedBin(stmt, innerQualifier)) { - Filter filter = innerQualifier.setQueryAsFilter(); + Filter filter = innerQualifier.getSecondaryIndexFilter(); if (filter != null) { stmt.setFilter(filter); - innerQualifier.setQueryAsFilter(true); + innerQualifier.setHasSecIndexFilter(true); break; // the filter from the first processed qualifier becomes statement's sIndex filter } } @@ -113,10 +113,10 @@ private void setFilterFromMultipleQualifiers(Statement stmt, Qualifier qualifier } private void setFilterFromSingleQualifier(Statement stmt, Qualifier qualifier) { - Filter filter = qualifier.setQueryAsFilter(); + Filter filter = qualifier.getSecondaryIndexFilter(); if (filter != null) { stmt.setFilter(filter); - qualifier.setQueryAsFilter(true); + qualifier.setHasSecIndexFilter(true); } } @@ -137,7 +137,7 @@ private boolean isIndexedBin(Statement stmt, Qualifier qualifier) { } private int getMinBinValuesRatioForQualifier(Statement stmt, Qualifier qualifier) { - // Get all indexes that uses this field + // Get all indexes for field List indexList = indexesCache.getAllIndexesForField( new IndexedField(stmt.getNamespace(), stmt.getSetName(), qualifier.getField())); diff --git a/src/main/java/org/springframework/data/aerospike/query/qualifier/Qualifier.java b/src/main/java/org/springframework/data/aerospike/query/qualifier/Qualifier.java index cd8ba229c..41f17000f 100644 --- a/src/main/java/org/springframework/data/aerospike/query/qualifier/Qualifier.java +++ b/src/main/java/org/springframework/data/aerospike/query/qualifier/Qualifier.java @@ -95,12 +95,12 @@ public CriteriaDefinition.AerospikeMetadata getMetadataField() { return (CriteriaDefinition.AerospikeMetadata) internalMap.get(METADATA_FIELD); } - public void setQueryAsFilter(Boolean queryAsFilter) { - internalMap.put(AS_FILTER, queryAsFilter); + public void setHasSecIndexFilter(Boolean queryAsFilter) { + internalMap.put(HAS_SINDEX_FILTER, queryAsFilter); } - public Boolean queryAsFilter() { - return internalMap.containsKey(AS_FILTER) && (Boolean) internalMap.get(AS_FILTER); + public Boolean hasSecIndexFilter() { + return internalMap.containsKey(HAS_SINDEX_FILTER) && (Boolean) internalMap.get(HAS_SINDEX_FILTER); } public void setDataSettings(AerospikeDataSettings dataSettings) { @@ -144,11 +144,11 @@ public List getDotPath() { return (List) internalMap.get(DOT_PATH); } - public Filter setQueryAsFilter() { + public Filter getSecondaryIndexFilter() { return FilterOperation.valueOf(getOperation().toString()).sIndexFilter(internalMap); } - public Exp toFilterExp() { + public Exp getFilterExp() { return FilterOperation.valueOf(getOperation().toString()).filterExp(internalMap); } diff --git a/src/main/java/org/springframework/data/aerospike/query/qualifier/QualifierKey.java b/src/main/java/org/springframework/data/aerospike/query/qualifier/QualifierKey.java index a411ceef4..16bf2cd15 100644 --- a/src/main/java/org/springframework/data/aerospike/query/qualifier/QualifierKey.java +++ b/src/main/java/org/springframework/data/aerospike/query/qualifier/QualifierKey.java @@ -20,5 +20,5 @@ public enum QualifierKey { QUALIFIERS, OPERATION, DIGEST_KEY, - AS_FILTER + HAS_SINDEX_FILTER } diff --git a/src/test/java/org/springframework/data/aerospike/BaseBlockingIntegrationTests.java b/src/test/java/org/springframework/data/aerospike/BaseBlockingIntegrationTests.java index 475ff175b..f2dcf447c 100644 --- a/src/test/java/org/springframework/data/aerospike/BaseBlockingIntegrationTests.java +++ b/src/test/java/org/springframework/data/aerospike/BaseBlockingIntegrationTests.java @@ -1,23 +1,38 @@ package org.springframework.data.aerospike; import com.aerospike.client.IAerospikeClient; +import com.aerospike.client.query.Statement; +import org.junit.jupiter.api.TestInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.env.Environment; import org.springframework.data.aerospike.config.BlockingTestConfig; import org.springframework.data.aerospike.config.CommonTestConfig; +import org.springframework.data.aerospike.config.IndexedBinsAnnotationsProcessor; import org.springframework.data.aerospike.core.AerospikeTemplate; +import org.springframework.data.aerospike.mapping.AerospikePersistentProperty; +import org.springframework.data.aerospike.mapping.BasicAerospikePersistentEntity; import org.springframework.data.aerospike.query.FilterOperation; import org.springframework.data.aerospike.query.QueryEngine; import org.springframework.data.aerospike.query.cache.IndexRefresher; import org.springframework.data.aerospike.query.cache.IndexesCache; +import org.springframework.data.aerospike.query.model.IndexedField; import org.springframework.data.aerospike.query.qualifier.Qualifier; import org.springframework.data.aerospike.repository.query.Query; import org.springframework.data.aerospike.server.version.ServerVersionSupport; +import org.springframework.data.aerospike.util.QueryUtils; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.StringUtils; import java.util.Collection; import java.util.List; +import static java.util.function.Predicate.not; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.aerospike.config.IndexedBinsAnnotationsProcessor.getBinNames; +import static org.springframework.data.aerospike.config.IndexedBinsAnnotationsProcessor.getEntityClass; +import static org.springframework.data.aerospike.config.IndexedBinsAnnotationsProcessor.hasAssertBinsAreIndexedAnnotation; +import static org.springframework.data.aerospike.core.TemplateUtils.getBinNamesFromTargetClass; import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeMetadata.LAST_UPDATE_TIME; @SpringBootTest( @@ -44,6 +59,8 @@ public abstract class BaseBlockingIntegrationTests extends BaseIntegrationTests protected IndexRefresher indexRefresher; @Autowired protected Environment env; + @Autowired + protected MappingContext, AerospikePersistentProperty> mappingContext; protected void deleteOneByOne(Collection collection) { collection.forEach(item -> template.delete(item)); @@ -62,4 +79,63 @@ protected List runLastUpdateTimeQuery(long lastUpdateTimeMillis, FilterOp .build(); return template.find(new Query(lastUpdateTimeLtMillis), entityClass).toList(); } + + protected boolean isIndexedBin(String namespace, String setName, String binName) { + boolean hasIndex = false; + if (StringUtils.hasLength(binName)) { + hasIndex = indexesCache.hasIndexFor( + new IndexedField(namespace, setName, binName) + ); + } + return hasIndex; + } + + protected void assertBinIsIndexed(String binName, Class clazz) { + assertThat(isIndexedBin(getNameSpace(), template.getSetName(clazz), binName)) + .as(String.format("Expecting bin %s to be indexed", binName)).isTrue(); + } + + protected void assertBinsAreIndexed(TestInfo testInfo) { + testInfo.getTestMethod().stream() + .filter(not(IndexedBinsAnnotationsProcessor::hasNoindexAnnotation)) + .forEach(testMethod -> { + assertThat(hasAssertBinsAreIndexedAnnotation(testMethod)) + .as(String.format("Expecting the test method %s to have @AssertBinsAreIndexed annotation", + testMethod.getName())) + .isTrue(); + String[] binNames = getBinNames(testMethod); + Class entityClass = getEntityClass(testMethod); + assertThat(binNames).as("Expecting bin names to be populated").isNotNull(); + assertThat(binNames).as("Expecting bin names ").isNotEmpty(); + assertThat(entityClass).as("Expecting entityClass to be populated").isNotNull(); + + for (String binName : binNames) { + assertBinIsIndexed(binName, entityClass); + } + }); + } + + /** + * Assert that the given query statement contains secondary index filter + * + * @param methodName Query method to be performed + * @param returnEntityClass Class of Query return entity + * @param methodParams Query parameters + */ + protected void assertStmtHasSecIndexFilter(String methodName, Class returnEntityClass, + Object... methodParams) { + assertThat(stmtHasSecIndexFilter(methodName, returnEntityClass, methodParams)) + .as(String.format("Expecting the query %s statement to have secondary index filter", methodName)).isTrue(); + } + + protected boolean stmtHasSecIndexFilter(String methodName, Class returnTypeClass, + Object... methodParams) { + String setName = template.getSetName(returnTypeClass); + String[] binNames = getBinNamesFromTargetClass(returnTypeClass, mappingContext); + Query query = QueryUtils.createQueryForMethodWithArgs(methodName, methodParams); + + Statement statement = queryEngine.getStatementBuilder().build(namespace, setName, query, binNames); + // Checking that the statement has secondary index filter (which means it will be used) + return statement.getFilter() != null; + } } diff --git a/src/test/java/org/springframework/data/aerospike/BaseReactiveIntegrationTests.java b/src/test/java/org/springframework/data/aerospike/BaseReactiveIntegrationTests.java index 8afab9c2a..02b8414cc 100644 --- a/src/test/java/org/springframework/data/aerospike/BaseReactiveIntegrationTests.java +++ b/src/test/java/org/springframework/data/aerospike/BaseReactiveIntegrationTests.java @@ -1,21 +1,40 @@ package org.springframework.data.aerospike; +import com.aerospike.client.query.Statement; import com.aerospike.client.reactor.IAerospikeReactorClient; +import org.junit.jupiter.api.TestInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.aerospike.config.CommonTestConfig; import org.springframework.data.aerospike.config.ReactiveTestConfig; import org.springframework.data.aerospike.core.ReactiveAerospikeTemplate; +import org.springframework.data.aerospike.mapping.AerospikePersistentProperty; +import org.springframework.data.aerospike.mapping.BasicAerospikePersistentEntity; import org.springframework.data.aerospike.query.FilterOperation; +import org.springframework.data.aerospike.query.ReactorQueryEngine; +import org.springframework.data.aerospike.query.cache.IndexesCache; import org.springframework.data.aerospike.query.cache.ReactorIndexRefresher; +import org.springframework.data.aerospike.query.model.IndexedField; import org.springframework.data.aerospike.query.qualifier.Qualifier; import org.springframework.data.aerospike.repository.query.Query; +import org.springframework.data.aerospike.sample.ReactiveIndexedPersonRepository; import org.springframework.data.aerospike.server.version.ServerVersionSupport; +import org.springframework.data.aerospike.util.QueryUtils; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; import java.io.Serializable; +import java.lang.reflect.Method; import java.util.List; +import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.aerospike.config.IndexedBinsAnnotationsProcessor.getBinNames; +import static org.springframework.data.aerospike.config.IndexedBinsAnnotationsProcessor.getEntityClass; +import static org.springframework.data.aerospike.config.IndexedBinsAnnotationsProcessor.hasAssertBinsAreIndexedAnnotation; +import static org.springframework.data.aerospike.config.IndexedBinsAnnotationsProcessor.hasNoindexAnnotation; +import static org.springframework.data.aerospike.core.TemplateUtils.getBinNamesFromTargetClass; import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeMetadata.LAST_UPDATE_TIME; @SpringBootTest( @@ -35,7 +54,14 @@ public abstract class BaseReactiveIntegrationTests extends BaseIntegrationTests @Autowired protected ServerVersionSupport serverVersionSupport; @Autowired + protected + ReactorQueryEngine reactiveQueryEngine; + @Autowired protected ReactorIndexRefresher reactorIndexRefresher; + @Autowired + protected IndexesCache indexesCache; + @Autowired + protected MappingContext, AerospikePersistentProperty> mappingContext; protected T findById(Serializable id, Class type) { return reactiveTemplate.findById(id, type).block(); @@ -62,4 +88,66 @@ protected List runLastUpdateTimeQuery(long lastUpdateTimeMillis, FilterOp .build(); return reactiveTemplate.find(new Query(lastUpdateTimeLtMillis), entityClass).collectList().block(); } + + protected boolean isIndexedBin(String namespace, String setName, String binName) { + boolean hasIndex = false; + if (StringUtils.hasLength(binName)) { + hasIndex = indexesCache.hasIndexFor( + new IndexedField(namespace, setName, binName) + ); + } + return hasIndex; + } + + protected void assertBinIsIndexed(String binName, Class clazz) { + assertThat(isIndexedBin(getNameSpace(), reactiveTemplate.getSetName(clazz), binName)) + .as(String.format("Expecting bin %s to be indexed", binName)).isTrue(); + } + + protected void assertBinsAreIndexed(TestInfo testInfo) { + Optional testMethodOptional = testInfo.getTestMethod(); + if (testMethodOptional.isPresent()) { + Method testMethod = testMethodOptional.get(); + if (!hasNoindexAnnotation(testMethod)) { + assertThat(hasAssertBinsAreIndexedAnnotation(testMethod)) + .as(String.format("Expecting the test method %s to have @AssertBinsAreIndexed annotation", + testMethod.getName())). + isTrue(); + String[] binNames = getBinNames(testMethod); + Class entityClass = getEntityClass(testMethod); + assertThat(binNames).as("Expecting bin names to be populated").isNotNull(); + assertThat(binNames).as("Expecting bin names ").isNotEmpty(); + assertThat(entityClass).as("Expecting entityClass to be populated").isNotNull(); + + for (String binName : binNames) { + assertBinIsIndexed(binName, entityClass); + } + } + } + } + + /** + * Assert that the given query statement contains secondary index filter + * + * @param methodName Query method to be performed + * @param returnEntityClass Class of Query return entity + * @param methodParams Query parameters + */ + protected void assertStmtHasSecIndexFilter(String methodName, Class returnEntityClass, + Object... methodParams) { + assertThat(stmtHasSecIndexFilter(methodName, returnEntityClass, methodParams)) + .as(String.format("Expecting the query %s statement to have secondary index filter", methodName)).isTrue(); + } + + protected boolean stmtHasSecIndexFilter(String methodName, Class returnEntityClass, + Object... methodParams) { + String setName = reactiveTemplate.getSetName(returnEntityClass); + String[] binNames = getBinNamesFromTargetClass(returnEntityClass, mappingContext); + Query query = QueryUtils.createQueryForMethodWithArgs(ReactiveIndexedPersonRepository.class, returnEntityClass, + methodName, methodParams); + + Statement statement = reactiveQueryEngine.getStatementBuilder().build(namespace, setName, query, binNames); + // Checking that the statement has secondary index filter (which means it will be used) + return statement.getFilter() != null; + } } diff --git a/src/test/java/org/springframework/data/aerospike/config/AssertBinsAreIndexed.java b/src/test/java/org/springframework/data/aerospike/config/AssertBinsAreIndexed.java new file mode 100644 index 000000000..837600fe0 --- /dev/null +++ b/src/test/java/org/springframework/data/aerospike/config/AssertBinsAreIndexed.java @@ -0,0 +1,18 @@ +package org.springframework.data.aerospike.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Assert that the given bin names are indexed. Opposite to {@link NoSecondaryIndexRequired} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface AssertBinsAreIndexed { + + String[] binNames(); + + Class entityClass(); +} diff --git a/src/test/java/org/springframework/data/aerospike/config/BlockingTestConfig.java b/src/test/java/org/springframework/data/aerospike/config/BlockingTestConfig.java index d5870d6ec..d72ff7bbe 100644 --- a/src/test/java/org/springframework/data/aerospike/config/BlockingTestConfig.java +++ b/src/test/java/org/springframework/data/aerospike/config/BlockingTestConfig.java @@ -60,4 +60,9 @@ public AdditionalAerospikeTestOperations aerospikeOperations(AerospikeTemplate t public IAerospikeClient aerospikeClient(AerospikeSettings settings) { return new AerospikeClient(getClientPolicy(), settings.getConnectionSettings().getHostsArray()); } + + @Bean + public IndexedBinsAnnotationsProcessor someAnnotationProcessor() { + return new IndexedBinsAnnotationsProcessor(); + } } diff --git a/src/test/java/org/springframework/data/aerospike/config/IndexedBinsAnnotationsProcessor.java b/src/test/java/org/springframework/data/aerospike/config/IndexedBinsAnnotationsProcessor.java new file mode 100644 index 000000000..2dd06b532 --- /dev/null +++ b/src/test/java/org/springframework/data/aerospike/config/IndexedBinsAnnotationsProcessor.java @@ -0,0 +1,32 @@ +package org.springframework.data.aerospike.config; + +import java.lang.reflect.Method; + +public class IndexedBinsAnnotationsProcessor { + + public static boolean hasAssertBinsAreIndexedAnnotation(Method testMethod) { + AssertBinsAreIndexed annotation = testMethod.getAnnotation(AssertBinsAreIndexed.class); + return annotation != null; + } + + public static boolean hasNoindexAnnotation(Method testMethod) { + NoSecondaryIndexRequired annotation = testMethod.getAnnotation(NoSecondaryIndexRequired.class); + return annotation != null; + } + + public static String[] getBinNames(Method testMethod) { + AssertBinsAreIndexed annotation = testMethod.getAnnotation(AssertBinsAreIndexed.class); + if (annotation != null) { + return annotation.binNames(); + } + return null; + } + + public static Class getEntityClass(Method testMethod) { + AssertBinsAreIndexed annotation = testMethod.getAnnotation(AssertBinsAreIndexed.class); + if (annotation != null) { + return annotation.entityClass(); + } + return null; + } +} diff --git a/src/test/java/org/springframework/data/aerospike/config/NoSecondaryIndexRequired.java b/src/test/java/org/springframework/data/aerospike/config/NoSecondaryIndexRequired.java new file mode 100644 index 000000000..d9a576664 --- /dev/null +++ b/src/test/java/org/springframework/data/aerospike/config/NoSecondaryIndexRequired.java @@ -0,0 +1,15 @@ +package org.springframework.data.aerospike.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mark test method as not requiring indexed bin names. Opposite to {@link AssertBinsAreIndexed} + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface NoSecondaryIndexRequired { + +} diff --git a/src/test/java/org/springframework/data/aerospike/query/reactive/BaseReactiveQueryEngineTests.java b/src/test/java/org/springframework/data/aerospike/query/reactive/BaseReactiveQueryEngineTests.java index fb1bee374..d6b4b9b81 100644 --- a/src/test/java/org/springframework/data/aerospike/query/reactive/BaseReactiveQueryEngineTests.java +++ b/src/test/java/org/springframework/data/aerospike/query/reactive/BaseReactiveQueryEngineTests.java @@ -8,14 +8,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.aerospike.BaseReactiveIntegrationTests; import org.springframework.data.aerospike.query.QueryEngineTestDataPopulator; -import org.springframework.data.aerospike.query.ReactorQueryEngine; import org.springframework.data.aerospike.query.cache.ReactorIndexRefresher; import reactor.core.publisher.Mono; public abstract class BaseReactiveQueryEngineTests extends BaseReactiveIntegrationTests { - @Autowired - ReactorQueryEngine queryEngine; @Autowired QueryEngineTestDataPopulator queryEngineTestDataPopulator; @Autowired diff --git a/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveIndexedQualifierTests.java b/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveIndexedQualifierTests.java index 992180c13..1b684444e 100644 --- a/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveIndexedQualifierTests.java +++ b/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveIndexedQualifierTests.java @@ -63,7 +63,7 @@ public void selectOnIndexedLTQualifier() { .setValue(Value.get(26)) .build(); - Flux flux = queryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); + Flux flux = reactiveQueryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { @@ -95,7 +95,7 @@ public void selectOnIndexedLTEQQualifier() { .setValue(Value.get(26)) .build(); - Flux flux = queryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); + Flux flux = reactiveQueryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { @@ -130,7 +130,7 @@ public void selectOnIndexedNumericEQQualifier() { .setValue(Value.get(26)) .build(); - Flux flux = queryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); + Flux flux = reactiveQueryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -154,7 +154,7 @@ public void selectOnIndexedGTEQQualifier() { .setValue(Value.get(28)) .build(); - Flux flux = queryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); + Flux flux = reactiveQueryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { @@ -189,7 +189,7 @@ public void selectOnIndexedGTQualifier() { .setValue(Value.get(28)) .build(); - Flux flux = queryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); + Flux flux = reactiveQueryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { @@ -212,7 +212,7 @@ public void selectOnIndexedStringEQQualifier() { .setValue(Value.get(ORANGE)) .build(); - Flux flux = queryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); + Flux flux = reactiveQueryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(qualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { @@ -235,7 +235,7 @@ public void selectWithBlueColorQuery() { .setValue(Value.get(BLUE)) .build(); - Flux flux = queryEngine.select(namespace, INDEXED_SET_NAME, new Query(qual1)); + Flux flux = reactiveQueryEngine.select(namespace, INDEXED_SET_NAME, new Query(qual1)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -265,7 +265,7 @@ public void selectWithQualifiersOnly() { .setValue(Value.get(29)) .build(); - Flux flux = queryEngine.select(namespace, INDEXED_SET_NAME, null, + Flux flux = reactiveQueryEngine.select(namespace, INDEXED_SET_NAME, null, new Query(Qualifier.and(qual1, qual2))); StepVerifier.create(flux.collectList()) @@ -296,7 +296,7 @@ public void selectWithGeoWithin() { .setValue(Value.getAsGeoJSON(rgnstr)) .build(); - Flux flux = queryEngine.select(namespace, INDEXED_GEO_SET, null, new Query(qualifier)); + Flux flux = reactiveQueryEngine.select(namespace, INDEXED_GEO_SET, null, new Query(qualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { diff --git a/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveQualifierTests.java b/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveQualifierTests.java index fbf759b30..cd26f4add 100644 --- a/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveQualifierTests.java +++ b/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveQualifierTests.java @@ -63,7 +63,7 @@ public void dropIndexes() { @Test void throwsExceptionWhenScansDisabled() { - queryEngine.setScansEnabled(false); + reactiveQueryEngine.setScansEnabled(false); try { Qualifier qualifier = Qualifier.builder() .setField("age") @@ -71,13 +71,13 @@ void throwsExceptionWhenScansDisabled() { .setValue(Value.get(26)) .build(); - StepVerifier.create(queryEngine.select(namespace, SET_NAME, null, new Query(qualifier))) + StepVerifier.create(reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(qualifier))) .expectErrorSatisfies(e -> assertThat(e) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("disabled by default")) .verify(); } finally { - queryEngine.setScansEnabled(true); + reactiveQueryEngine.setScansEnabled(true); } } @@ -90,7 +90,7 @@ public void lTQualifier() { .setValue(Value.get(26)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -114,7 +114,7 @@ public void numericLTEQQualifier() { .setValue(Value.get(26)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { AtomicInteger age25Count = new AtomicInteger(); @@ -145,7 +145,7 @@ public void numericEQQualifier() { .setValue(Value.get(26)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { @@ -166,7 +166,7 @@ public void numericGTEQQualifier() { .setValue(Value.get(28)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { AtomicInteger age28Count = new AtomicInteger(); @@ -197,7 +197,7 @@ public void numericGTQualifier() { .setValue(Value.get(28)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -216,7 +216,7 @@ public void stringEQQualifier() { .setValue(Value.get(ORANGE)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -236,7 +236,7 @@ public void stringEQQualifierCaseSensitive() { .setValue(Value.get(ORANGE.toUpperCase())) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -257,7 +257,7 @@ public void stringStartWithQualifier() { .setValue(Value.get("blu")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -276,7 +276,7 @@ public void stringStartWithEntireWordQualifier() { .setValue(Value.get(BLUE)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -298,7 +298,7 @@ public void stringStartWithICASEQualifier() { .setValue(Value.get("BLU")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -319,7 +319,7 @@ public void stringEndsWithQualifier() { .setValue(Value.get(greenEnding)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -338,7 +338,7 @@ public void stringEndsWithEntireWordQualifier() { .setValue(Value.get(GREEN)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(stringEqQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -359,7 +359,7 @@ public void betweenQualifier() { .setSecondValue(Value.get(29)) // + 1 as upper limit is exclusive .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { AtomicInteger age26Count = new AtomicInteger(); @@ -396,7 +396,7 @@ public void containingQualifier() { .setValue(Value.get("l")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { Map actualColors = results.stream() @@ -420,7 +420,7 @@ public void inQualifier() { .setValue(Value.get(inColours)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(qualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(qualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { Map actualColors = results.stream() @@ -444,7 +444,7 @@ public void listContainsQualifier() { .setValue(Value.get(searchColor)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { // Every Record with a color == "color" has a one element list ["color"] @@ -474,7 +474,7 @@ public void mapKeysContainQualifier() { .setValue(Value.get(searchColor)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { // Every Record with a color == "color" has a one element map {"color" => #} @@ -503,7 +503,7 @@ public void testMapValuesContainQualifier() { .setValue(Value.get(searchColor)) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { // Every Record with a color == "color" has a one element map {"color" => #} @@ -528,7 +528,8 @@ public void testContainingDoesNotUseSpecialCharacterQualifier() { .setValue(Value.get(".*")) .build(); - Flux flux = queryEngine.select(namespace, SPECIAL_CHAR_SET, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SPECIAL_CHAR_SET, null, + new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -547,7 +548,8 @@ public void testStartWithDoesNotUseSpecialCharacterQualifier() { .setValue(Value.get(".*")) .build(); - Flux flux = queryEngine.select(namespace, SPECIAL_CHAR_SET, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SPECIAL_CHAR_SET, null, + new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -569,7 +571,8 @@ public void testEndWithDoesNotUseSpecialCharacterQualifier() { .setValue(Value.get(".*")) .build(); - Flux flux = queryEngine.select(namespace, SPECIAL_CHAR_SET, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SPECIAL_CHAR_SET, null, + new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -592,7 +595,8 @@ public void testEQICaseDoesNotUseSpecialCharacter() { .setValue(Value.get(".*")) .build(); - Flux flux = queryEngine.select(namespace, SPECIAL_CHAR_SET, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SPECIAL_CHAR_SET, null, + new Query(ageRangeQualifier)); StepVerifier.create(flux) .verifyComplete(); } @@ -608,7 +612,8 @@ public void testContainingFindsSquareBracket() { .setValue(Value.get(specialString)) .build(); - Flux flux = queryEngine.select(namespace, SPECIAL_CHAR_SET, null, new Query(ageRangeQualifier)); + Flux flux = reactiveQueryEngine.select(namespace, SPECIAL_CHAR_SET, null, + new Query(ageRangeQualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -637,7 +642,8 @@ public void stringEqualIgnoreCaseWorksOnIndexedBin() { .setValue(Value.get("BlUe")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(caseInsensitiveQual)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, + new Query(caseInsensitiveQual)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -670,7 +676,7 @@ public void selectWithOrQualifiers() { Qualifier or = Qualifier.or(qual1, qual2); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(or)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(or)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { AtomicInteger colorMatched = new AtomicInteger(); @@ -725,7 +731,7 @@ public void selectWithBetweenAndOrQualifiers() { Qualifier or2 = Qualifier.or(qualColorIsGreen, qualNameIs696); Qualifier and = Qualifier.and(or, or2); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(and)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(and)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { AtomicBoolean has25 = new AtomicBoolean(false); diff --git a/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveSelectorTests.java b/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveSelectorTests.java index 2ed14dc0b..500af9eee 100644 --- a/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveSelectorTests.java +++ b/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveSelectorTests.java @@ -26,7 +26,7 @@ public class ReactiveSelectorTests extends BaseReactiveQueryEngineTests { @Test public void selectOneWithKey() { KeyQualifier kq = new KeyQualifier(Value.get("selector-test:3")); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(kq)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(kq)); StepVerifier.create(flux) .expectNextCount(1) @@ -36,7 +36,7 @@ public void selectOneWithKey() { @Test public void selectOneWithNonExistingKey() { KeyQualifier kq = new KeyQualifier(Value.get("selector-test:no-such-record")); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(kq)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(kq)); StepVerifier.create(flux) .expectNextCount(0) @@ -45,7 +45,7 @@ public void selectOneWithNonExistingKey() { @Test public void selectAll() { - Flux flux = queryEngine.select(namespace, SET_NAME, null, null); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, null); StepVerifier.create(flux) .expectNextCount(RECORD_COUNT) @@ -60,7 +60,7 @@ public void selectEndsWith() { .setValue(Value.get("e")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(qual1)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(qual1)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -79,7 +79,7 @@ public void selectStartsWith() { .setValue(Value.get("bl")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(startsWithQual)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(startsWithQual)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -107,7 +107,8 @@ public void startWithAndEqualIgnoreCaseReturnsAllItems() { .setValue(Value.get("NA")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(Qualifier.and(qual1, qual2))); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(Qualifier.and(qual1, + qual2))); StepVerifier.create(flux) .expectNextCount(queryEngineTestDataPopulator.colourCounts.get("blue")) .verifyComplete(); @@ -123,7 +124,7 @@ public void equalIgnoreCaseReturnsNoItemsIfNoneMatched() { .setValue(Value.get("BLUE")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(qual1)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(qual1)); StepVerifier.create(flux) .expectNextCount(0) .verifyComplete(); @@ -139,7 +140,7 @@ public void startWithIgnoreCaseReturnsNoItemsIfNoneMatched() { .setValue(Value.get("NA")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(qual1)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(qual1)); StepVerifier.create(flux) .expectNextCount(0) .verifyComplete(); @@ -157,7 +158,7 @@ public void stringEqualIgnoreCaseWorksOnUnindexedBin() { .setValue(Value.get("BlUe")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(caseInsensitiveQual)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(caseInsensitiveQual)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) @@ -178,7 +179,7 @@ public void stringEqualIgnoreCaseWorksRequiresFullMatch() { .setValue(Value.get("lue")) .build(); - Flux flux = queryEngine.select(namespace, SET_NAME, null, new Query(caseInsensitiveQual)); + Flux flux = reactiveQueryEngine.select(namespace, SET_NAME, null, new Query(caseInsensitiveQual)); StepVerifier.create(flux) .expectNextCount(0) @@ -199,7 +200,7 @@ public void selectWithGeoWithin() { .setValue(Value.getAsGeoJSON(rgnstr)) .build(); - Flux flux = queryEngine.select(namespace, GEO_SET, null, new Query(qual1)); + Flux flux = reactiveQueryEngine.select(namespace, GEO_SET, null, new Query(qual1)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { assertThat(results) diff --git a/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveUsersTests.java b/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveUsersTests.java index 8d76a8311..02496c5c4 100644 --- a/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveUsersTests.java +++ b/src/test/java/org/springframework/data/aerospike/query/reactive/ReactiveUsersTests.java @@ -22,7 +22,7 @@ public void usersInNorthRegion() { .setValue(Value.get("n")) .build(); - Flux flux = queryEngine.select(namespace, USERS_SET, null, new Query(qualifier)); + Flux flux = reactiveQueryEngine.select(namespace, USERS_SET, null, new Query(qualifier)); StepVerifier.create(flux.collectList()) .expectNextMatches(results -> { diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/IndexedPersonRepositoryQueryTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/IndexedPersonRepositoryQueryTests.java index 79ca46cac..32e30b60a 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/IndexedPersonRepositoryQueryTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/IndexedPersonRepositoryQueryTests.java @@ -5,6 +5,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.aerospike.BaseBlockingIntegrationTests; @@ -26,6 +28,11 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class IndexedPersonRepositoryQueryTests extends BaseBlockingIntegrationTests { + @BeforeEach + public void beforeEach(TestInfo testInfo) { + assertBinsAreIndexed(testInfo); + } + @Autowired protected IndexedPersonRepository repository; protected static final IndexedPerson john = IndexedPerson.builder() @@ -74,84 +81,153 @@ public class IndexedPersonRepositoryQueryTests extends BaseBlockingIntegrationTe public void beforeAll() { additionalAerospikeTestOperations.deleteAll(repository, allIndexedPersons); additionalAerospikeTestOperations.saveAll(repository, allIndexedPersons); + String setName = template.getSetName(IndexedPerson.class); List newIndexes = new ArrayList<>(); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_first_name_index").bin("firstName").indexType(STRING).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_last_name_index").bin("lastName").indexType(STRING).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_age_index").bin("age").indexType(NUMERIC).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_strings_index").bin("strings").indexType(STRING).indexCollectionType(LIST).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_ints_index").bin("ints").indexType(NUMERIC).indexCollectionType(LIST).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_string_map_keys_index").bin("stringMap").indexType(STRING) - .indexCollectionType(MAPKEYS).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_string_map_values_index").bin("stringMap").indexType(STRING) - .indexCollectionType(MAPVALUES).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_int_map_keys_index").bin("intMap").indexType(STRING).indexCollectionType(MAPKEYS) + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_first_name_index") + .bin("firstName") + .indexType(STRING) .build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_int_map_values_index").bin("intMap").indexType(NUMERIC) - .indexCollectionType(MAPVALUES).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_address_keys_index").bin("address").indexType(STRING).indexCollectionType(MAPKEYS) + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_last_name_index") + .bin("lastName") + .indexType(STRING) + .build()); + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_age_index") + .bin("age") + .indexType(NUMERIC) + .build()); + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_isActive_index") + .bin("isActive") + .indexType(STRING) .build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_address_values_index").bin("address").indexType(STRING) + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_strings_index") + .bin("strings") + .indexType(STRING) + .indexCollectionType(LIST) + .build()); + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_ints_index") + .bin("ints") + .indexType(NUMERIC) + .indexCollectionType(LIST) + .build()); + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_string_map_keys_index") + .bin("stringMap") + .indexType(STRING) + .indexCollectionType(MAPKEYS) + .build()); + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_string_map_values_index") + .bin("stringMap") + .indexType(STRING) .indexCollectionType(MAPVALUES).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_int_map_keys_index") + .bin("intMap") + .indexType(STRING) + .indexCollectionType(MAPKEYS) + .build()); + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_int_map_values_index") + .bin("intMap") + .indexType(NUMERIC) + .indexCollectionType(MAPVALUES) + .build()); + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_address_keys_index") + .bin("address") + .indexType(STRING) + .indexCollectionType(MAPKEYS) + .build()); + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_address_values_index") + .bin("address") + .indexType(STRING) + .indexCollectionType(MAPVALUES) + .build()); + newIndexes.add(Index.builder() + .set(setName) .name("indexed_person_friend_address_keys_index") - .bin("friend").indexType(STRING).indexCollectionType(MAPKEYS) - .ctx(new CTX[]{CTX.mapKey(Value.get("address"))}).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) + .bin("friend") + .indexType(STRING) + .indexCollectionType(MAPKEYS) + .ctx(new CTX[]{CTX.mapKey(Value.get("address"))}) + .build()); + newIndexes.add(Index.builder() + .set(setName) .name("indexed_person_friend_address_values_index") - .bin("friend").indexType(STRING).indexCollectionType(MAPVALUES) - .ctx(new CTX[]{CTX.mapValue(Value.get("address"))}).build()); - newIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) + .bin("friend") + .indexType(STRING) + .indexCollectionType(MAPVALUES) + .ctx(new CTX[]{CTX.mapValue(Value.get("address"))}) + .build()); + newIndexes.add(Index.builder() + .set(setName) .name("indexed_person_friend_bestFriend_address_keys_index") - .bin("friend").indexType(STRING).indexCollectionType(MAPKEYS) - .ctx(new CTX[]{CTX.mapKey(Value.get("bestFriend")), CTX.mapKey(Value.get("address"))}).build()); + .bin("friend") + .indexType(STRING) + .indexCollectionType(MAPKEYS) + .ctx(new CTX[]{CTX.mapKey(Value.get("bestFriend")), CTX.mapKey(Value.get("address"))}) + .build()); + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_bestFriend_friend_address_keys_index") + .bin("bestFriend") + .indexType(STRING) + .indexCollectionType(MAPKEYS) + .ctx(new CTX[]{CTX.mapKey(Value.get("friend")), CTX.mapKey(Value.get("address"))}) + .build()); + newIndexes.add(Index.builder() + .set(setName) + .name("indexed_person_bestFriend_friend_address_values_index") + .bin("bestFriend") + .indexType(STRING) + .indexCollectionType(MAPVALUES) + .ctx(new CTX[]{CTX.mapKey(Value.get("friend")), CTX.mapKey(Value.get("address"))}) + .build()); additionalAerospikeTestOperations.createIndexes(newIndexes); } @AfterAll public void afterAll() { additionalAerospikeTestOperations.deleteAll(repository, allIndexedPersons); - List dropIndexes = new ArrayList<>(); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_first_name_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_last_name_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_age_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_strings_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_ints_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_string_map_keys_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_string_map_values_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_int_map_keys_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_int_map_values_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_address_keys_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_address_values_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_friend_address_keys_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_friend_address_values_index").build()); - dropIndexes.add(Index.builder().set(template.getSetName(IndexedPerson.class)) - .name("indexed_person_friend_bestFriend_address_keys_index").build()); + String setName = template.getSetName(IndexedPerson.class); + + dropIndexes.add(Index.builder().set(setName).name("indexed_person_first_name_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_last_name_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_age_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_isActive_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_strings_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_ints_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_string_map_keys_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_string_map_values_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_int_map_keys_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_int_map_values_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_address_keys_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_address_values_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_friend_address_keys_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_friend_address_values_index").build()); + dropIndexes.add(Index.builder().set(setName).name("indexed_person_friend_bestFriend_address_keys_index") + .build()); additionalAerospikeTestOperations.dropIndexes(dropIndexes); } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/countBy/EqualsTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/countBy/EqualsTests.java index 1295a3411..5a5e38ece 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/countBy/EqualsTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/countBy/EqualsTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.blocking.indexed.countBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.NoSecondaryIndexRequired; import org.springframework.data.aerospike.repository.query.blocking.indexed.IndexedPersonRepositoryQueryTests; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -11,7 +12,8 @@ public class EqualsTests extends IndexedPersonRepositoryQueryTests { @Test - public void countBySimpleProperty_String() { + @NoSecondaryIndexRequired + public void countBySimpleProperty_String_NegativeTest() { assertThatThrownBy(() -> repository.countByLastName("Lerois")) .isInstanceOf(UnsupportedOperationException.class) .hasMessage("Query method IndexedPerson.countByLastName is not supported"); diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/BetweenTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/BetweenTests.java index 591033da5..7a02e639d 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/BetweenTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/BetweenTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.blocking.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; import org.springframework.data.aerospike.query.QueryParam; import org.springframework.data.aerospike.repository.query.blocking.indexed.IndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; @@ -16,44 +17,55 @@ public class BetweenTests extends IndexedPersonRepositoryQueryTests { @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyBetween_Integer() { + assertStmtHasSecIndexFilter("findByAgeBetween", IndexedPerson.class, 40, 45); Iterable it = repository.findByAgeBetween(40, 45); assertThat(it).hasSize(2).contains(john, peter); } @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyBetween_Integer_OrderBySimpleProperty() { + assertStmtHasSecIndexFilter("findByAgeBetweenOrderByLastName", IndexedPerson.class, 30, 45); Iterable it = repository.findByAgeBetweenOrderByLastName(30, 45); assertThat(it).hasSize(3); } @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyBetween_Integer_AND_SimplePropertyEquals_String() { QueryParam ageBetween = QueryParam.of(40, 45); - QueryParam lastNames = QueryParam.of("Matthews"); - Iterable it = repository.findByAgeBetweenAndLastName(ageBetween, lastNames); + QueryParam lastName = QueryParam.of("Matthews"); + assertStmtHasSecIndexFilter("findByAgeBetweenAndLastName", IndexedPerson.class, ageBetween, lastName); + Iterable it = repository.findByAgeBetweenAndLastName(ageBetween, lastName); assertThat(it).hasSize(0); ageBetween = QueryParam.of(20, 26); - lastNames = QueryParam.of("Smith"); - Iterable result = repository.findByAgeBetweenAndLastName(ageBetween, lastNames); + lastName = QueryParam.of("Smith"); + assertStmtHasSecIndexFilter("findByAgeBetweenAndLastName", IndexedPerson.class, ageBetween, lastName); + Iterable result = repository.findByAgeBetweenAndLastName(ageBetween, lastName); assertThat(result).hasSize(1).contains(billy); } @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyBetween_Integer_OR_SimplePropertyEquals_String() { QueryParam ageBetween = QueryParam.of(40, 45); - QueryParam lastNames = QueryParam.of("James"); - Iterable it = repository.findByAgeBetweenOrLastName(ageBetween, lastNames); + QueryParam lastName = QueryParam.of("James"); +// assertStmtHasSecIndexFilter("findByAgeBetweenOrLastName", IndexedPerson.class, ageBetween, lastName); + Iterable it = repository.findByAgeBetweenOrLastName(ageBetween, lastName); assertThat(it).containsExactlyInAnyOrder(john, peter, tricia); ageBetween = QueryParam.of(20, 26); - lastNames = QueryParam.of("Macintosh"); - Iterable result = repository.findByAgeBetweenOrLastName(ageBetween, lastNames); + lastName = QueryParam.of("Macintosh"); +// assertStmtHasSecIndexFilter("findByAgeBetweenOrLastName", IndexedPerson.class, ageBetween, lastName); + Iterable result = repository.findByAgeBetweenOrLastName(ageBetween, lastName); assertThat(result).containsExactlyInAnyOrder(billy, peter); } @Test + @AssertBinsAreIndexed(binNames = "bestFriend", entityClass = IndexedPerson.class) void findByNestedSimplePropertyBetween_Integer_3_levels() { assertThat(jane.getAddress().getApartment()).isEqualTo(2); @@ -62,6 +74,8 @@ void findByNestedSimplePropertyBetween_Integer_3_levels() { billy.setBestFriend(tricia); repository.save(billy); + // TODO: Currently deeply nested queries don't have secondary index filter +// assertStmtHasSecIndexFilter("findByBestFriendFriendAddressApartmentBetween", IndexedPerson.class, 1, 3); List persons = repository.findByBestFriendFriendAddressApartmentBetween(1, 3); assertThat(persons).contains(billy); diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/ContainingTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/ContainingTests.java index d141ec30c..81f6d75ac 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/ContainingTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/ContainingTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.blocking.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; import org.springframework.data.aerospike.repository.query.blocking.indexed.IndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; @@ -17,7 +18,9 @@ public class ContainingTests extends IndexedPersonRepositoryQueryTests { @Test + @AssertBinsAreIndexed(binNames = "strings", entityClass = IndexedPerson.class) void findByCollectionContaining_String() { + assertStmtHasSecIndexFilter("findByStringsContaining", IndexedPerson.class, "str1"); assertThat(repository.findByStringsContaining("str1")).containsOnly(john, peter); assertThat(repository.findByStringsContaining("str2")).containsOnly(john, peter); assertThat(repository.findByStringsContaining("str3")).containsOnly(peter); @@ -25,7 +28,9 @@ void findByCollectionContaining_String() { } @Test + @AssertBinsAreIndexed(binNames = "ints", entityClass = IndexedPerson.class) void findByCollectionContaining_Integer() { + assertStmtHasSecIndexFilter("findByIntsContaining", IndexedPerson.class, 550); assertThat(repository.findByIntsContaining(550)).containsOnly(john, jane); assertThat(repository.findByIntsContaining(990)).containsOnly(john, jane); assertThat(repository.findByIntsContaining(600)).containsOnly(jane); @@ -33,25 +38,31 @@ void findByCollectionContaining_Integer() { } @Test + @AssertBinsAreIndexed(binNames = "stringMap", entityClass = IndexedPerson.class) void findByMapKeysContaining_String() { assertThat(billy.getStringMap()).containsKey("key1"); + assertStmtHasSecIndexFilter("findByStringMapContaining", IndexedPerson.class, KEY, "key1"); List persons = repository.findByStringMapContaining(KEY, "key1"); assertThat(persons).contains(billy); } @Test + @AssertBinsAreIndexed(binNames = "stringMap", entityClass = IndexedPerson.class) void findByMapValuesContaining_String() { assertThat(billy.getStringMap()).containsValue("val1"); + assertStmtHasSecIndexFilter("findByStringMapContaining", IndexedPerson.class, VALUE, "key1"); List persons = repository.findByStringMapContaining(VALUE, "val1"); assertThat(persons).contains(billy); } @Test + @AssertBinsAreIndexed(binNames = "intMap", entityClass = IndexedPerson.class) void findByExactMapKeyAndValue_Integer() { assertThat(tricia.getIntMap()).containsKey("key1"); assertThat(tricia.getIntMap().get("key1")).isEqualTo(0); + assertStmtHasSecIndexFilter("findByIntMapContaining", IndexedPerson.class, KEY_VALUE_PAIR, "key1", 0); Iterable result = repository.findByIntMapContaining(KEY_VALUE_PAIR, "key1", 0); assertThat(result).contains(tricia); diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/CrudRepositoryQueryTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/CrudRepositoryQueryTests.java index 9ab5df3f4..da77500da 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/CrudRepositoryQueryTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/CrudRepositoryQueryTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.blocking.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.NoSecondaryIndexRequired; import org.springframework.data.aerospike.repository.query.blocking.indexed.IndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; import org.springframework.data.aerospike.sample.Person; @@ -16,6 +17,7 @@ public class CrudRepositoryQueryTests extends IndexedPersonRepositoryQueryTests { @Test + @NoSecondaryIndexRequired public void findsPersonById() { Optional person = repository.findById(john.getId()); @@ -26,6 +28,7 @@ public void findsPersonById() { } @Test + @NoSecondaryIndexRequired public void findsAllWithGivenIds() { List result = (List) repository.findAllById(List.of(john.getId(), billy.getId())); diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/EqualsTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/EqualsTests.java index 672399dde..a2497562e 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/EqualsTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/EqualsTests.java @@ -1,9 +1,13 @@ package org.springframework.data.aerospike.repository.query.blocking.indexed.findBy; +import com.aerospike.client.Value; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.BaseIntegrationTests; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; import org.springframework.data.aerospike.query.QueryParam; import org.springframework.data.aerospike.repository.query.blocking.indexed.IndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; +import org.springframework.data.aerospike.sample.Person; import org.springframework.data.aerospike.util.TestUtils; import java.util.List; @@ -16,79 +20,125 @@ public class EqualsTests extends IndexedPersonRepositoryQueryTests { @Test + @AssertBinsAreIndexed(binNames = "lastName", entityClass = IndexedPerson.class) public void findBySimplePropertyEquals_String() { + assertStmtHasSecIndexFilter("findByLastName", IndexedPerson.class, "Gillaham"); List result = repository.findByLastName("Gillaham"); assertThat(result).containsOnly(jane); + assertStmtHasSecIndexFilter("findByFirstName", IndexedPerson.class, "Tricia"); + assertBinIsIndexed("firstName", IndexedPerson.class); List result2 = repository.findByFirstName("Tricia"); assertThat(result2).containsOnly(tricia); } @Test - public void findByTwoSimplePropertiesEqual_StringAndBoolean() { + @AssertBinsAreIndexed(binNames = "isActive", entityClass = IndexedPerson.class) + void findBySimplePropertyEquals_Boolean_NoSecondaryIndexFilter() { + boolean initialValue = Value.UseBoolBin; + Value.UseBoolBin = true; // save boolean as bool, available since Server 5.6+ + + IndexedPerson boolBinPerson = IndexedPerson.from( + Person.builder() + .id(BaseIntegrationTests.nextId()) + .isActive(true) + .firstName("Test") + .build() + ); + repository.save(boolBinPerson); + + // Secondary index filter for a boolean value is not supported + assertThat(stmtHasSecIndexFilter("findByIsActive", IndexedPerson.class, true)).isFalse(); + assertThat(repository.findByIsActive(true)).contains(boolBinPerson); + + Value.UseBoolBin = initialValue; // set back to the default value + repository.delete(boolBinPerson); + } + + @Test + @AssertBinsAreIndexed(binNames = {"isActive", "firstName"}, entityClass = IndexedPerson.class) + public void findByTwoSimplePropertiesEqual_BooleanAndString() { assertThat(tricia.isActive()).isFalse(); - QueryParam isActive = QueryParam.of(false); - QueryParam firstNames = QueryParam.of("Tricia"); - List result = repository.findByIsActiveAndFirstName(isActive, firstNames); + QueryParam paramFalse = QueryParam.of(false); + QueryParam paramTricia = QueryParam.of("Tricia"); + + assertStmtHasSecIndexFilter("findByIsActiveAndFirstName", IndexedPerson.class, paramFalse, paramTricia); + List result = repository.findByIsActiveAndFirstName(paramFalse, paramTricia); - assertThat(result) - .hasSize(1) - .containsOnly(tricia); + assertThat(result).containsOnly(tricia); } @Test + @AssertBinsAreIndexed(binNames = {"firstName", "age"}, entityClass = IndexedPerson.class) public void findByTwoSimplePropertiesEqual_StringAndInteger() { - QueryParam firstNames = QueryParam.of("Billy"); - QueryParam ages = QueryParam.of(25); - List result = repository.findByFirstNameAndAge(firstNames, ages); + QueryParam firstName = QueryParam.of("Billy"); + QueryParam age = QueryParam.of(25); + assertStmtHasSecIndexFilter("findByFirstNameAndAge", IndexedPerson.class, firstName, age); + List result = repository.findByFirstNameAndAge(firstName, age); assertThat(result).containsOnly(billy); - firstNames = QueryParam.of("Peter"); - ages = QueryParam.of(41); - result = repository.findByFirstNameAndAge(firstNames, ages); + firstName = QueryParam.of("Peter"); + age = QueryParam.of(41); + assertStmtHasSecIndexFilter("findByFirstNameAndAge", IndexedPerson.class, firstName, age); + result = repository.findByFirstNameAndAge(firstName, age); assertThat(result).containsOnly(peter); } @Test + @AssertBinsAreIndexed(binNames = "address", entityClass = IndexedPerson.class) void findByNestedSimpleProperty_String() { - assertThat(john.getAddress().getZipCode()).isEqualTo("C0123"); - List result = repository.findByAddressZipCode("C0123"); + String zipCode = "C0123"; + assertThat(john.getAddress().getZipCode()).isEqualTo(zipCode); + assertStmtHasSecIndexFilter("findByAddressZipCode", IndexedPerson.class, zipCode); + List result = repository.findByAddressZipCode(zipCode); assertThat(result).contains(john); } @Test + @AssertBinsAreIndexed(binNames = "friend", entityClass = IndexedPerson.class) void findByNestedSimpleProperty_String_2_levels() { - assertThat(john.getAddress().getZipCode()).isEqualTo("C0123"); + String zipCode = "C0123"; + assertThat(john.getAddress().getZipCode()).isEqualTo(zipCode); jane.setFriend(john); repository.save(jane); - List result = repository.findByFriendAddressZipCode("C0123"); + // Currently nested queries don't have secondary index filter +// assertStmtHasSecIndexFilter("findByFriendAddressZipCode", IndexedPerson.class, zipCode); + List result = repository.findByFriendAddressZipCode(zipCode); assertThat(result).contains(jane); TestUtils.setFriendsToNull(repository, jane); } @Test + @AssertBinsAreIndexed(binNames = "friend", entityClass = IndexedPerson.class) void findByNestedSimpleProperty_String_3_levels() { - assertThat(john.getAddress().getZipCode()).isEqualTo("C0123"); + String zipCode = "C0123"; + assertThat(john.getAddress().getZipCode()).isEqualTo(zipCode); jane.setBestFriend(john); repository.save(jane); peter.setFriend(jane); repository.save(peter); - List result = repository.findByFriendBestFriendAddressZipCode("C0123"); + // Currently deeply nested queries don't have secondary index filter +// assertStmtHasSecIndexFilter("findByFriendBestFriendAddressZipCode", IndexedPerson.class, zipCode); + List result = repository.findByFriendBestFriendAddressZipCode(zipCode); assertThat(result).contains(peter); TestUtils.setFriendsToNull(repository, jane, peter); } @Test + @AssertBinsAreIndexed(binNames = "friend", entityClass = IndexedPerson.class) void findByNestedSimpleProperty_Integer_3_levels() { - assertThat(john.getAddress().getApartment()).isEqualTo(1); + int apartment = 1; + assertThat(john.getAddress().getApartment()).isEqualTo(apartment); jane.setBestFriend(john); repository.save(jane); peter.setFriend(jane); repository.save(peter); - List result = repository.findByFriendBestFriendAddressApartment(1); + // Currently deeply nested queries don't have secondary index filter +// assertStmtHasSecIndexFilter("findByFriendBestFriendAddressApartment", IndexedPerson.class, apartment); + List result = repository.findByFriendBestFriendAddressApartment(apartment); assertThat(result).contains(peter); TestUtils.setFriendsToNull(repository, jane, peter); } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/GreaterThanTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/GreaterThanTests.java index abc9526b8..7f6bdf1cc 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/GreaterThanTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/GreaterThanTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.blocking.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; import org.springframework.data.aerospike.repository.query.blocking.indexed.IndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; import org.springframework.data.domain.PageRequest; @@ -19,13 +20,18 @@ public class GreaterThanTests extends IndexedPersonRepositoryQueryTests { @Test - public void findBySimplePropertyLessThan_String() { + @AssertBinsAreIndexed(binNames = "firstName", entityClass = IndexedPerson.class) + public void findBySimplePropertyGreaterThan_String_NoSecondaryIndexFilter() { + // "Greater than a String" has no secondary index Filter + assertThat(stmtHasSecIndexFilter("findByFirstNameGreaterThan", IndexedPerson.class, "Bill")).isFalse(); List result = repository.findByFirstNameGreaterThan("Bill"); assertThat(result).containsAll(allIndexedPersons); } @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyGreaterThan_Integer_Paginated() { + assertStmtHasSecIndexFilter("findByAgeGreaterThan", IndexedPerson.class, 40); Slice slice = repository.findByAgeGreaterThan(40, PageRequest.of(0, 10)); assertThat(slice.hasContent()).isTrue(); assertThat(slice.hasNext()).isFalse(); @@ -33,7 +39,9 @@ public void findBySimplePropertyGreaterThan_Integer_Paginated() { } @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyGreaterThan_Integer_Paginated_respectsLimitAndOffsetAndSort() { + assertStmtHasSecIndexFilter("findByAgeGreaterThan", IndexedPerson.class, 40); List result = IntStream.range(0, 4) .mapToObj(index -> repository.findByAgeGreaterThan(40, PageRequest.of(index, 1, Sort.by("age")))) .flatMap(slice -> slice.getContent().stream()) @@ -45,7 +53,9 @@ public void findBySimplePropertyGreaterThan_Integer_Paginated_respectsLimitAndOf } @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyGreaterThan_Integer_Paginated_validHasPrevAndHasNext() { + assertStmtHasSecIndexFilter("findByAgeGreaterThan", IndexedPerson.class, 40); Slice first = repository.findByAgeGreaterThan(40, PageRequest.of(0, 1, Sort.by("age"))); assertThat(first.hasContent()).isTrue(); assertThat(first.getNumberOfElements()).isEqualTo(1); @@ -53,12 +63,14 @@ public void findBySimplePropertyGreaterThan_Integer_Paginated_validHasPrevAndHas assertThat(first.isFirst()).isTrue(); assertThat(first.isLast()).isFalse(); + assertStmtHasSecIndexFilter("findByAgeGreaterThan", IndexedPerson.class, 40); Slice last = repository.findByAgeGreaterThan(40, PageRequest.of(2, 1, Sort.by("age"))); assertThat(last.hasContent()).isTrue(); assertThat(last.getNumberOfElements()).isEqualTo(1); assertThat(last.hasNext()).isFalse(); assertThat(last.isLast()).isTrue(); + assertStmtHasSecIndexFilter("findByAgeGreaterThan", IndexedPerson.class, 100); Slice slice = repository.findByAgeGreaterThan(100, PageRequest.of(0, 10)); assertThat(slice.hasContent()).isFalse(); assertThat(slice.hasNext()).isFalse(); diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/NotContainingTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/NotContainingTests.java index fe4a27790..55223247b 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/NotContainingTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/indexed/findBy/NotContainingTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.blocking.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; import org.springframework.data.aerospike.repository.query.blocking.indexed.IndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; @@ -16,16 +17,22 @@ public class NotContainingTests extends IndexedPersonRepositoryQueryTests { @Test - void findByMapKeysNotContaining_String() { + @AssertBinsAreIndexed(binNames = "stringMap", entityClass = IndexedPerson.class) + void findByMapKeysNotContaining_String_NoSecondaryIndexFilter() { assertThat(billy.getStringMap()).containsKey("key1"); + // "Not containing" has no secondary index Filter + assertThat(stmtHasSecIndexFilter("findByStringMapNotContaining", IndexedPerson.class, KEY, "key3")).isFalse(); List persons = repository.findByStringMapNotContaining(KEY, "key3"); assertThat(persons).contains(billy); } @Test + @AssertBinsAreIndexed(binNames = "stringMap", entityClass = IndexedPerson.class) void findByMapValuesNotContaining_String() { assertThat(billy.getStringMap()).containsValue("val1"); + // "Not containing" has no secondary index Filter + assertThat(stmtHasSecIndexFilter("findByStringMapNotContaining", IndexedPerson.class, VALUE, "val3")).isFalse(); List persons = repository.findByStringMapNotContaining(VALUE, "val3"); assertThat(persons).contains(billy); diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/EqualsTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/EqualsTests.java index 3bc3ec614..149588ab5 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/EqualsTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/EqualsTests.java @@ -216,7 +216,7 @@ void findByNestedSimplePropertyEquals() { } @Test - void findByNestedSimplePropertyEqualsNegativeTest() { + void findByNestedSimplePropertyEquals_NegativeTest() { assertThatThrownBy(() -> negativeTestsRepository.findByFriendAddressZipCode()) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Address.zipCode EQ: invalid number of arguments, expecting one"); @@ -440,7 +440,7 @@ void findByNestedPOJOEquals() { } @Test - void findByNestedPojoEqualsNegativeTest() { + void findByNestedPojoEquals_NegativeTest() { assertThatThrownBy(() -> negativeTestsRepository.findByFriendAddress()) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Person.address EQ: invalid number of arguments, expecting one POJO"); diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/ReactiveIndexedPersonRepositoryQueryTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/ReactiveIndexedPersonRepositoryQueryTests.java index 0615fba06..65fa0233a 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/ReactiveIndexedPersonRepositoryQueryTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/ReactiveIndexedPersonRepositoryQueryTests.java @@ -4,6 +4,8 @@ import com.aerospike.client.query.IndexType; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.aerospike.BaseReactiveIntegrationTests; @@ -19,6 +21,11 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class ReactiveIndexedPersonRepositoryQueryTests extends BaseReactiveIntegrationTests { + @BeforeEach + public void beforeEach(TestInfo testInfo) { + assertBinsAreIndexed(testInfo); + } + protected static final IndexedPerson alain = IndexedPerson.builder() .id(nextId()) .firstName("Alain") diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/BetweenTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/BetweenTests.java index 3557454d2..7702bacc4 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/BetweenTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/BetweenTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.reactive.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; import org.springframework.data.aerospike.repository.query.reactive.indexed.ReactiveIndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; import reactor.core.scheduler.Schedulers; @@ -15,10 +16,11 @@ public class BetweenTests extends ReactiveIndexedPersonRepositoryQueryTests { @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyBetween_Integer() { + assertStmtHasSecIndexFilter("findByAgeBetween", IndexedPerson.class, 39, 45); List results = reactiveRepository.findByAgeBetween(39, 45) .subscribeOn(Schedulers.parallel()).collectList().block(); - assertThat(results).hasSize(2).contains(alain, luc); } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/ContainingTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/ContainingTests.java index fc829696b..ad3af6592 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/ContainingTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/ContainingTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.reactive.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; import org.springframework.data.aerospike.repository.query.reactive.indexed.ReactiveIndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; import reactor.core.scheduler.Schedulers; @@ -18,23 +19,27 @@ public class ContainingTests extends ReactiveIndexedPersonRepositoryQueryTests { @Test + @AssertBinsAreIndexed(binNames = "strings", entityClass = IndexedPerson.class) public void findByCollectionContaining_String() { + assertStmtHasSecIndexFilter("findByStringsContaining", IndexedPerson.class, "str1"); List results = reactiveRepository.findByStringsContaining("str1") .subscribeOn(Schedulers.parallel()).collectList().block(); - assertThat(results).containsExactlyInAnyOrder(alain); } @Test + @AssertBinsAreIndexed(binNames = "ints", entityClass = IndexedPerson.class) public void findByCollectionContaining_Integer() { + assertStmtHasSecIndexFilter("findByIntsContaining", IndexedPerson.class, 550); List results = reactiveRepository.findByIntsContaining(550) .subscribeOn(Schedulers.parallel()).collectList().block(); - assertThat(results).containsExactlyInAnyOrder(daniel, emilien); } @Test + @AssertBinsAreIndexed(binNames = "stringMap", entityClass = IndexedPerson.class) public void findByMapKeysContaining_String() { + assertStmtHasSecIndexFilter("findByStringMapContaining", IndexedPerson.class, KEY, "key1"); List results = reactiveRepository.findByStringMapContaining(KEY, "key1") .subscribeOn(Schedulers.parallel()).collectList().block(); @@ -42,36 +47,39 @@ public void findByMapKeysContaining_String() { } @Test + @AssertBinsAreIndexed(binNames = "stringMap", entityClass = IndexedPerson.class) public void findByMapValuesContaining_String() { + assertStmtHasSecIndexFilter("findByStringMapContaining", IndexedPerson.class, VALUE, "val1"); List results = reactiveRepository.findByStringMapContaining(VALUE, "val1") .subscribeOn(Schedulers.parallel()).collectList().block(); - assertThat(results).contains(luc, petra); } @Test + @AssertBinsAreIndexed(binNames = "stringMap", entityClass = IndexedPerson.class) public void findByExactMapKeyAndValue_String() { assertThat(petra.getStringMap().containsKey("key1")).isTrue(); assertThat(petra.getStringMap().containsValue("val1")).isTrue(); assertThat(luc.getStringMap().containsKey("key1")).isTrue(); assertThat(luc.getStringMap().containsValue("val1")).isTrue(); + assertStmtHasSecIndexFilter("findByStringMapContaining", IndexedPerson.class, KEY_VALUE_PAIR, "key1", "val1"); List results = reactiveRepository.findByStringMapContaining(KEY_VALUE_PAIR, "key1", "val1") .subscribeOn(Schedulers.parallel()).collectList().block(); - assertThat(results).contains(petra, luc); } @Test + @AssertBinsAreIndexed(binNames = "intMap", entityClass = IndexedPerson.class) public void findByExactMapKeyAndValue_Integer() { assertThat(emilien.getIntMap().containsKey("key1")).isTrue(); assertThat(emilien.getIntMap().get("key1")).isZero(); assertThat(lilly.getIntMap().containsKey("key1")).isTrue(); assertThat(lilly.getIntMap().get("key1")).isNotZero(); + assertStmtHasSecIndexFilter("findByIntMapContaining", IndexedPerson.class, KEY_VALUE_PAIR, "key1", 0); List results = reactiveRepository.findByIntMapContaining(KEY_VALUE_PAIR, "key1", 0) .subscribeOn(Schedulers.parallel()).collectList().block(); - assertThat(results).containsExactly(emilien); } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/CustomQueriesTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/CustomQueriesTests.java index 7e2754c17..1e9586528 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/CustomQueriesTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/CustomQueriesTests.java @@ -2,6 +2,8 @@ import com.aerospike.client.Value; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; +import org.springframework.data.aerospike.config.NoSecondaryIndexRequired; import org.springframework.data.aerospike.query.FilterOperation; import org.springframework.data.aerospike.query.qualifier.Qualifier; import org.springframework.data.aerospike.repository.query.Query; @@ -16,6 +18,7 @@ public class CustomQueriesTests extends ReactiveIndexedPersonRepositoryQueryTests { @Test + @NoSecondaryIndexRequired public void findPersonsByMetadata() { // creating an expression "since_update_time metadata value is less than 50 seconds" Qualifier sinceUpdateTimeLt50Seconds = Qualifier.metadataBuilder() @@ -39,6 +42,7 @@ public void findPersonsByMetadata() { } @Test + @AssertBinsAreIndexed(binNames = {"firstName", "age",}, entityClass = IndexedPerson.class) public void findPersonsByQuery() { Iterable result; diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/EqualsTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/EqualsTests.java index 40d89cadc..609155694 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/EqualsTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/EqualsTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.reactive.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; import org.springframework.data.aerospike.query.QueryParam; import org.springframework.data.aerospike.repository.query.reactive.indexed.ReactiveIndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; @@ -16,34 +17,39 @@ public class EqualsTests extends ReactiveIndexedPersonRepositoryQueryTests { @Test + @AssertBinsAreIndexed(binNames = {"lastName", "firstName"}, entityClass = IndexedPerson.class) public void findBySimpleProperty_String() { + assertStmtHasSecIndexFilter("findByLastName", IndexedPerson.class, "Coutant-Kerbalec"); List results = reactiveRepository.findByLastName("Coutant-Kerbalec") .subscribeOn(Schedulers.parallel()).collectList().block(); assertThat(results).containsOnly(petra, emilien); + assertStmtHasSecIndexFilter("findByFirstName", IndexedPerson.class, "Lilly"); List results2 = reactiveRepository.findByFirstName("Lilly") .subscribeOn(Schedulers.parallel()).collectList().block(); assertThat(results2).containsExactlyInAnyOrder(lilly); } @Test + @AssertBinsAreIndexed(binNames = {"firstName", "age"}, entityClass = IndexedPerson.class) public void findBySimpleProperty_String_AND_SimpleProperty_Integer() { QueryParam firstName = QueryParam.of("Lilly"); QueryParam age = QueryParam.of(28); + + assertStmtHasSecIndexFilter("findByFirstNameAndAge", IndexedPerson.class, firstName, age); List results = reactiveRepository.findByFirstNameAndAge(firstName, age) .subscribeOn(Schedulers.parallel()).collectList().block(); - assertThat(results).containsOnly(lilly); } @Test + @AssertBinsAreIndexed(binNames = "address", entityClass = IndexedPerson.class) public void findByNestedSimpleProperty_String() { String zipCode = "C0123"; assertThat(alain.getAddress().getZipCode()).isEqualTo(zipCode); - + assertStmtHasSecIndexFilter("findByAddressZipCode", IndexedPerson.class, zipCode); List results = reactiveRepository.findByAddressZipCode(zipCode) .subscribeOn(Schedulers.parallel()).collectList().block(); - assertThat(results).contains(alain); } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/GreaterThanTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/GreaterThanTests.java index 247aa184f..d5dbb7a50 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/GreaterThanTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/GreaterThanTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.reactive.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; import org.springframework.data.aerospike.repository.query.reactive.indexed.ReactiveIndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; import org.springframework.data.domain.Page; @@ -19,7 +20,9 @@ public class GreaterThanTests extends ReactiveIndexedPersonRepositoryQueryTests { @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyGreaterThan_Integer_Paginated() { + assertStmtHasSecIndexFilter("findByAgeGreaterThan", IndexedPerson.class, 1, PageRequest.of(0, 1)); Page page = reactiveRepository.findByAgeGreaterThan(1, PageRequest.of(0, 1)) .subscribeOn(Schedulers.parallel()).block(); assertThat(page).containsAnyElementsOf(allIndexedPersons); @@ -39,7 +42,9 @@ public void findBySimplePropertyGreaterThan_Integer_Paginated() { } @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyGreaterThan_Integer_Unpaged() { + assertStmtHasSecIndexFilter("findByAgeGreaterThan", IndexedPerson.class, 40, Pageable.unpaged()); Slice slice = reactiveRepository.findByAgeGreaterThan(40, Pageable.unpaged()) .subscribeOn(Schedulers.parallel()).block(); assertThat(slice.hasContent()).isTrue(); diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/LessThanTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/LessThanTests.java index 0e47fca48..aa43cc564 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/LessThanTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/LessThanTests.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query.reactive.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; import org.springframework.data.aerospike.repository.query.reactive.indexed.ReactiveIndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; import org.springframework.data.domain.Page; @@ -15,7 +16,9 @@ public class LessThanTests extends ReactiveIndexedPersonRepositoryQueryTests { @Test + @AssertBinsAreIndexed(binNames = "age", entityClass = IndexedPerson.class) public void findBySimplePropertyLessThan_Integer_Unpaged() { + assertStmtHasSecIndexFilter("findByAgeLessThan", IndexedPerson.class, 40, Pageable.unpaged()); Page page = reactiveRepository.findByAgeLessThan(40, Pageable.unpaged()) .subscribeOn(Schedulers.parallel()).block(); assertThat(page.hasContent()).isTrue(); diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/StartsWithTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/StartsWithTests.java index c2b550d9b..97bb59bb6 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/StartsWithTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/reactive/indexed/findBy/StartsWithTests.java @@ -1,6 +1,8 @@ package org.springframework.data.aerospike.repository.query.reactive.indexed.findBy; import org.junit.jupiter.api.Test; +import org.springframework.data.aerospike.config.AssertBinsAreIndexed; +import org.springframework.data.aerospike.config.NoSecondaryIndexRequired; import org.springframework.data.aerospike.repository.query.reactive.indexed.ReactiveIndexedPersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.IndexedPerson; import org.springframework.data.aerospike.util.TestUtils; @@ -17,18 +19,24 @@ public class StartsWithTests extends ReactiveIndexedPersonRepositoryQueryTests { @Test - void findBySimplePropertyStartingWith_String_Distinct() { + @AssertBinsAreIndexed(binNames = "lastName", entityClass = IndexedPerson.class) + void findBySimplePropertyStartingWith_String_Distinct_NoSecondaryIndexFilter() { + // There is no secondary index filter for "starts with" + assertThat(stmtHasSecIndexFilter("findDistinctByLastNameStartingWith", IndexedPerson.class, "Coutant-Kerbalec")).isFalse(); List persons = reactiveRepository.findDistinctByLastNameStartingWith("Coutant-Kerbalec") .subscribeOn(Schedulers.parallel()).collectList().block(); assertThat(persons).hasSize(1); + // There is no secondary index filter for "starts with" + assertThat(stmtHasSecIndexFilter("findByLastNameStartingWith", IndexedPerson.class, "Coutant-Kerbalec")).isFalse(); List persons2 = reactiveRepository.findByLastNameStartingWith("Coutant-Kerbalec") .subscribeOn(Schedulers.parallel()).collectList().block(); assertThat(persons2).hasSize(2); } @Test - void findByNestedSimplePropertyStartingWith_String_Distinct() { + @NoSecondaryIndexRequired + void findByNestedSimplePropertyStartingWith_String_Distinct_NegativeTest() { alain.setFriend(luc); reactiveRepository.save(alain); lilly.setFriend(petra); diff --git a/src/test/java/org/springframework/data/aerospike/util/QueryUtils.java b/src/test/java/org/springframework/data/aerospike/util/QueryUtils.java index e6d3ac6e7..a6da3ad5b 100644 --- a/src/test/java/org/springframework/data/aerospike/util/QueryUtils.java +++ b/src/test/java/org/springframework/data/aerospike/util/QueryUtils.java @@ -9,6 +9,7 @@ import org.springframework.data.aerospike.repository.query.Query; import org.springframework.data.aerospike.sample.Person; import org.springframework.data.aerospike.sample.PersonRepository; +import org.springframework.data.domain.Pageable; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.ParametersParameterAccessor; @@ -20,6 +21,7 @@ import java.lang.invoke.MethodType; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.stream.Stream; @@ -45,22 +47,28 @@ private static Class unwrap(Class c) { } public static Query createQueryForMethodWithArgs(String methodName, Object... args) { + return createQueryForMethodWithArgs(PersonRepository.class, Person.class, methodName, args); + } + + public static Query createQueryForMethodWithArgs(Class repositoryClass, Class entityClass, + 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); + Class[] argTypesCheckedForPageable = checkForPageable(argTypes); + Method method = ReflectionUtils.findMethod(repositoryClass, methodName, argTypesCheckedForPageable); if (method == null) { //noinspection rawtypes - Class[] argTypesToPrimitives = Stream.of(args).map(Object::getClass).map(c -> { + Class[] argTypesToPrimitives = Stream.of(argTypesCheckedForPageable).map(c -> { if (ClassUtils.isPrimitiveOrWrapper(c)) { return MethodType.methodType(c).unwrap().returnType(); } return c; }).toArray(Class[]::new); - method = ReflectionUtils.findMethod(PersonRepository.class, methodName, argTypesToPrimitives); + method = ReflectionUtils.findMethod(repositoryClass, methodName, argTypesToPrimitives); } - PartTree partTree = new PartTree(method.getName(), Person.class); + PartTree partTree = new PartTree(method.getName(), entityClass); AerospikeMappingContext context = new AerospikeMappingContext(); AerospikeCustomConversions conversions = new AerospikeCustomConversions(Collections.emptyList()); @@ -69,11 +77,27 @@ public static Query createQueryForMethodWithArgs(String methodName, Object... ar AerospikeQueryCreator creator = new AerospikeQueryCreator(partTree, new ParametersParameterAccessor( - new QueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class), + new QueryMethod(method, new DefaultRepositoryMetadata(repositoryClass), new SpelAwareProxyProjectionFactory()).getParameters(), args), context, converter); return creator.createQuery(); } + /** + * Check instances of Pageable and use the interface as we do in repositories' methods + * @param argTypes Types of arguments + * @return Array of arguments types with Pageable instances replaced with Pageable + */ + private static Class[] checkForPageable(Class[] argTypes) { + return Arrays.stream(argTypes).map(QueryUtils::checkForPageable).toArray(Class[]::new); + } + + private static Class checkForPageable(Class argType) { + if (Pageable.class.isAssignableFrom(argType)) { + return Pageable.class; + } + return argType; + } + private static MappingAerospikeConverter getMappingAerospikeConverter(AerospikeCustomConversions conversions) { MappingAerospikeConverter converter = new MappingAerospikeConverter(new AerospikeMappingContext(), conversions, new AerospikeTypeAliasAccessor(), new AerospikeDataSettings());