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 bd09e55a4..de2fe215f 100644 --- a/src/main/java/org/springframework/data/aerospike/query/FilterOperation.java +++ b/src/main/java/org/springframework/data/aerospike/query/FilterOperation.java @@ -19,7 +19,6 @@ import com.aerospike.client.cdt.CTX; import com.aerospike.client.cdt.ListReturnType; import com.aerospike.client.cdt.MapReturnType; -import com.aerospike.client.command.ParticleType; import com.aerospike.client.exp.Exp; import com.aerospike.client.exp.ListExp; import com.aerospike.client.exp.MapExp; @@ -46,7 +45,6 @@ import static com.aerospike.client.command.ParticleType.INTEGER; import static com.aerospike.client.command.ParticleType.LIST; import static com.aerospike.client.command.ParticleType.MAP; -import static com.aerospike.client.command.ParticleType.NULL; import static com.aerospike.client.command.ParticleType.STRING; import static org.springframework.data.aerospike.query.qualifier.QualifierKey.*; import static org.springframework.data.aerospike.util.FilterOperationRegexpBuilder.getContaining; @@ -54,6 +52,8 @@ import static org.springframework.data.aerospike.util.FilterOperationRegexpBuilder.getNotContaining; import static org.springframework.data.aerospike.util.FilterOperationRegexpBuilder.getStartsWith; import static org.springframework.data.aerospike.util.FilterOperationRegexpBuilder.getStringEquals; +import static org.springframework.data.aerospike.util.Utils.getExpType; +import static org.springframework.data.aerospike.util.Utils.getValueExpOrFail; public enum FilterOperation { AND { @@ -111,8 +111,7 @@ public Filter sIndexFilter(Map qualifierMap) { public Exp filterExp(Map qualifierMap) { // Convert IN to EQ with logical OR as there is no direct support for IN query return getMetadataExp(qualifierMap).orElseGet(() -> { - Value value = getValueAsCollectionOrFail(qualifierMap); - Collection collection = (Collection) value.getObject(); + Collection collection = getValueAsCollectionOrFail(qualifierMap); Exp[] arrElementsExp = collection.stream().map(item -> Qualifier.builder() .setField(getField(qualifierMap)) @@ -136,8 +135,7 @@ public Filter sIndexFilter(Map qualifierMap) { public Exp filterExp(Map qualifierMap) { // Convert NOT_IN to NOTEQ with logical AND as there is no direct support for NOT_IN query return getMetadataExp(qualifierMap).orElseGet(() -> { - Value value = getValueAsCollectionOrFail(qualifierMap); - Collection collection = (Collection) value.getObject(); + Collection collection = getValueAsCollectionOrFail(qualifierMap); Exp[] arrElementsExp = collection.stream().map(item -> Qualifier.builder() .setField(getField(qualifierMap)) @@ -500,6 +498,52 @@ public Filter sIndexFilter(Map qualifierMap) { return null; // not supported } }, + MAP_VAL_IN_BY_KEY { + @Override + public Exp filterExp(Map qualifierMap) { + // Convert IN to EQ with logical OR as there is no direct support for IN query + Collection collection = getValueAsCollectionOrFail(qualifierMap); + Exp[] arrElementsExp = collection.stream().map(item -> + Qualifier.builder() + .setField(getField(qualifierMap)) + .setFilterOperation(FilterOperation.MAP_VAL_EQ_BY_KEY) + .setKey(getKey(qualifierMap)) + .setValue(Value.get(item)) + .build() + .toFilterExp() + ).toArray(Exp[]::new); + + return Exp.or(arrElementsExp); + } + + @Override + public Filter sIndexFilter(Map qualifierMap) { + return null; + } + }, + MAP_VAL_NOT_IN_BY_KEY { + @Override + public Exp filterExp(Map qualifierMap) { + // Convert NOT_IN to NOTEQ with logical AND as there is no direct support for NOT_IN query + Collection collection = getValueAsCollectionOrFail(qualifierMap); + Exp[] arrElementsExp = collection.stream().map(item -> + Qualifier.builder() + .setField(getField(qualifierMap)) + .setFilterOperation(FilterOperation.MAP_VAL_NOTEQ_BY_KEY) + .setKey(getKey(qualifierMap)) + .setValue(Value.get(item)) + .build() + .toFilterExp() + ).toArray(Exp[]::new); + + return Exp.and(arrElementsExp); + } + + @Override + public Filter sIndexFilter(Map qualifierMap) { + return null; + } + }, MAP_VAL_GT_BY_KEY { @Override public Exp filterExp(Map qualifierMap) { @@ -727,10 +771,42 @@ public Filter sIndexFilter(Map qualifierMap) { MAP_VAL_CONTAINING_BY_KEY { @Override public Exp filterExp(Map qualifierMap) { - String containingRegexp = getContaining(getValue(qualifierMap).toString()); - Exp bin = MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING, Exp.val(getKey(qualifierMap).toString()), - Exp.mapBin(getField(qualifierMap))); - return Exp.regexCompare(containingRegexp, regexFlags(qualifierMap), bin); + Integer nestedType = FilterOperation.getNestedType(qualifierMap); + if (nestedType == null) throw new IllegalStateException("Expecting valid nestedType, got null"); + switch (nestedType) { + case STRING -> { + // Out of simple properties only a String is validated for CONTAINING + String containingRegexp = getContaining(getValue(qualifierMap).toString()); + Exp nestedString = MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING, + Exp.val(getKey(qualifierMap).toString()), Exp.mapBin(getField(qualifierMap))); + return Exp.regexCompare(containingRegexp, regexFlags(qualifierMap), nestedString); + } + case LIST -> { + Exp value = getExpOrFail(getValue(qualifierMap), "MAP_VAL_CONTAINING_BY_KEY"); + Exp key = getExpOrFail(getKey(qualifierMap), "MAP_VAL_CONTAINING_BY_KEY"); + + // Map value is a List + Exp nestedList = MapExp.getByKey(MapReturnType.VALUE, Exp.Type.LIST, key, + Exp.mapBin(getField(qualifierMap))); + // Check whether List contains the value + return Exp.gt( + ListExp.getByValue(ListReturnType.COUNT, value, nestedList), + Exp.val(0)); + } + case MAP -> { + Value val = getValue(qualifierMap); + Exp value = getExpOrFail(val, "MAP_VAL_CONTAINING_BY_KEY"); + Exp key = getExpOrFail(getKey(qualifierMap), "MAP_VAL_CONTAINING_BY_KEY"); + Exp secondKey = getExpOrFail(getNestedKey(qualifierMap), "MAP_VAL_CONTAINING_BY_KEY"); + + Exp nestedMap = MapExp.getByKey(MapReturnType.VALUE, Exp.Type.MAP, key, + Exp.mapBin(getField(qualifierMap))); + return Exp.eq( + MapExp.getByKey(MapReturnType.VALUE, getExpType(val), secondKey, nestedMap), + value); + } + default -> throw new UnsupportedOperationException("Unsupported nested type: " + nestedType); + } } @Override @@ -741,17 +817,47 @@ public Filter sIndexFilter(Map qualifierMap) { MAP_VAL_NOT_CONTAINING_BY_KEY { @Override public Exp filterExp(Map qualifierMap) { - String containingRegexp = getContaining(getValue(qualifierMap).toString()); - Exp mapIsNull = Exp.not(Exp.binExists(getField(qualifierMap))); - Exp mapKeysNotContaining = Exp.eq( - MapExp.getByKey(MapReturnType.COUNT, Exp.Type.INT, Exp.val(getKey(qualifierMap).toString()), - Exp.mapBin(getField(qualifierMap))), + Integer nestedType = getNestedType(qualifierMap); + Exp mapBinDoesNotExist = Exp.not(Exp.binExists(getField(qualifierMap))); + Exp key = getExpOrFail(getKey(qualifierMap), "MAP_VAL_NOT_CONTAINING_BY_KEY"); + Exp mapNotContainingKey = Exp.eq( + MapExp.getByKey(MapReturnType.COUNT, Exp.Type.INT, key, Exp.mapBin(getField(qualifierMap))), Exp.val(0)); - Exp binValue = MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING, - Exp.val(getKey(qualifierMap).toString()), - Exp.mapBin(getField(qualifierMap))); - return Exp.or(mapIsNull, mapKeysNotContaining, - Exp.not(Exp.regexCompare(containingRegexp, regexFlags(qualifierMap), binValue))); + + if (nestedType == null) throw new IllegalStateException("Expecting valid nestedType, got null"); + switch (nestedType) { + case STRING -> { + // Out of simple properties only a String is validated for NOT_CONTAINING + String containingRegexp = getContaining(getValue(qualifierMap).toString()); + Exp nestedString = MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING, + Exp.val(getKey(qualifierMap).toString()), + Exp.mapBin(getField(qualifierMap))); + return Exp.or(mapBinDoesNotExist, mapNotContainingKey, + Exp.not(Exp.regexCompare(containingRegexp, regexFlags(qualifierMap), nestedString))); + } + case LIST -> { + Exp value = getExpOrFail(getValue(qualifierMap), "MAP_VAL_NOT_CONTAINING_BY_KEY"); + + Exp nestedList = MapExp.getByKey(MapReturnType.VALUE, Exp.Type.LIST, key, + Exp.mapBin(getField(qualifierMap))); + Exp nestedListNotContainingValue = Exp.eq(ListExp.getByValue(ListReturnType.COUNT, value, + nestedList), Exp.val(0)); + return Exp.or(mapBinDoesNotExist, mapNotContainingKey, nestedListNotContainingValue); + } + case MAP -> { + Value val = getValue(qualifierMap); + Exp value = getExpOrFail(val, "MAP_VAL_NOT_CONTAINING_BY_KEY"); + Exp secondKey = getExpOrFail(getNestedKey(qualifierMap), "MAP_VAL_NOT_CONTAINING_BY_KEY"); + + Exp nestedMap = MapExp.getByKey(MapReturnType.VALUE, Exp.Type.MAP, key, + Exp.mapBin(getField(qualifierMap))); + Exp nestedMapNotContainingValueByKey = Exp.ne( + MapExp.getByKey(MapReturnType.VALUE, getExpType(val), secondKey, nestedMap), + value); + return Exp.or(mapBinDoesNotExist, mapNotContainingKey, nestedMapNotContainingValueByKey); + } + default -> throw new UnsupportedOperationException("Unsupported value type: " + nestedType); + } } @Override @@ -944,19 +1050,9 @@ public Exp filterExp(Map qualifierMap) { if (val instanceof Value.BoolIntValue) { qualifierMap.put(VALUE, new Value.BooleanValue((Boolean) (getValue(qualifierMap).getObject()))); } - - Exp value = switch (val.getType()) { - case INTEGER -> Exp.val(val.toLong()); - case STRING -> Exp.val(val.toString()); - case BOOL -> Exp.val((Boolean) val.getObject()); - case LIST -> Exp.val((List) val.getObject()); - case MAP -> Exp.val((Map) val.getObject()); - case ParticleType.NULL -> Exp.nil(); - default -> throw new UnsupportedOperationException( - "COLLECTION_VAL_CONTAINING FilterExpression unsupported type: got " + - val.getClass().getSimpleName()); - }; - + String errMsg = "COLLECTION_VAL_CONTAINING FilterExpression unsupported type: got " + + val.getClass().getSimpleName(); + Exp value = getValueExpOrFail(val, errMsg); return Exp.gt( ListExp.getByValue(ListReturnType.COUNT, value, Exp.listBin(getField(qualifierMap))), Exp.val(0)); @@ -980,18 +1076,9 @@ public Exp filterExp(Map qualifierMap) { if (val instanceof Value.BoolIntValue) { qualifierMap.put(VALUE, new Value.BooleanValue((Boolean) (getKey(qualifierMap).getObject()))); } - - Exp value = switch (val.getType()) { - case INTEGER -> Exp.val(val.toLong()); - case STRING -> Exp.val(val.toString()); - case BOOL -> Exp.val((Boolean) val.getObject()); - case LIST -> Exp.val((List) val.getObject()); - case MAP -> Exp.val((Map) val.getObject()); - case ParticleType.NULL -> Exp.nil(); - default -> throw new UnsupportedOperationException( - "COLLECTION_VAL_CONTAINING FilterExpression unsupported type: got " + val.getClass() - .getSimpleName()); - }; + String errMsg = "COLLECTION_VAL_NOT_CONTAINING FilterExpression unsupported type: got " + + val.getClass().getSimpleName(); + Exp value = getValueExpOrFail(val, errMsg); Exp binIsNull = Exp.not(Exp.binExists(getField(qualifierMap))); Exp listNotContaining = Exp.eq( @@ -1259,14 +1346,20 @@ private static Exp processMetadataFieldNotIn(Map qualifier return processMetadataFieldInOrNot(qualifierMap, true); } - private static Value getValueAsCollectionOrFail(Map qualifierMap) { + private static Exp getExpOrFail(Value value, String filterOpName) { + String errMsg = String.format("%s FilterExpression unsupported value type: got %s", filterOpName, + value.getClass().getSimpleName()); + return getValueExpOrFail(value, errMsg); + } + + private static Collection getValueAsCollectionOrFail(Map qualifierMap) { Value value = getValue(qualifierMap); String errMsg = "FilterOperation.IN expects argument with type Collection, instead got: " + value.getObject().getClass().getSimpleName(); if (value.getType() != LIST || !(value.getObject() instanceof Collection)) { throw new IllegalArgumentException(errMsg); } - return value; + return (Collection) value.getObject(); } /** @@ -1371,23 +1464,25 @@ private static Exp mapValuesContain(Map qualifierMap) { return mapValuesCountComparedToZero(qualifierMap, Exp::gt, errMsg); } - private static Exp getValueExp(Value value, String errMsg) { - return switch (value.getType()) { - case INTEGER -> Exp.val(value.toLong()); - case STRING -> Exp.val(value.toString()); - case LIST -> Exp.val((List) value.getObject()); - case MAP -> Exp.val((Map) value.getObject()); - case NULL -> Exp.nil(); - default -> throw new UnsupportedOperationException(errMsg); - }; - } - // operator is Exp::gt to query for mapKeysContain or Exp::eq to query for mapKeysNotContain private static Exp mapKeysCount(Map qualifierMap, BinaryOperator operator, String errMsg) { - Exp key = getValueExp(getValue(qualifierMap), errMsg); + Exp key = getValueExpOrFail(getValue(qualifierMap), errMsg); + Exp map = Exp.mapBin(getField(qualifierMap)); + Value mapBinKey = getKey(qualifierMap); + + if (!mapBinKey.equals(Value.NullValue.INSTANCE)) { + // If map key != null it is a nested query (one level) + String err = "MAP_VAL_NOT_CONTAINING_BY_KEY FilterExpression unsupported type: got " + + mapBinKey.getClass().getSimpleName(); + Exp nestedMapKey = getValueExpOrFail(mapBinKey, err); + + // locate a Map within its parent bin + map = MapExp.getByKey(MapReturnType.VALUE, Exp.Type.MAP, nestedMapKey, Exp.mapBin(getField(qualifierMap))); + } + return operator.apply( - MapExp.getByKey(MapReturnType.COUNT, Exp.Type.INT, key, Exp.mapBin(getField(qualifierMap))), + MapExp.getByKey(MapReturnType.COUNT, Exp.Type.INT, key, map), Exp.val(0)); } @@ -1395,9 +1490,22 @@ private static Exp mapKeysCount(Map qualifierMap, BinaryOp private static Exp mapValuesCountComparedToZero(Map qualifierMap, BinaryOperator operator, String errMsg) { - Exp value = getValueExp(getValue(qualifierMap), errMsg); + Exp value = getValueExpOrFail(getValue(qualifierMap), errMsg); + Exp map = Exp.mapBin(getField(qualifierMap)); + Value mapBinKey = getKey(qualifierMap); + + if (!mapBinKey.equals(Value.NullValue.INSTANCE)) { + // If map key != null it means a nested query (one level) + String err = "MAP_VAL_NOT_CONTAINING_BY_KEY FilterExpression unsupported type: got " + + mapBinKey.getClass().getSimpleName(); + Exp nestedMapKey = getValueExpOrFail(mapBinKey, err); + + // if it is a nested query we need to locate a Map within its parent bin + map = MapExp.getByKey(MapReturnType.VALUE, Exp.Type.MAP, nestedMapKey, Exp.mapBin(getField(qualifierMap))); + } + return operator.apply( - MapExp.getByValue(MapReturnType.COUNT, value, Exp.mapBin(getField(qualifierMap))), + MapExp.getByValue(MapReturnType.COUNT, value, map), Exp.val(0)); } @@ -1512,11 +1620,11 @@ private static Exp getMapValEq(Map qualifierMap, Exp.Type boolean useCtx) { if (useCtx) { return MapExp.getByKey(MapReturnType.VALUE, expType, - Exp.val(getKey(qualifierMap).toString()), // key (field name) + getValueExpOrFail(getKey(qualifierMap), "MAP_VAL_EQ: unsupported type"), // key (field name) Exp.mapBin(getField(qualifierMap)), dotPathToCtxMapKeys(dotPathArr)); } else { return MapExp.getByKey(MapReturnType.VALUE, expType, - Exp.val(getKey(qualifierMap).toString()), + getValueExpOrFail(getKey(qualifierMap), "MAP_VAL_EQ: unsupported type"), Exp.mapBin(getField(qualifierMap))); } } @@ -1601,6 +1709,10 @@ protected static Object getKeyAsObject(Map qualifierMap) { return qualifierMap.get(KEY); } + protected static Value getNestedKey(Map qualifierMap) { + return Value.get(qualifierMap.get(NESTED_KEY)); + } + protected static Value getValue(Map qualifierMap) { return Value.get(qualifierMap.get(VALUE)); } @@ -1613,6 +1725,11 @@ protected static Value getSecondValue(Map qualifierMap) { return Value.get(qualifierMap.get(SECOND_VALUE)); } + protected static Integer getNestedType(Map qualifierMap) { + Object fieldType = qualifierMap.get(NESTED_TYPE); + return fieldType != null ? (int) fieldType : null; + } + @SuppressWarnings("unchecked") protected static List getDotPath(Map qualifierMap) { return (List) qualifierMap.get(DOT_PATH); diff --git a/src/main/java/org/springframework/data/aerospike/query/qualifier/QualifierBuilder.java b/src/main/java/org/springframework/data/aerospike/query/qualifier/QualifierBuilder.java index 72e5bbd04..4d3b1080f 100644 --- a/src/main/java/org/springframework/data/aerospike/query/qualifier/QualifierBuilder.java +++ b/src/main/java/org/springframework/data/aerospike/query/qualifier/QualifierBuilder.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.query.qualifier; import com.aerospike.client.Value; +import com.aerospike.client.command.ParticleType; import java.util.List; @@ -8,6 +9,8 @@ import static org.springframework.data.aerospike.query.qualifier.QualifierKey.FIELD; import static org.springframework.data.aerospike.query.qualifier.QualifierKey.IGNORE_CASE; import static org.springframework.data.aerospike.query.qualifier.QualifierKey.KEY; +import static org.springframework.data.aerospike.query.qualifier.QualifierKey.NESTED_KEY; +import static org.springframework.data.aerospike.query.qualifier.QualifierKey.NESTED_TYPE; import static org.springframework.data.aerospike.query.qualifier.QualifierKey.SECOND_VALUE; import static org.springframework.data.aerospike.query.qualifier.QualifierKey.VALUE; @@ -40,6 +43,18 @@ public QualifierBuilder setKey(Value key) { return this; } + /** + * For "find by one level nested map containing" queries. + * Set nested Map key. + *

+ * Use one of the Value get() methods ({@link Value#get(int)}, {@link Value#get(String)} etc.) to firstly read the + * key into a {@link Value} object. + */ + public QualifierBuilder setNestedKey(Value key) { + this.map.put(NESTED_KEY, key); + return this; + } + /** * Set value. *

@@ -62,6 +77,15 @@ public QualifierBuilder setSecondValue(Value secondValue) { return this; } + /** + * For "find by one level nested map containing" queries. + * Set the type of the nested map value using {@link ParticleType}. + */ + public QualifierBuilder setNestedType(int type) { + this.map.put(NESTED_TYPE, type); + return this; + } + /** * Required only for a nested value query (e.g. find by a POJO field). */ 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 bf082dc9f..a411ceef4 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 @@ -11,8 +11,10 @@ public enum QualifierKey { MULTIPLE_IDS_FIELD, IGNORE_CASE, KEY, + NESTED_KEY, VALUE, SECOND_VALUE, + NESTED_TYPE, DOT_PATH, DATA_SETTINGS, QUALIFIERS, diff --git a/src/main/java/org/springframework/data/aerospike/repository/query/AerospikeQueryCreator.java b/src/main/java/org/springframework/data/aerospike/repository/query/AerospikeQueryCreator.java index 5474c4bc6..a354d8dfe 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/query/AerospikeQueryCreator.java +++ b/src/main/java/org/springframework/data/aerospike/repository/query/AerospikeQueryCreator.java @@ -129,9 +129,22 @@ private IAerospikeQueryCreator getQueryCreator(Part part, AerospikePersistentPro if (property.isIdProperty()) { queryCreator = new IdQueryCreator(queryParameters); } else if (property.isCollectionLike()) { - queryCreator = new CollectionQueryCreator(part, property, fieldName, queryParameters, filterOperation, converter); + if (part.getProperty().hasNext()) { // a POJO field + PropertyPath nestedProperty = getNestedPropertyPath(part.getProperty()); + queryCreator = new CollectionQueryCreator(part, nestedProperty, property, fieldName, queryParameters, + filterOperation, converter, true); + } else { + queryCreator = new CollectionQueryCreator(part, part.getProperty(), property, fieldName, + queryParameters, filterOperation, converter, false); + } } else if (property.isMap()) { - queryCreator = new MapQueryCreator(part, property, fieldName, queryParameters, filterOperation, converter); + if (part.getProperty().hasNext()) { // a POJO field + queryCreator = new MapQueryCreator(part, property, fieldName, queryParameters, filterOperation, + converter, true); + } else { + queryCreator = new MapQueryCreator(part, property, fieldName, queryParameters, filterOperation, + converter, false); + } } else { if (part.getProperty().hasNext()) { // a POJO field (a simple property field or an inner POJO) PropertyPath nestedProperty = getNestedPropertyPath(part.getProperty()); diff --git a/src/main/java/org/springframework/data/aerospike/repository/query/AerospikeQueryCreatorUtils.java b/src/main/java/org/springframework/data/aerospike/repository/query/AerospikeQueryCreatorUtils.java index 95b028373..61c8c543e 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/query/AerospikeQueryCreatorUtils.java +++ b/src/main/java/org/springframework/data/aerospike/repository/query/AerospikeQueryCreatorUtils.java @@ -13,8 +13,10 @@ import org.springframework.data.util.TypeInformation; import org.springframework.util.StringUtils; +import java.util.Collection; import java.util.List; import java.util.TreeMap; +import java.util.stream.Stream; import static org.springframework.data.aerospike.convert.AerospikeConverter.CLASS_KEY; import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeNullQueryCriterion; @@ -24,8 +26,8 @@ public class AerospikeQueryCreatorUtils { - protected static Qualifier setQualifier(QualifierBuilder qb, - String fieldName, FilterOperation op, Part part, List dotPath) { + protected static Qualifier setQualifier(QualifierBuilder qb, String fieldName, FilterOperation op, Part part, + List dotPath) { qb.setField(fieldName) .setFilterOperation(op) .setIgnoreCase(ignoreCaseToBoolean(part)); @@ -131,6 +133,10 @@ protected static void setQualifierBuilderKey(QualifierBuilder qb, Object key) { qb.setKey(getValueOfQueryParameter(key)); } + protected static void setQualifierBuilderSecondKey(QualifierBuilder qb, Object key) { + qb.setNestedKey(getValueOfQueryParameter(key)); + } + protected static void setQualifierBuilderValue(QualifierBuilder qb, Object value) { qb.setValue(getValueOfQueryParameter(value)); } @@ -153,19 +159,20 @@ protected static boolean isPojo(Class clazz) { // if it is a first level POJO return !Utils.isSimpleValueType(clazz) && !type.isCollectionLike(); } - protected static void validateTypes(MappingAerospikeConverter converter, PropertyPath property, FilterOperation op, - List queryParameters) { - String queryPartDescription = String.join(" ", property.toString(), op.toString()); - validateTypes(converter, property, queryParameters, queryPartDescription); + protected static void validateTypes(MappingAerospikeConverter converter, PropertyPath propertyPath, + FilterOperation op, List queryParameters) { + String queryPartDescription = String.join(" ", propertyPath.toString(), op.toString()); + validateTypes(converter, propertyPath, queryParameters, op, queryPartDescription); } - protected static void validateTypes(MappingAerospikeConverter converter, PropertyPath property, - List queryParameters, String queryPartDescription) { - validateTypes(converter, property.getTypeInformation().getType(), queryParameters, queryPartDescription); + protected static void validateTypes(MappingAerospikeConverter converter, PropertyPath propertyPath, + List queryParameters, FilterOperation op, String queryPartDescription) { + validateTypes(converter, propertyPath.getTypeInformation() + .getType(), queryParameters, op, queryPartDescription); } protected static void validateTypes(MappingAerospikeConverter converter, Class propertyType, - List queryParameters, String queryPartDescription, + List queryParameters, FilterOperation op, String queryPartDescription, String... alternativeTypes) { // Checking versus Number rather than strict type to be able to compare, e.g., integer to a long if (isAssignable(Number.class, propertyType) && isAssignableValue(Number.class, queryParameters.get(0))) { @@ -173,7 +180,13 @@ protected static void validateTypes(MappingAerospikeConverter converter, Class clazz = propertyType; - if (!queryParameters.stream().allMatch(param -> isAssignableValueOrConverted(clazz, param, converter))) { + Stream params = queryParameters.stream(); + if ((op == FilterOperation.IN || op == FilterOperation.NOT_IN) + && queryParameters.size() == 1 + && queryParameters.get(0) instanceof Collection) { + params = ((Collection) queryParameters.get(0)).stream(); + } + if (!params.allMatch(param -> isAssignableValueOrConverted(clazz, param, converter))) { String validTypes = propertyType.getSimpleName(); if (alternativeTypes.length > 0) { validTypes = String.format("one of the following types: %s", propertyType.getSimpleName() + ", " @@ -184,6 +197,20 @@ protected static void validateTypes(MappingAerospikeConverter converter, Class queryParameters, String queryPartDescription) { + // Number of arguments is not zero + if (!queryParameters.isEmpty()) { + throw new IllegalArgumentException(queryPartDescription + ": expecting no arguments"); + } + } + + protected static void validateQueryIn(List queryParameters, String queryPartDescription) { + // Number of arguments is not one + if (queryParameters.size() != 1) { + throw new IllegalArgumentException(queryPartDescription + ": invalid number of arguments, expecting one"); + } + } + protected static boolean isAssignableValueOrConverted(Class propertyType, Object obj, MappingAerospikeConverter converter) { return isAssignableValue(propertyType, obj) diff --git a/src/main/java/org/springframework/data/aerospike/repository/query/CollectionQueryCreator.java b/src/main/java/org/springframework/data/aerospike/repository/query/CollectionQueryCreator.java index 464458257..5ddef84dd 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/query/CollectionQueryCreator.java +++ b/src/main/java/org/springframework/data/aerospike/repository/query/CollectionQueryCreator.java @@ -1,5 +1,6 @@ package org.springframework.data.aerospike.repository.query; +import com.aerospike.client.command.ParticleType; import org.springframework.data.aerospike.convert.MappingAerospikeConverter; import org.springframework.data.aerospike.mapping.AerospikePersistentProperty; import org.springframework.data.aerospike.query.FilterOperation; @@ -12,44 +13,57 @@ import java.util.List; import static org.springframework.data.aerospike.query.FilterOperation.BETWEEN; +import static org.springframework.data.aerospike.query.FilterOperation.CONTAINING; import static org.springframework.data.aerospike.query.FilterOperation.IN; +import static org.springframework.data.aerospike.query.FilterOperation.IS_NOT_NULL; +import static org.springframework.data.aerospike.query.FilterOperation.IS_NULL; +import static org.springframework.data.aerospike.query.FilterOperation.NOT_CONTAINING; import static org.springframework.data.aerospike.query.FilterOperation.NOT_IN; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.getCollectionElementsClass; +import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.getCorrespondingMapValueFilterOperationOrFail; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifier; +import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderKey; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderSecondValue; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderValue; +import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.validateQueryIn; +import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.validateQueryIsNull; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.validateTypes; public class CollectionQueryCreator implements IAerospikeQueryCreator { private final Part part; + private final PropertyPath propertyPath; private final AerospikePersistentProperty property; private final String fieldName; private final List queryParameters; private final FilterOperation filterOperation; private final MappingAerospikeConverter converter; + private final boolean isNested; - public CollectionQueryCreator(Part part, AerospikePersistentProperty property, String fieldName, - List queryParameters, FilterOperation filterOperation, - MappingAerospikeConverter converter) { + public CollectionQueryCreator(Part part, PropertyPath propertyPath, AerospikePersistentProperty property, + String fieldName, List queryParameters, FilterOperation filterOperation, + MappingAerospikeConverter converter, boolean isNested) { this.part = part; + this.propertyPath = propertyPath; this.property = property; this.fieldName = fieldName; this.queryParameters = queryParameters; this.filterOperation = filterOperation; this.converter = converter; + this.isNested = isNested; } @Override public void validate() { - String queryPartDescription = String.join(" ", part.getProperty().toString(), filterOperation.toString()); + String queryPartDescription = String.join(" ", propertyPath.toString(), filterOperation.toString()); switch (filterOperation) { case CONTAINING, NOT_CONTAINING -> validateCollectionQueryContaining(queryParameters, queryPartDescription); case EQ, NOTEQ, GT, GTEQ, LT, LTEQ -> validateCollectionQueryComparison(queryParameters, queryPartDescription); case BETWEEN -> validateCollectionQueryBetween(queryParameters, queryPartDescription); - default -> throw new UnsupportedOperationException( - String.format("Unsupported operation: %s applied to %s", filterOperation, property)); + case IN, NOT_IN -> validateQueryIn(queryParameters, queryPartDescription); + case IS_NOT_NULL, IS_NULL -> validateQueryIsNull(queryParameters, queryPartDescription); + default -> throw new UnsupportedOperationException("Unsupported operation: " + queryPartDescription); } } @@ -69,7 +83,7 @@ private void validateCollectionQueryComparison(List queryParameters, Str } if (queryParameters.get(0) instanceof Collection) { - validateTypes(converter, Collection.class, queryParameters, queryPartDescription); + validateTypes(converter, Collection.class, queryParameters, filterOperation, queryPartDescription); } else { throw new IllegalArgumentException(queryPartDescription + ": invalid argument type, expecting Collection"); } @@ -84,7 +98,7 @@ private void validateCollectionQueryBetween(List queryParameters, String // Not Collection Object value = queryParameters.get(0); if (value instanceof Collection) { - validateTypes(converter, Collection.class, queryParameters, queryPartDescription); + validateTypes(converter, Collection.class, queryParameters, filterOperation, queryPartDescription); } else { throw new IllegalArgumentException(queryPartDescription + ": invalid argument type, expecting Collection"); } @@ -100,13 +114,14 @@ private void validateCollectionContainingTypes(PropertyPath property, List componentsClass = getCollectionElementsClass(property); if (componentsClass != null) { - validateTypes(converter, componentsClass, queryParameters, queryPartDescription, "Collection"); + validateTypes(converter, componentsClass, queryParameters, filterOperation, queryPartDescription, + "Collection"); } } } @@ -118,16 +133,33 @@ public Qualifier process() { if (filterOperation == BETWEEN || filterOperation == IN || filterOperation == NOT_IN) { setQualifierBuilderValue(qb, queryParameters.get(0)); - if (queryParameters.size() >= 2) setQualifierBuilderSecondValue(qb, queryParameters.get(1)); + if (queryParameters.size() == 2) setQualifierBuilderSecondValue(qb, queryParameters.get(1)); } - if (!(queryParameters.get(0) instanceof Collection)) { - // CONTAINING - op = getCorrespondingListFilterOperationOrFail(op); + List dotPath = null; + if (isNested) { // POJO field + if (op == CONTAINING || op == NOT_CONTAINING) { + qb.setNestedType(ParticleType.LIST); + } + + // getting MAP_VAL_ operation because the property is in a POJO which is represented by a Map in DB + op = getCorrespondingMapValueFilterOperationOrFail(op); + + if (queryParameters.isEmpty() && (filterOperation == IS_NOT_NULL || filterOperation == IS_NULL)) { + setQualifierBuilderValue(qb, property.getFieldName()); + } else { + setQualifierBuilderValue(qb, queryParameters.get(0)); + setQualifierBuilderKey(qb, property.getFieldName()); + } + dotPath = List.of(part.getProperty().toDotPath()); + } else { // first level + if (op == CONTAINING || op == NOT_CONTAINING) { + op = getCorrespondingListFilterOperationOrFail(op); + } + setQualifierBuilderValue(qb, queryParameters.get(0)); } - setQualifierBuilderValue(qb, queryParameters.get(0)); - return setQualifier(qb, fieldName, op, part, null); + return setQualifier(qb, fieldName, op, part, dotPath); } private FilterOperation getCorrespondingListFilterOperationOrFail(FilterOperation op) { diff --git a/src/main/java/org/springframework/data/aerospike/repository/query/MapQueryCreator.java b/src/main/java/org/springframework/data/aerospike/repository/query/MapQueryCreator.java index b77a205da..7b6eaeccc 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/query/MapQueryCreator.java +++ b/src/main/java/org/springframework/data/aerospike/repository/query/MapQueryCreator.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query; import com.aerospike.client.Value; +import com.aerospike.client.command.ParticleType; import org.springframework.data.aerospike.convert.MappingAerospikeConverter; import org.springframework.data.aerospike.mapping.AerospikePersistentProperty; import org.springframework.data.aerospike.query.FilterOperation; @@ -14,14 +15,7 @@ import java.util.Map; import static org.springframework.data.aerospike.query.FilterOperation.*; -import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.getCorrespondingMapValueFilterOperationOrFail; -import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.getValueOfQueryParameter; -import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.isAssignableValueOrConverted; -import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.qualifierAndConcatenated; -import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifier; -import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderKey; -import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderSecondValue; -import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderValue; +import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.*; import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeNullQueryCriterion.NULL_PARAM; import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeQueryCriterion.KEY_VALUE_PAIR; @@ -33,16 +27,18 @@ public class MapQueryCreator implements IAerospikeQueryCreator { private final List queryParameters; private final FilterOperation filterOperation; private final MappingAerospikeConverter converter; + private final boolean isNested; public MapQueryCreator(Part part, AerospikePersistentProperty property, String fieldName, List queryParameters, FilterOperation filterOperation, - MappingAerospikeConverter converter) { + MappingAerospikeConverter converter, boolean isNested) { this.part = part; this.property = property; this.fieldName = fieldName; this.queryParameters = queryParameters; this.filterOperation = filterOperation; this.converter = converter; + this.isNested = isNested; } @Override @@ -52,8 +48,9 @@ public void validate() { case CONTAINING, NOT_CONTAINING -> validateMapQueryContaining(queryPartDescription); case EQ, NOTEQ, GT, GTEQ, LT, LTEQ -> validateMapQueryComparison(queryPartDescription); case BETWEEN -> validateMapQueryBetween(queryPartDescription); - default -> throw new UnsupportedOperationException( - String.format("Unsupported operation: %s applied to %s", filterOperation, property)); + case IN, NOT_IN -> validateQueryIn(queryParameters, queryPartDescription); + case IS_NOT_NULL, IS_NULL -> validateQueryIsNull(queryParameters, queryPartDescription); + default -> throw new UnsupportedOperationException("Unsupported operation: " + queryPartDescription); } } @@ -175,46 +172,78 @@ public Qualifier process() { Qualifier qualifier; QualifierBuilder qb = Qualifier.builder(); int paramsSize = queryParameters.size(); + List dotPath = null; + FilterOperation op = filterOperation; if (filterOperation == BETWEEN || filterOperation == IN || filterOperation == NOT_IN) { setQualifierBuilderValue(qb, queryParameters.get(0)); - if (queryParameters.size() >= 2) setQualifierBuilderSecondValue(qb, queryParameters.get(1)); - qualifier = setQualifier(qb, fieldName, filterOperation, part, null); + if (queryParameters.size() == 2) setQualifierBuilderSecondValue(qb, queryParameters.get(1)); + if (isNested) { + setQualifierBuilderKey(qb, property.getFieldName()); + dotPath = List.of(part.getProperty().toDotPath()); + // getting MAP_VAL_ operation because the property is in a POJO which is represented by a Map in DB + op = getCorrespondingMapValueFilterOperationOrFail(filterOperation); + } + qualifier = setQualifier(qb, fieldName, op, part, dotPath); return qualifier; } - if (paramsSize == 2) { - qualifier = processMapTwoParams(part, queryParameters, filterOperation, fieldName); - } else if (queryParameters.size() < 2) { - setQualifierBuilderValue(qb, queryParameters.get(0)); - qualifier = setQualifier(qb, fieldName, filterOperation, part, null); - } else { // multiple parameters - qualifier = processMapMultipleParams(part, queryParameters, filterOperation, fieldName); + if (isNested) { // POJO field + if (op == CONTAINING || op == NOT_CONTAINING) { + // for nested MapContaining queries + qb.setNestedType(ParticleType.MAP); + } + + if (paramsSize == 2) { + setQualifierBuilderKey(qb, property.getFieldName()); + qualifier = processMapTwoParams(qb, part, queryParameters, filterOperation, fieldName); + } else if (queryParameters.size() < 2) { + if (queryParameters.isEmpty() && (filterOperation == IS_NOT_NULL || filterOperation == IS_NULL)) { + setQualifierBuilderValue(qb, property.getFieldName()); + } else { + setQualifierBuilderValue(qb, queryParameters.get(0)); + setQualifierBuilderKey(qb, property.getFieldName()); + } + // getting MAP_VAL_ operation because the property is in a POJO which is represented by a Map in DB + op = getCorrespondingMapValueFilterOperationOrFail(filterOperation); + dotPath = List.of(part.getProperty().toDotPath()); + qualifier = setQualifier(qb, fieldName, op, part, dotPath); + } else { // multiple parameters + qualifier = processMapMultipleParams(qb); + } + } else { + if (paramsSize == 2) { + qualifier = processMapTwoParams(qb, part, queryParameters, filterOperation, fieldName); + } else if (queryParameters.size() < 2) { + setQualifierBuilderValue(qb, queryParameters.get(0)); + qualifier = setQualifier(qb, fieldName, filterOperation, part, dotPath); + } else { // multiple parameters + qualifier = processMapMultipleParams(qb); + } } return qualifier; } - private Qualifier processMapTwoParams(Part part, List params, FilterOperation op, String fieldName) { + private Qualifier processMapTwoParams(QualifierBuilder qb, Part part, List params, FilterOperation op, + String fieldName) { Qualifier qualifier; if (op == FilterOperation.CONTAINING) { - qualifier = processMapContaining(part, fieldName, MAP_KEYS_CONTAIN, MAP_VALUES_CONTAIN, + qualifier = processMapContaining(qb, part, fieldName, MAP_KEYS_CONTAIN, MAP_VALUES_CONTAIN, MAP_VAL_EQ_BY_KEY); } else if (op == FilterOperation.NOT_CONTAINING) { - qualifier = processMapContaining(part, fieldName, MAP_KEYS_NOT_CONTAIN, MAP_VALUES_NOT_CONTAIN, + qualifier = processMapContaining(qb, part, fieldName, MAP_KEYS_NOT_CONTAIN, MAP_VALUES_NOT_CONTAIN, MAP_VAL_NOTEQ_BY_KEY); } else { - qualifier = processMapOtherThanContaining(part, params, op, fieldName); + qualifier = processMapOtherThanContaining(qb, part, params, op, fieldName); } return qualifier; } - private Qualifier processMapContaining(Part part, String fieldName, FilterOperation keysOp, + private Qualifier processMapContaining(QualifierBuilder qb, Part part, String fieldName, FilterOperation keysOp, FilterOperation valuesOp, FilterOperation byKeyOp) { FilterOperation op = byKeyOp; - QualifierBuilder qb = Qualifier.builder(); - if (queryParameters.get(0) instanceof AerospikeQueryCriterion queryCriterion) { switch (queryCriterion) { case KEY -> { @@ -231,9 +260,9 @@ private Qualifier processMapContaining(Part part, String fieldName, FilterOperat return setQualifier(qb, fieldName, op, part, null); } - private Qualifier processMapOtherThanContaining(Part part, List queryParameters, FilterOperation op, + private Qualifier processMapOtherThanContaining(QualifierBuilder qb, Part part, List queryParameters, + FilterOperation op, String fieldName) { - QualifierBuilder qb = Qualifier.builder(); Object param1 = queryParameters.get(0); List dotPath = List.of(part.getProperty().toDotPath(), Value.get(param1).toString()); @@ -246,33 +275,41 @@ private Qualifier processMapOtherThanContaining(Part part, List queryPar return setQualifier(qb, fieldName, op, part, dotPath); } - private Qualifier processMapMultipleParams(Part part, List params, FilterOperation op, String fieldName) { - if (op == FilterOperation.CONTAINING || op == FilterOperation.NOT_CONTAINING) { - return processMapMultipleParamsContaining(part, params, op, fieldName); + private Qualifier processMapMultipleParams(QualifierBuilder qb) { + if (filterOperation == FilterOperation.CONTAINING || filterOperation == FilterOperation.NOT_CONTAINING) { + return processMapMultipleParamsContaining(qb, part, queryParameters, filterOperation, fieldName, isNested); } else { - return processMapOtherThanContaining(part, params, op, fieldName); + return processMapOtherThanContaining(qb, part, queryParameters, filterOperation, fieldName); } } - private Qualifier processMapMultipleParamsContaining(Part part, List params, FilterOperation op, - String fieldName) { + private Qualifier processMapMultipleParamsContaining(QualifierBuilder qb, Part part, List params, + FilterOperation op, String fieldName, boolean isNested) { List dotPath; - QualifierBuilder qb = Qualifier.builder(); AerospikeQueryCriterion queryCriterion; Object firstParam = params.get(0); if (firstParam instanceof AerospikeQueryCriterion) { - queryCriterion = (AerospikeQueryCriterion) params.get(0); + queryCriterion = (AerospikeQueryCriterion) firstParam; if (queryCriterion == KEY_VALUE_PAIR) { - switch (op) { - case EQ, CONTAINING -> op = MAP_VAL_EQ_BY_KEY; - case NOTEQ -> op = MAP_VAL_NOTEQ_BY_KEY; - case NOT_CONTAINING -> op = MAP_VAL_NOT_CONTAINING_BY_KEY; + Value key; + if (isNested) { + switch (op) { + case EQ, CONTAINING -> op = MAP_VAL_CONTAINING_BY_KEY; + case NOTEQ, NOT_CONTAINING -> op = MAP_VAL_NOT_CONTAINING_BY_KEY; + } + key = getValueOfQueryParameter(property.getFieldName()); + setQualifierBuilderSecondKey(qb, params.get(1)); + } else { + switch (op) { + case EQ, CONTAINING -> op = MAP_VAL_EQ_BY_KEY; + case NOTEQ, NOT_CONTAINING -> op = MAP_VAL_NOTEQ_BY_KEY; + } + key = getValueOfQueryParameter(params.get(1)); } - Value key = getValueOfQueryParameter(params.get(1)); qb.setKey(key); dotPath = List.of(part.getProperty().toDotPath(), key.toString()); - setQualifierBuilderValue(qb, queryParameters.get(2)); + setQualifierBuilderValue(qb, params.get(2)); } else { throw new UnsupportedOperationException("Unsupported parameter: " + queryCriterion); } diff --git a/src/main/java/org/springframework/data/aerospike/repository/query/PojoQueryCreator.java b/src/main/java/org/springframework/data/aerospike/repository/query/PojoQueryCreator.java index 27341795b..5f1ec1dea 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/query/PojoQueryCreator.java +++ b/src/main/java/org/springframework/data/aerospike/repository/query/PojoQueryCreator.java @@ -12,12 +12,16 @@ import static org.springframework.data.aerospike.query.FilterOperation.BETWEEN; import static org.springframework.data.aerospike.query.FilterOperation.IN; +import static org.springframework.data.aerospike.query.FilterOperation.IS_NOT_NULL; +import static org.springframework.data.aerospike.query.FilterOperation.IS_NULL; import static org.springframework.data.aerospike.query.FilterOperation.NOT_IN; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.getCorrespondingMapValueFilterOperationOrFail; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifier; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderKey; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderSecondValue; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderValue; +import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.validateQueryIn; +import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.validateQueryIsNull; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.validateTypes; public class PojoQueryCreator implements IAerospikeQueryCreator { @@ -53,10 +57,9 @@ public void validate() { case EQ, NOTEQ, GT, GTEQ, LT, LTEQ -> validatePojoQueryComparison(queryParameters, queryPartDescription); case BETWEEN -> validatePojoQueryBetween(queryParameters, queryPartDescription); - case IN, NOT_IN -> validatePojoQueryIn(queryParameters, queryPartDescription); - case IS_NOT_NULL, IS_NULL -> validatePojoQueryIsNull(queryParameters, queryPartDescription); - default -> throw new UnsupportedOperationException( - String.format("Unsupported operation: %s applied to %s", filterOperation, property)); + case IN, NOT_IN -> validateQueryIn(queryParameters, queryPartDescription); + case IS_NOT_NULL, IS_NULL -> validateQueryIsNull(queryParameters, queryPartDescription); + default -> throw new UnsupportedOperationException("Unsupported operation: " + queryPartDescription); } validateTypes(converter, propertyPath, filterOperation, queryParameters); @@ -78,36 +81,36 @@ private void validatePojoQueryBetween(List queryParameters, String query } } - private void validatePojoQueryIn(List queryParameters, String queryPartDescription) { - // Number of arguments is not one - if (queryParameters.size() != 1) { - throw new IllegalArgumentException(queryPartDescription + ": invalid number of arguments, expecting one"); - } - } - - private void validatePojoQueryIsNull(List queryParameters, String queryPartDescription) { - // Number of arguments is not zero - if (!queryParameters.isEmpty()) { - throw new IllegalArgumentException(queryPartDescription + ": expecting no arguments"); - } - } - @Override public Qualifier process() { - List dotPath = null; + Qualifier qualifier; QualifierBuilder qb = Qualifier.builder(); FilterOperation op = filterOperation; + List dotPath = null; + if (filterOperation == BETWEEN || filterOperation == IN || filterOperation == NOT_IN) { setQualifierBuilderValue(qb, queryParameters.get(0)); - if (queryParameters.size() >=2) setQualifierBuilderSecondValue(qb, queryParameters.get(1)); + if (queryParameters.size() == 2) setQualifierBuilderSecondValue(qb, queryParameters.get(1)); + if (isNested) { + setQualifierBuilderKey(qb, property.getFieldName()); + dotPath = List.of(part.getProperty().toDotPath()); + // getting MAP_VAL_ operation because the property is in a POJO which is represented by a Map in DB + op = getCorrespondingMapValueFilterOperationOrFail(filterOperation); + } + qualifier = setQualifier(qb, fieldName, op, part, dotPath); + return qualifier; } if (isNested) { // POJO field // getting MAP_VAL_ operation because the property is in a POJO which is represented by a Map in DB op = getCorrespondingMapValueFilterOperationOrFail(filterOperation); - setQualifierBuilderKey(qb, property.getFieldName()); - setQualifierBuilderValue(qb, queryParameters.get(0)); + if (queryParameters.isEmpty() && (filterOperation == IS_NOT_NULL || filterOperation == IS_NULL)) { + setQualifierBuilderValue(qb, property.getFieldName()); + } else { + setQualifierBuilderValue(qb, queryParameters.get(0)); + setQualifierBuilderKey(qb, property.getFieldName()); + } dotPath = List.of(part.getProperty().toDotPath()); } else { // first level POJO if (op != FilterOperation.BETWEEN) { diff --git a/src/main/java/org/springframework/data/aerospike/repository/query/SimplePropertyQueryCreator.java b/src/main/java/org/springframework/data/aerospike/repository/query/SimplePropertyQueryCreator.java index 9e543d21c..3eab7720d 100644 --- a/src/main/java/org/springframework/data/aerospike/repository/query/SimplePropertyQueryCreator.java +++ b/src/main/java/org/springframework/data/aerospike/repository/query/SimplePropertyQueryCreator.java @@ -1,6 +1,7 @@ package org.springframework.data.aerospike.repository.query; import com.aerospike.client.Value; +import com.aerospike.client.command.ParticleType; import org.springframework.data.aerospike.convert.MappingAerospikeConverter; import org.springframework.data.aerospike.mapping.AerospikePersistentProperty; import org.springframework.data.aerospike.query.FilterOperation; @@ -13,13 +14,16 @@ import java.util.List; import static org.springframework.data.aerospike.query.FilterOperation.BETWEEN; +import static org.springframework.data.aerospike.query.FilterOperation.CONTAINING; import static org.springframework.data.aerospike.query.FilterOperation.IS_NOT_NULL; import static org.springframework.data.aerospike.query.FilterOperation.IS_NULL; +import static org.springframework.data.aerospike.query.FilterOperation.NOT_CONTAINING; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.getCorrespondingMapValueFilterOperationOrFail; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifier; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderKey; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderSecondValue; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.setQualifierBuilderValue; +import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.validateQueryIsNull; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.validateTypes; public class SimplePropertyQueryCreator implements IAerospikeQueryCreator { @@ -55,22 +59,28 @@ public void validate() { switch (filterOperation) { case CONTAINING, NOT_CONTAINING, GT, GTEQ, LT, LTEQ, LIKE, STARTS_WITH, ENDS_WITH, EQ, NOTEQ -> { validateSimplePropertyQueryComparison(queryPartDescription, queryParameters); - validateTypes(converter, propertyPath, queryParameters, queryPartDescription); + validateSimplePropertyContaining(queryPartDescription, queryParameters, filterOperation, propertyPath); } case IN, NOT_IN -> { validateSimplePropertyQueryComparison(queryPartDescription, queryParameters); - validateSimplePropertyInQueryTypes(queryPartDescription, queryParameters); } case BETWEEN -> { validateSimplePropertyQueryBetween(queryPartDescription, queryParameters); - validateTypes(converter, propertyPath, queryParameters, queryPartDescription); } case IS_NOT_NULL, IS_NULL -> { - validateSimplePropertyQueryIsNull(queryPartDescription, queryParameters); - validateTypes(converter, propertyPath, queryParameters, queryPartDescription); + validateQueryIsNull(queryParameters, queryPartDescription); } - default -> throw new UnsupportedOperationException( - String.format("Unsupported operation: %s applied to %s", filterOperation, property)); + default -> throw new UnsupportedOperationException("Unsupported operation: " + queryPartDescription); + } + validateTypes(converter, propertyPath, queryParameters, filterOperation, queryPartDescription); + } + + private void validateSimplePropertyContaining(String queryPartDescription, List queryParameters, + FilterOperation filterOperation, PropertyPath propertyPath) { + if ((filterOperation == CONTAINING || filterOperation == NOT_CONTAINING) + && (!propertyPath.getType().equals(String.class) || !(queryParameters.get(0) instanceof String))) { + throw new IllegalArgumentException(queryPartDescription + ": expecting both property and argument to be " + + "a String"); } } @@ -89,7 +99,7 @@ private void validateSimplePropertyQueryComparison(String queryPartDescription, private void validateSimplePropertyInQueryTypes(String queryPartDescription, List queryParameters) { Object param1 = queryParameters.get(0); if (param1 instanceof Collection) { - validateTypes(converter, Collection.class, queryParameters, queryPartDescription); + validateTypes(converter, Collection.class, queryParameters, filterOperation, queryPartDescription); } } @@ -100,16 +110,8 @@ private void validateSimplePropertyQueryBetween(String queryPartDescription, Lis } } - private void validateSimplePropertyQueryIsNull(String queryPartDescription, List queryParameters) { - // Number of arguments is not zero - if (!queryParameters.isEmpty()) { - throw new IllegalArgumentException(queryPartDescription + ": expecting no arguments"); - } - } - @Override public Qualifier process() { - List dotPath = null; QualifierBuilder qb = Qualifier.builder(); if (isBooleanQuery) { @@ -122,17 +124,23 @@ public Qualifier process() { setQualifierBuilderSecondValue(qb, queryParameters.get(1)); } - FilterOperation op = filterOperation; - if (isNested) { // POJO field - if (filterOperation == IS_NOT_NULL || filterOperation == IS_NULL) { - setQualifierBuilderValue(qb, property.getFieldName()); - } + if (filterOperation == CONTAINING || filterOperation == NOT_CONTAINING) { + // only a String can be used with CONTAINING, it is validated in validateSimplePropertyContaining() + qb.setNestedType(ParticleType.STRING); + } + List dotPath = null; + FilterOperation op = filterOperation; + if (isNested) { // POJO field // getting MAP_VAL_ operation because the property is in a POJO which is represented by a Map in DB op = getCorrespondingMapValueFilterOperationOrFail(op); - if (!queryParameters.isEmpty()) setQualifierBuilderValue(qb, queryParameters.get(0)); - setQualifierBuilderKey(qb, property.getFieldName()); + if (queryParameters.isEmpty() && (filterOperation == IS_NOT_NULL || filterOperation == IS_NULL)) { + setQualifierBuilderValue(qb, property.getFieldName()); + } else { + setQualifierBuilderValue(qb, queryParameters.get(0)); + setQualifierBuilderKey(qb, property.getFieldName()); + } dotPath = List.of(part.getProperty().toDotPath()); } else { // first level simple property setQualifierBuilderValue(qb, queryParameters.get(0)); diff --git a/src/main/java/org/springframework/data/aerospike/util/Utils.java b/src/main/java/org/springframework/data/aerospike/util/Utils.java index 6457004a7..a8ec7eff0 100644 --- a/src/main/java/org/springframework/data/aerospike/util/Utils.java +++ b/src/main/java/org/springframework/data/aerospike/util/Utils.java @@ -19,10 +19,12 @@ import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Info; import com.aerospike.client.ResultCode; +import com.aerospike.client.Value; import com.aerospike.client.cluster.Node; +import com.aerospike.client.command.ParticleType; +import com.aerospike.client.exp.Exp; import lombok.experimental.UtilityClass; import org.springframework.dao.InvalidDataAccessResourceUsageException; -import org.springframework.util.StringUtils; import java.io.File; import java.net.InetAddress; @@ -32,17 +34,22 @@ import java.nio.file.Path; import java.time.ZoneId; import java.time.temporal.Temporal; -import java.util.Arrays; import java.util.Currency; import java.util.Date; +import java.util.List; import java.util.Locale; -import java.util.Objects; -import java.util.Optional; +import java.util.Map; import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Pattern; +import static com.aerospike.client.command.ParticleType.BOOL; +import static com.aerospike.client.command.ParticleType.DOUBLE; +import static com.aerospike.client.command.ParticleType.INTEGER; +import static com.aerospike.client.command.ParticleType.LIST; +import static com.aerospike.client.command.ParticleType.MAP; +import static com.aerospike.client.command.ParticleType.STRING; import static org.springframework.util.ClassUtils.isPrimitiveOrWrapper; /** @@ -104,21 +111,36 @@ public static long getObjectsCount(Node node, String namespace, String setName) return InfoResponseUtils.getPropertyFromInfoResponse(infoString, "objects", Long::parseLong); } - public static Optional getIntegerProperty(String property) { - if (StringUtils.hasText(property)) { - int result; - try { - result = Integer.parseInt(property); - } catch (NumberFormatException e) { - return Optional.empty(); - } - return Optional.of(result); - } - return Optional.empty(); + /** + * Convert {@link Value} type to {@link Exp} + * + * @param value Value instance + * @param errMsg Error message to use + * @return Exp instance + */ + public static Exp getValueExpOrFail(Value value, String errMsg) { + return switch (value.getType()) { + case INTEGER -> Exp.val(value.toLong()); + case BOOL -> Exp.val((Boolean) value.getObject()); + case STRING -> Exp.val(value.toString()); + case LIST -> Exp.val((List) value.getObject()); + case MAP -> Exp.val((Map) value.getObject()); + case ParticleType.NULL -> Exp.nil(); + default -> throw new UnsupportedOperationException(errMsg); + }; } - public static boolean allArrayElementsAreNull(Object[] array) { - return Arrays.stream(array).allMatch(Objects::isNull); + public static Exp.Type getExpType(Value value) { + return switch (value.getType()) { + case INTEGER -> Exp.Type.INT; + case DOUBLE -> Exp.Type.FLOAT; + case BOOL -> Exp.Type.BOOL; + case STRING -> Exp.Type.STRING; + case LIST -> Exp.Type.LIST; + case MAP -> Exp.Type.MAP; + case ParticleType.NULL -> Exp.Type.NIL; + default -> throw new UnsupportedOperationException("Unsupported Value type: " + value.getType()); + }; } /** diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/BetweenTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/BetweenTests.java index c30a667d5..0b8bfd0cd 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/BetweenTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/BetweenTests.java @@ -47,6 +47,14 @@ void findByNestedSimplePropertyBetween_Integer() { TestUtils.setFriendsToNull(repository, oliver, dave, carter); } + @Test + void findByNestedSimplePropertyBetween_String() { + assertThat(carter.getAddress().getZipCode()).isEqualTo("C0124"); + assertThat(dave.getAddress().getZipCode()).isEqualTo("C0123"); + assertThat(repository.findByAddressZipCodeBetween("C0123", "C0124")) + .containsExactly(dave); + } + @Test void findByCollectionBetween_IntegerList() { List list1 = List.of(100, 200, 300); @@ -80,6 +88,22 @@ void findByCollectionBetween_NegativeTest() { .hasMessage("Person.ints BETWEEN: invalid argument type, expecting Collection"); } + @Test + void findByNestedCollectionBetween() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsBetween(List.of(1, 2, 3, 4), List.of(1, 2, 3, 4, 5)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByMapBetween() { if (serverVersionSupport.isFindByCDTSupported()) { @@ -128,6 +152,22 @@ void findByMapOfListsBetween() { } } + @Test + void findByNestedMapBetween() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntMapBetween(Map.of("1", 2, "3", 4), Map.of("1", 2, "3", 4, "5", 6)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByMapBetween_NegativeTest() { assertThatThrownBy(() -> negativeTestsRepository.findByIntMapBetween()) @@ -185,4 +225,21 @@ void findByPOJOBetween_NegativeTest() { .isInstanceOf(IllegalArgumentException.class) .hasMessage("Person.address BETWEEN: Type mismatch, expecting Address"); } + + @Test + void findByNestedPojoBetween() { + if (serverVersionSupport.isFindByCDTSupported()) { + Address address1 = new Address("Foo Street 1", 1, "C0123", "Bar"); + Address address2 = new Address("Foo Street 1", 2, "C0124", "Bar"); + assertThat(dave.getAddress()).isNotNull(); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendAddressBetween(address1, address2); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/ContainingTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/ContainingTests.java index 6bb4336e9..b2d221aea 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/ContainingTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/ContainingTests.java @@ -4,6 +4,7 @@ import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.Address; import org.springframework.data.aerospike.sample.Person; +import org.springframework.data.aerospike.util.TestUtils; import java.util.ArrayList; import java.util.HashMap; @@ -23,13 +24,13 @@ public class ContainingTests extends PersonRepositoryQueryTests { @Test - void findBySimplePropertyContaining_String() { + void findBySimplePropertyContainingString() { List persons = repository.findByFirstNameContaining("er"); assertThat(persons).containsExactlyInAnyOrder(carter, oliver, leroi, leroi2); } @Test - void findDistinctByStringSimplePropertyContaining() { + void findDistinctBySimplePropertyContainingString() { List persons = repository.findDistinctByFirstNameContaining("er"); assertThat(persons).hasSize(3); @@ -38,7 +39,7 @@ void findDistinctByStringSimplePropertyContaining() { } @Test - void findByNestedSimplePropertyContaining() { + void findByNestedSimplePropertyContainingString() { Address cartersAddress = carter.getAddress(); Address davesAddress = dave.getAddress(); @@ -78,6 +79,22 @@ void findByCollectionContaining() { assertThat(repository.findByIntsContaining(7777)).isEmpty(); } + @Test + void findByNestedCollectionContainingInteger() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsContaining(1); + + assertThat(result).containsOnly(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByCollectionContainingNull() { List strings = new ArrayList<>(); @@ -189,6 +206,38 @@ void findByMapValuesContainingList() { assertThat(persons3).isEmpty(); } + @Test + void findByMapContainingNullValue() { + Map stringMap = new HashMap<>(); + stringMap.put("key", null); + stefan.setStringMap(stringMap); + repository.save(stefan); + + // find Persons with stringMap containing null value (regardless of key) + assertThat(repository.findByStringMapContaining(VALUE, NULL_PARAM)).contains(stefan); + + // Currently getting key-specific results for a Map requires 2 steps: + // firstly query for all entities with existing map key + List personsWithMapKeyExists = repository.findByStringMapContaining(KEY, "key"); + // and then leave only the records that have the key's value == null + List personsWithMapValueNull = personsWithMapKeyExists.stream() + .filter(person -> person.getStringMap().get("key") == null).toList(); + assertThat(personsWithMapValueNull).contains(stefan); + + stefan.setStringMap(null); // cleanup + repository.save(stefan); + } + + @Test + void findByExactMapKeyAndValue_Integer() { + assertThat(carter.getIntMap()).containsKey("key1"); + assertThat(carter.getIntMap()).containsValue(0); + + List persons; + persons = repository.findByIntMapContaining(KEY_VALUE_PAIR, "key1", 0); + assertThat(persons).containsExactlyInAnyOrder(carter); + } + @Test void findByExactMapKeyAndValue_Boolean() { oliver.setMapOfBoolean(Map.of("test", true)); @@ -269,35 +318,81 @@ void findByExactMapKeyAndValue_POJO() { } @Test - void findByExactMapKeyAndValue_Integer() { - assertThat(carter.getIntMap()).containsKey("key1"); - assertThat(carter.getIntMap()).containsValue(0); + void findByExactNestedMapKeyAndValue_Integer() { + if (serverVersionSupport.isFindByCDTSupported()) { + assertThat(carter.getIntMap()).containsKey("key1"); + assertThat(carter.getIntMap()).containsValue(0); - List persons; - persons = repository.findByIntMapContaining(KEY_VALUE_PAIR, "key1", 0); - assertThat(persons).containsExactlyInAnyOrder(carter); + dave.setFriend(carter); + repository.save(dave); + + List result = repository.findByFriendIntMapContaining(KEY_VALUE_PAIR, "key1", 0); + + assertThat(result).containsOnly(dave); + TestUtils.setFriendsToNull(repository, dave); + } } @Test - void findByMapContainingNullValue() { - Map stringMap = new HashMap<>(); - stringMap.put("key", null); - stefan.setStringMap(stringMap); - repository.save(stefan); + void findByNestedMapKeysContainingString() { + if (serverVersionSupport.isFindByCDTSupported()) { + assertThat(donny.getStringMap()).containsKey("key1"); + assertThat(boyd.getStringMap()).containsKey("key1"); - // find Persons with stringMap containing null value (regardless of a key) - assertThat(repository.findByStringMapContaining(VALUE, NULL_PARAM)).contains(stefan); + dave.setFriend(donny); + repository.save(dave); + carter.setFriend(boyd); + repository.save(carter); - // Currently getting key-specific results for a Map requires 2 steps: - // firstly query for all entities with existing map key - List personsWithMapKeyExists = repository.findByStringMapContaining(KEY, "key"); - // and then leave only the records that have the key's value == null - List personsWithMapValueNull = personsWithMapKeyExists.stream() - .filter(person -> person.getStringMap().get("key") == null).toList(); - assertThat(personsWithMapValueNull).contains(stefan); + List persons = repository.findByFriendStringMapContaining(KEY, "key1"); + assertThat(persons).contains(dave, carter); + TestUtils.setFriendsToNull(repository, dave, carter); + } + } - stefan.setStringMap(null); // cleanup - repository.save(stefan); + @Test + void findByNestedMapValuesContainingString() { + if (serverVersionSupport.isFindByCDTSupported()) { + assertThat(donny.getStringMap()).containsValue("val1"); + assertThat(boyd.getStringMap()).containsValue("val1"); + + dave.setFriend(donny); + repository.save(dave); + carter.setFriend(boyd); + repository.save(carter); + + List persons = repository.findByFriendStringMapContaining(VALUE, "val1"); + assertThat(persons).contains(dave, carter); + TestUtils.setFriendsToNull(repository, dave, carter); + } + } + + @Test + void findByNestedMapContainingNullValue() { + if (serverVersionSupport.isFindByCDTSupported()) { + Map stringMap = new HashMap<>(); + stringMap.put("key", null); + stefan.setStringMap(stringMap); + repository.save(stefan); + + dave.setFriend(stefan); + repository.save(dave); + + // find Persons with stringMap containing null value (regardless of key) + assertThat(repository.findByFriendStringMapContaining(VALUE, NULL_PARAM)).contains(dave); + + // Currently getting key-specific results for a Map requires 2 steps: + // firstly query for all entities with existing map key + List personsWithMapKeyExists = repository.findByFriendStringMapContaining(KEY, "key"); + // and then leave only the records that have the key's value == null + List personsWithMapValueNull = personsWithMapKeyExists.stream() + .filter(person -> person.getFriend().getStringMap().get("key") == null).toList(); + assertThat(personsWithMapValueNull).contains(dave); + + TestUtils.setFriendsToNull(repository, dave); // cleanup + stefan.setStringMap(null); + repository.save(stefan); + } } @Test 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 8756be2bf..3bc3ec614 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 @@ -197,7 +197,11 @@ void findBySimpleProperty_AND_simpleProperty_DynamicProjection() { @Test void findByNestedSimplePropertyEquals() { - String zipCode = "C012345"; + String zipCode = "C0124"; + assertThat(carter.getAddress().getZipCode()).isEqualTo(zipCode); + assertThat(repository.findByAddressZipCode(zipCode)).containsExactly(carter); + + zipCode = "C012345"; Address address = new Address("Foo Street 1", 1, zipCode, "Bar"); dave.setAddress(address); repository.save(dave); @@ -208,7 +212,6 @@ void findByNestedSimplePropertyEquals() { List result = repository.findByFriendAddressZipCode(zipCode); assertThat(result).containsExactly(carter); - TestUtils.setFriendsToNull(repository, carter); } @@ -338,6 +341,23 @@ void findByCollectionEquals_NegativeTest() { .hasMessage("Person.strings EQ: invalid number of arguments, expecting one"); } + @Test + void findByNestedCollectionEquals() { + if (serverVersionSupport.isFindByCDTSupported()) { + var ints = List.of(1, 2, 3, 4); + dave.setInts(ints); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendInts(ints); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByMapEquals() { if (serverVersionSupport.isFindByCDTSupported()) { @@ -353,6 +373,23 @@ void findByMapEquals() { } } + @Test + void findByNestedMapEquals() { + if (serverVersionSupport.isFindByCDTSupported()) { + var intMap = Map.of("1", 2, "3", 4); + dave.setIntMap(intMap); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntMap(intMap); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByMapEquals_NegativeTest() { assertThatThrownBy(() -> negativeTestsRepository.findByStringMapEquals("map1")) diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/ExistsTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/ExistsTests.java index 59c8d1ded..1767cbd93 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/ExistsTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/ExistsTests.java @@ -4,7 +4,13 @@ import org.junit.jupiter.api.Test; import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.Address; +import org.springframework.data.aerospike.sample.Person; +import org.springframework.data.aerospike.util.TestUtils; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; /** @@ -39,6 +45,38 @@ void findByNestedSimplePropertyExists() { repository.save(stefan); } + @Test + void findByNestedCollectionExists() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsExists(); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedMapExists() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntMapExists(); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByPOJOExists() { Assertions.assertThat(stefan.getAddress()).isNull(); @@ -50,6 +88,21 @@ void findByPOJOExists() { .doesNotContain(stefan); } + @Test + void findByNestedPojoExists() { + if (serverVersionSupport.isFindByCDTSupported()) { + assertThat(dave.getAddress()).isNotNull(); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendAddressExists(); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByPOJOExistsNegativeTest() { assertThatThrownBy(() -> negativeTestsRepository.findByAddressExists(new Address(null, null, null, null))) diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/GreaterThanOrEqualTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/GreaterThanOrEqualTests.java index c13880d83..4d12c864c 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/GreaterThanOrEqualTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/GreaterThanOrEqualTests.java @@ -2,9 +2,12 @@ import org.junit.jupiter.api.Test; import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; +import org.springframework.data.aerospike.sample.Address; import org.springframework.data.aerospike.sample.Person; +import org.springframework.data.aerospike.util.TestUtils; import java.util.List; +import java.util.Map; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -15,7 +18,14 @@ public class GreaterThanOrEqualTests extends PersonRepositoryQueryTests { @Test - void findByCollectionGreaterThanOrEqual() { + void findByNestedSimplePropertyGreaterThan_String() { + String zipCode = "C0124"; + assertThat(carter.getAddress().getZipCode()).isEqualTo(zipCode); + assertThat(repository.findByAddressZipCodeGreaterThanEqual(zipCode)).containsExactly(carter); + } + + @Test + void findByCollectionGreaterThanOrEqual_Set() { if (serverVersionSupport.isFindByCDTSupported()) { Set setToCompareWith = Set.of(0, 1, 2, 3, 4); dave.setIntSet(setToCompareWith); @@ -26,4 +36,65 @@ void findByCollectionGreaterThanOrEqual() { assertThat(persons).contains(dave); } } + + @Test + void findByCollectionGreaterThanOrEqual_List() { + if (serverVersionSupport.isFindByCDTSupported()) { + List listToCompareWith = List.of(1, 2); + dave.setInts(listToCompareWith); + repository.save(dave); + assertThat(dave.getInts()).isEqualTo(listToCompareWith); + + List persons = repository.findByIntsGreaterThanEqual(List.of(1, 1, 1, 1, 1, 1, 10)); + assertThat(persons).contains(dave); + } + } + + @Test + void findByNestedCollectionGreaterThanOrEqual() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsGreaterThanEqual(List.of(1, 2, 3, 4)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedMapGreaterThanOrEqual() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntMapGreaterThanEqual(Map.of("1", 2, "3", 4)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedPojoGreaterThanOrEqual() { + if (serverVersionSupport.isFindByCDTSupported()) { + Address address = new Address("Foo Street 1", 1, "C0123", "Bar"); + assertThat(carter.getAddress()).isNotNull(); + + dave.setFriend(carter); + repository.save(dave); + + List result = repository.findByFriendAddressGreaterThanEqual(address); + + assertThat(result).contains(dave); + TestUtils.setFriendsToNull(repository, dave); + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/GreaterThanTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/GreaterThanTests.java index b9fa004ca..61287192c 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/GreaterThanTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/GreaterThanTests.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Test; import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; +import org.springframework.data.aerospike.sample.Address; import org.springframework.data.aerospike.sample.Person; import org.springframework.data.aerospike.sample.PersonSomeFields; import org.springframework.data.aerospike.util.TestUtils; @@ -88,6 +89,28 @@ void findBySimpleProperty_Integer_Unpaged() { assertThat(slice.isLast()).isTrue(); } + @Test + void findBySimplePropertyGreaterThan_Integer_projection() { + Slice slice = repository.findPersonSomeFieldsByAgeGreaterThan(40, PageRequest.of(0, 10)); + + assertThat(slice.hasContent()).isTrue(); + assertThat(slice.hasNext()).isFalse(); + assertThat(slice.getContent()).hasSize(4).contains(dave.toPersonSomeFields(), + carter.toPersonSomeFields(), boyd.toPersonSomeFields(), leroi.toPersonSomeFields()); + } + + @Test + void findBySimplePropertyGreaterThan_String() { + List result = repository.findByFirstNameGreaterThan("Leroa"); + assertThat(result).contains(leroi, leroi2); + } + + @Test + void findByNestedSimplePropertyGreaterThan_String() { + assertThat(carter.getAddress().getZipCode()).isEqualTo("C0124"); + assertThat(repository.findByAddressZipCodeGreaterThan("C0123")).containsExactly(carter); + } + @Test void findByNestedSimplePropertyGreaterThan_Integer() { alicia.setFriend(boyd); @@ -111,22 +134,6 @@ void findByNestedSimplePropertyGreaterThan_Integer() { TestUtils.setFriendsToNull(repository, alicia, dave, carter, leroi); } - @Test - void findBySimplePropertyGreaterThan_Integer_projection() { - Slice slice = repository.findPersonSomeFieldsByAgeGreaterThan(40, PageRequest.of(0, 10)); - - assertThat(slice.hasContent()).isTrue(); - assertThat(slice.hasNext()).isFalse(); - assertThat(slice.getContent()).hasSize(4).contains(dave.toPersonSomeFields(), - carter.toPersonSomeFields(), boyd.toPersonSomeFields(), leroi.toPersonSomeFields()); - } - - @Test - void findBySimplePropertyGreaterThan_String() { - List result = repository.findByFirstNameGreaterThan("Leroa"); - assertThat(result).contains(leroi, leroi2); - } - @Test void findByCollectionGreaterThan() { List listToCompare1 = List.of(100, 200, 300, 400); @@ -205,6 +212,22 @@ void findByCollection_NegativeTest() { .hasMessage("Person.ints GT: invalid argument type, expecting Collection"); } + @Test + void findByNestedCollectionGreaterThan() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsGreaterThan(List.of(1, 2, 3, 3)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByMapGreaterThan() { if (serverVersionSupport.isFindByCDTSupported()) { @@ -216,4 +239,36 @@ void findByMapGreaterThan() { assertThat(persons).containsExactlyInAnyOrder(boyd); } } + + @Test + void findByNestedMapGreaterThan() { + if (serverVersionSupport.isFindByCDTSupported()) { + Address address = new Address("Foo Street 1", 1, "C0123", "Bar"); + assertThat(carter.getAddress()).isNotNull(); + + dave.setFriend(carter); + repository.save(dave); + + List result = repository.findByFriendAddressGreaterThan(address); + + assertThat(result).contains(dave); + TestUtils.setFriendsToNull(repository, dave); + } + } + + @Test + void findByNestedPojoGreaterThan() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntMapGreaterThan(Map.of("1", 2, "3", 3)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/InTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/InTests.java index 6454d1d95..fa79b2cc1 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/InTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/InTests.java @@ -2,9 +2,12 @@ import org.junit.jupiter.api.Test; import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; +import org.springframework.data.aerospike.sample.Address; import org.springframework.data.aerospike.sample.Person; +import org.springframework.data.aerospike.util.TestUtils; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -23,4 +26,65 @@ void findBySimplePropertyIn_String() { result = repository.findByFirstNameIn(List.of("Alicia", "Stefan")); assertThat(result).contains(alicia, stefan); } + + @Test + void findByNestedSimplePropertyIn_String() { + assertThat(carter.getAddress().getZipCode()).isEqualTo("C0124"); + assertThat(dave.getAddress().getZipCode()).isEqualTo("C0123"); + assertThat(repository.findByAddressZipCodeIn(List.of("C0123", "C0124", "C0125"))) + .containsExactlyInAnyOrder(dave, carter); + } + + @Test + void findByNestedCollectionIn() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsIn(List.of(List.of(0, 1, 2, 3, 4, 5, 6, 7), + List.of(1, 2, 3), List.of(1, 2, 3, 4))); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedMapIn() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntMapIn(List.of(Map.of("0", 1, "2", 3, "4", 5, "6", 7), + Map.of("1", 2, "3", 4567), Map.of("1", 2, "3", 4))); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedPojoIn() { + if (serverVersionSupport.isFindByCDTSupported()) { + Address address1 = new Address("Foo Street 1", 1, "C0123", "Bar"); + Address address2 = new Address("Foo Street 2", 2, "C0124", "C0123"); + Address address3 = new Address("Foo Street 1", 23, "C0125", "Bar"); + Address address4 = new Address("Foo Street 1", 456, "C0126", "Bar"); + assertThat(carter.getAddress()).isEqualTo(address2); + + dave.setFriend(carter); + repository.save(dave); + + List result = repository.findByFriendAddressIn(List.of(address1, address2, address3, address4)); + + assertThat(result).contains(dave); + TestUtils.setFriendsToNull(repository, dave); + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/LessThanOrEqualTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/LessThanOrEqualTests.java index 116f03488..ce42483df 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/LessThanOrEqualTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/LessThanOrEqualTests.java @@ -2,10 +2,12 @@ import org.junit.jupiter.api.Test; import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; +import org.springframework.data.aerospike.sample.Address; import org.springframework.data.aerospike.sample.Person; import org.springframework.data.aerospike.util.TestUtils; import java.util.List; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -27,10 +29,62 @@ void findByNestedSimplePropertyLessThanOrEqual_Integer() { List result = repository.findByFriendAgeLessThanEqual(42); - assertThat(result) - .hasSize(2) - .containsExactlyInAnyOrder(dave, carter); - + assertThat(result).containsExactlyInAnyOrder(dave, carter); TestUtils.setFriendsToNull(repository, alicia, dave, carter, leroi); } + + @Test + void findByNestedSimplePropertyGreaterThan_String() { + assertThat(carter.getAddress().getZipCode()).isEqualTo("C0124"); + assertThat(dave.getAddress().getZipCode()).isEqualTo("C0123"); + assertThat(repository.findByAddressZipCodeLessThanEqual("C0125")).containsExactlyInAnyOrder(carter, dave); + } + + @Test + void findByNestedCollectionLessThanOrEqual() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsLessThanEqual(List.of(1, 2, 3, 4, 5)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedMapLessThanOrEqual() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntMapLessThanEqual(Map.of("1", 2, "3", 4, "5", 6)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedPojoLessThanOrEqual() { + if (serverVersionSupport.isFindByCDTSupported()) { + Address address = new Address("Foo Street 1", 2, "C0124", "Bar"); + assertThat(dave.getAddress()).isNotNull(); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendAddressLessThanEqual(address); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/LessThanTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/LessThanTests.java index d2bdd708f..bb74db1fb 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/LessThanTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/LessThanTests.java @@ -4,10 +4,12 @@ import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.Address; import org.springframework.data.aerospike.sample.Person; +import org.springframework.data.aerospike.util.TestUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import java.util.List; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -27,6 +29,13 @@ void findBySimpleProperty_Integer_Unpaged() { assertThat(page.getTotalElements()).isEqualTo(page.getSize()); } + @Test + void findByNestedSimplePropertyGreaterThan_String() { + assertThat(carter.getAddress().getZipCode()).isEqualTo("C0124"); + assertThat(dave.getAddress().getZipCode()).isEqualTo("C0123"); + assertThat(repository.findByAddressZipCodeLessThan("C0124")).containsExactly(dave); + } + @Test void findByCollectionLessThan() { if (serverVersionSupport.isFindByCDTSupported()) { @@ -63,6 +72,38 @@ void findPersonsByCollectionLessThan_NegativeTest() { .hasMessage("Person.strings LT: invalid number of arguments, expecting one"); } + @Test + void findByNestedCollectionLessThan() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsLessThan(List.of(1, 2, 3, 4, 5)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedMapLessThan() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntMapLessThan(Map.of("1", 2, "3", 4, "5", 6)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByMapLessThanNegativeTest() { assertThatThrownBy(() -> negativeTestsRepository.findByIntMapLessThan(100)) @@ -95,4 +136,20 @@ void findByPOJOLessThan() { assertThat(persons).containsExactlyInAnyOrder(dave, boyd); } } + + @Test + void findByNestedPojoLessThan() { + if (serverVersionSupport.isFindByCDTSupported()) { + Address address = new Address("Foo Street 1", 2, "C0124", "Bar"); + assertThat(dave.getAddress()).isNotNull(); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendAddressLessThan(address); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotContainingTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotContainingTests.java index d562e8db6..32731c487 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotContainingTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotContainingTests.java @@ -4,6 +4,7 @@ import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.Address; import org.springframework.data.aerospike.sample.Person; +import org.springframework.data.aerospike.util.TestUtils; import java.util.ArrayList; import java.util.HashMap; @@ -24,13 +25,13 @@ public class NotContainingTests extends PersonRepositoryQueryTests { @Test - void findBySimplePropertyNotContaining_String() { + void findBySimplePropertyNotContainingString() { List persons = repository.findByFirstNameNotContaining("er"); assertThat(persons).containsExactlyInAnyOrder(dave, donny, alicia, boyd, stefan, matias, douglas); } @Test - void findByNestedSimplePropertyNotContaining() { + void findByNestedSimplePropertyNotContainingString() { Address cartersAddress = carter.getAddress(); Address davesAddress = dave.getAddress(); Address boydsAddress = boyd.getAddress(); @@ -78,7 +79,7 @@ void findByCollectionNotContainingPOJO() { } @Test - void findByCollectionContainingNull() { + void findByCollectionNotContainingNull() { List strings = new ArrayList<>(); strings.add("ing"); stefan.setStrings(strings); @@ -95,12 +96,61 @@ void findByCollectionContainingNull() { } @Test - void findByCollection_NegativeTest() { + void findByNestedCollectionNotContainingInteger() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsNotContaining(1000); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByCollectionNotContaining_NegativeTest() { assertThatThrownBy(() -> negativeTestsRepository.findByIntsNotContaining()) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Person.ints NOT_CONTAINING: invalid number of arguments, expecting one"); } + @Test + void findByMapNotContainingNullValue() { + Map stringMap = new HashMap<>(); + stringMap.put("key", "str"); + stefan.setStringMap(stringMap); + repository.save(stefan); + + // find Persons with stringMap not containing null value (regardless of key) + assertThat(repository.findByStringMapNotContaining(VALUE, NULL_PARAM)).contains(stefan); + + // Currently getting key-specific results for a Map requires 2 steps: + // firstly query for all entities with existing map key + List personsWithMapKeyExists = repository.findByStringMapContaining(KEY, "key"); + // and then process the results programmatically - leave only the records that have the key's value != null + List personsWithMapValueNotNull = personsWithMapKeyExists.stream() + .filter(person -> person.getStringMap().get("key") != null).toList(); + assertThat(personsWithMapValueNotNull).contains(stefan); + + // Checking that the query results change if there is null value + stringMap.put("key", null); + stefan.setStringMap(stringMap); + repository.save(stefan); + assertThat(repository.findByStringMapNotContaining(VALUE, NULL_PARAM)).doesNotContain(stefan); + + personsWithMapKeyExists = repository.findByStringMapContaining(KEY, "key"); + personsWithMapValueNotNull = personsWithMapKeyExists.stream() + .filter(person -> person.getStringMap().get("key") != null).toList(); + assertThat(personsWithMapValueNotNull).doesNotContain(stefan); + + stefan.setStringMap(null); // cleanup + repository.save(stefan); + } + @Test void findByMapNotContainingKey_String() { assertThat(donny.getStringMap()).containsKey("key1"); @@ -145,35 +195,92 @@ void findByMapNotContainingKeyValuePair_String() { } @Test - void findByMapNotContainingNullValue() { - Map stringMap = new HashMap<>(); - stringMap.put("key", "str"); - stefan.setStringMap(stringMap); - repository.save(stefan); + void findByNestedMapNotContainingKeyValuePair_Integer() { + if (serverVersionSupport.isFindByCDTSupported()) { + assertThat(carter.getIntMap()).containsKey("key1"); + assertThat(carter.getIntMap().get("key1")).isNotEqualTo(1); - // find Persons with stringMap not containing null value (regardless of a key) - assertThat(repository.findByStringMapNotContaining(VALUE, NULL_PARAM)).contains(stefan); + dave.setFriend(carter); + repository.save(dave); - // Currently getting key-specific results for a Map requires 2 steps: - // firstly query for all entities with existing map key - List personsWithMapKeyExists = repository.findByStringMapContaining(KEY, "key"); - // and then process the results programmatically - leave only the records that have the key's value != null - List personsWithMapValueNotNull = personsWithMapKeyExists.stream() - .filter(person -> person.getStringMap().get("key") != null).toList(); - assertThat(personsWithMapValueNotNull).contains(stefan); + List result = repository.findByFriendIntMapNotContaining(KEY_VALUE_PAIR, "key1", 1); - stringMap.put("key", null); - stefan.setStringMap(stringMap); - repository.save(stefan); - assertThat(repository.findByStringMapNotContaining(VALUE, NULL_PARAM)).doesNotContain(stefan); + assertThat(result).contains(dave); + TestUtils.setFriendsToNull(repository, dave); + } + } - personsWithMapKeyExists = repository.findByStringMapContaining(KEY, "key"); - personsWithMapValueNotNull = personsWithMapKeyExists.stream() - .filter(person -> person.getStringMap().get("key") != null).toList(); - assertThat(personsWithMapValueNotNull).doesNotContain(stefan); + @Test + void findByNestedMapKeysNotContainingString() { + if (serverVersionSupport.isFindByCDTSupported()) { + assertThat(donny.getStringMap()).doesNotContainKey("key100"); + assertThat(boyd.getStringMap()).doesNotContainKey("key100"); + + dave.setFriend(donny); + repository.save(dave); + carter.setFriend(boyd); + repository.save(carter); + + List persons = repository.findByFriendStringMapNotContaining(KEY, "key100"); + assertThat(persons).contains(dave, carter); + TestUtils.setFriendsToNull(repository, dave, carter); + } + } - stefan.setStringMap(null); // cleanup - repository.save(stefan); + @Test + void findByNestedMapValuesNotContainingString() { + if (serverVersionSupport.isFindByCDTSupported()) { + assertThat(donny.getStringMap()).doesNotContainValue("val100"); + assertThat(boyd.getStringMap()).doesNotContainValue("val100"); + + dave.setFriend(donny); + repository.save(dave); + carter.setFriend(boyd); + repository.save(carter); + + List persons = repository.findByFriendStringMapNotContaining(VALUE, "val100"); + assertThat(persons).contains(dave, carter); + TestUtils.setFriendsToNull(repository, dave, carter); + } + } + + @Test + void findByNestedMapNotContainingNullValue() { + if (serverVersionSupport.isFindByCDTSupported()) { + Map stringMap = new HashMap<>(); + stringMap.put("key", "str"); + stefan.setStringMap(stringMap); + repository.save(stefan); + + dave.setFriend(stefan); + repository.save(dave); + + // find Persons with stringMap containing null value (regardless of key) + assertThat(repository.findByFriendStringMapNotContaining(VALUE, NULL_PARAM)).contains(dave); + + // Currently getting key-specific results for a Map requires 2 steps: + // firstly query for all entities with existing map key + List personsWithMapKeyExists = repository.findByFriendStringMapContaining(KEY, "key"); + // and then process the results programmatically - leave only the records that have the key's value != null + List personsWithMapValueNotNull = personsWithMapKeyExists.stream() + .filter(person -> person.getFriend().getStringMap().get("key") != null).toList(); + assertThat(personsWithMapValueNotNull).contains(dave); + + // Checking that the query results change if there is null value + stringMap.put("key", null); + stefan.setStringMap(stringMap); + repository.save(stefan); + assertThat(repository.findByFriendStringMapNotContaining(VALUE, NULL_PARAM)).doesNotContain(dave); + + personsWithMapKeyExists = repository.findByFriendStringMapContaining(KEY, "key"); + personsWithMapValueNotNull = personsWithMapKeyExists.stream() + .filter(person -> person.getFriend().getStringMap().get("key") != null).toList(); + assertThat(personsWithMapValueNotNull).doesNotContain(dave); + + TestUtils.setFriendsToNull(repository, dave); // cleanup + stefan.setStringMap(null); + repository.save(stefan); + } } @Test diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotEqualTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotEqualTests.java index f9f00415b..80d3f2fcd 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotEqualTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotEqualTests.java @@ -37,6 +37,10 @@ void findBySimplePropertyNotEqual_String() { @Test void findByNestedSimplePropertyNotEqual() { + String zipCode = "C0123456789"; + assertThat(carter.getAddress().getZipCode()).isNotEqualTo(zipCode); + assertThat(repository.findByAddressZipCodeIsNot(zipCode)).contains(carter); + oliver.setFriend(alicia); repository.save(oliver); dave.setFriend(oliver); @@ -88,6 +92,22 @@ void findByCollection_NegativeTest() { .hasMessage("Person.strings NOTEQ: invalid number of arguments, expecting one"); } + @Test + void findByNestedCollectionNotEqual() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsIsNot(List.of(0, 1, 2, 3, 4, 5, 6, 7)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByMapNotEqual() { if (serverVersionSupport.isFindByCDTSupported()) { @@ -118,6 +138,22 @@ void findByMapNotEqual_NegativeTest() { .hasMessage("Person.stringMap NOTEQ: invalid number of arguments, expecting one"); } + @Test + void findByNestedMapNotEqual() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntMapIsNot(Map.of("0", 1, "2", 3, "4", 5, "6", 7)); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByPOJONotEqual() { if (serverVersionSupport.isFindByCDTSupported()) { @@ -144,6 +180,22 @@ void findByPOJONotEqual_NegativeTest() { .hasMessage("Person.address NOTEQ: Type mismatch, expecting Address"); } + @Test + void findByNestedPojoNotEqual() { + if (serverVersionSupport.isFindByCDTSupported()) { + Address address = new Address("Foo Street 1", 100, "C0123", "Bar"); + assertThat(dave.getAddress()).isNotEqualTo(address); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendAddressIsNot(address); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByNestedPOJONotEqual_NegativeTest() { assertThatThrownBy(() -> negativeTestsRepository.findByFriendAddressIsNot()) diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotInTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotInTests.java index ccb9bff6c..8ec86780e 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotInTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotInTests.java @@ -2,10 +2,13 @@ import org.junit.jupiter.api.Test; import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; +import org.springframework.data.aerospike.sample.Address; import org.springframework.data.aerospike.sample.Person; +import org.springframework.data.aerospike.util.TestUtils; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; @@ -24,4 +27,68 @@ void findBySimplePropertyNotIn_String() { firstNames = List.of("Dave", "Donny", "Carter", "Boyd", "Leroi", "Stefan", "Matias", "Douglas"); assertThat(repository.findByFirstNameNotIn(firstNames)).containsExactlyInAnyOrder(oliver, alicia); } + + @Test + void findByNestedSimplePropertyNotIn_String() { + assertThat(carter.getAddress().getZipCode()).isEqualTo("C0124"); + assertThat(dave.getAddress().getZipCode()).isEqualTo("C0123"); + assertThat(repository.findByAddressZipCodeNotIn(List.of("C0123", "C0125"))).containsOnly(carter); + } + + @Test + void findByNestedCollectionNotIn() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntsNotIn(List.of(List.of(0, 1, 2, 3, 4, 5, 6, 7), + List.of(1, 2, 3), List.of(0, 1, 2, 3, 4, 5))); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedMapNotIn() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendIntMapNotIn(List.of(Map.of("0", 1, "2", 3, "4", 5, "6", 7), + Map.of("1", 2, "3", 4567), Map.of("0", 1, "2", 3, "4", 5))); + + assertThat(result).contains(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedPojoNotIn() { + if (serverVersionSupport.isFindByCDTSupported()) { + Address address1 = new Address("Foo Street 1", 1, "C0123", "Bar"); + Address address2 = new Address("Foo Street 1", 2, "C0124", "C0123"); + Address address3 = new Address("Foo Street 1", 23, "C0125", "Bar"); + Address address4 = new Address("Foo Street 1", 456, "C0126", "Bar"); + assertThat(carter.getAddress()) + .isNotEqualTo(address1) + .isNotEqualTo(address2) + .isNotEqualTo(address3) + .isNotEqualTo(address4); + + dave.setFriend(carter); + repository.save(dave); + + List result = repository.findByFriendAddressNotIn(List.of(address1, address2, address3, address4)); + + assertThat(result).contains(dave); + TestUtils.setFriendsToNull(repository, dave); + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotNullTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotNullTests.java index f0e370812..76b5542f9 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotNullTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NotNullTests.java @@ -4,6 +4,13 @@ import org.junit.jupiter.api.Test; import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.Address; +import org.springframework.data.aerospike.sample.Person; +import org.springframework.data.aerospike.util.TestUtils; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for the "Is not null" repository query. Keywords: NotNull, IsNotNull. @@ -29,6 +36,46 @@ void findByNestedSimpleValueIsNotNull() { repository.save(stefan); } + @Test + void findByNestedCollectionIsNotNull() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + assertThat(carter.getFriend().getInts()).isNotNull(); + assertThat(dave.getFriend()).isNull(); + + List result = repository.findByFriendIntsIsNotNull(); + + assertThat(result) + .contains(carter) + .doesNotContain(dave); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedMapIsNotNull() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + assertThat(carter.getFriend().getIntMap()).isNotNull(); + assertThat(dave.getFriend()).isNull(); + + List result = repository.findByFriendIntMapIsNotNull(); + + assertThat(result) + .contains(carter) + .doesNotContain(dave); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByPOJOIsNotNull() { Assertions.assertThat(stefan.getAddress()).isNull(); @@ -49,4 +96,21 @@ void findByPOJOIsNotNull() { stefan.setAddress(null); // cleanup repository.save(stefan); } + + @Test + void findByNestedPojoIsNotNull() { + if (serverVersionSupport.isFindByCDTSupported()) { + assertThat(dave.getAddress()).isNotNull(); + + carter.setFriend(dave); + repository.save(carter); + + List result = repository.findByFriendAddressIsNotNull(); + + assertThat(result) + .contains(carter) + .doesNotContain(dave); + TestUtils.setFriendsToNull(repository, carter); + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NullTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NullTests.java index 88f89d6fe..58acfae24 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NullTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/noindex/findBy/NullTests.java @@ -1,11 +1,16 @@ package org.springframework.data.aerospike.repository.query.blocking.noindex.findBy; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.data.aerospike.repository.query.blocking.noindex.PersonRepositoryQueryTests; import org.springframework.data.aerospike.sample.Address; +import org.springframework.data.aerospike.sample.Person; import org.springframework.data.aerospike.util.TestUtils; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + /** * Tests for the "Is null" repository query. Keywords: Null, IsNull. */ @@ -13,13 +18,13 @@ public class NullTests extends PersonRepositoryQueryTests { @Test void findByNestedSimplePropertyIsNull() { - Assertions.assertThat(stefan.getAddress()).isNull(); - Assertions.assertThat(carter.getAddress().getZipCode()).isNotNull(); - Assertions.assertThat(dave.getAddress().getZipCode()).isNotNull(); + assertThat(stefan.getAddress()).isNull(); + assertThat(carter.getAddress().getZipCode()).isNotNull(); + assertThat(dave.getAddress().getZipCode()).isNotNull(); stefan.setAddress(new Address(null, null, null, null)); repository.save(stefan); - Assertions.assertThat(repository.findByAddressZipCodeIsNull()) + assertThat(repository.findByAddressZipCodeIsNull()) .contains(stefan) .doesNotContain(carter, dave); @@ -27,27 +32,87 @@ void findByNestedSimplePropertyIsNull() { repository.save(dave); carter.setFriend(dave); repository.save(carter); - Assertions.assertThat(repository.findByFriendBestFriendAddressZipCodeIsNull()).contains(carter); + assertThat(repository.findByFriendBestFriendAddressZipCodeIsNull()).contains(carter); stefan.setAddress(null); // cleanup repository.save(stefan); TestUtils.setFriendsToNull(repository, carter, dave); } + @Test + void findByNestedCollectionIsNull() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setInts(List.of(1, 2, 3, 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + assertThat(carter.getFriend().getInts()).isNotNull(); + assertThat(dave.getFriend()).isNull(); + + List result = repository.findByFriendIntsIsNull(); + + assertThat(result) + .contains(dave) + .doesNotContain(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + + @Test + void findByNestedMapIsNull() { + if (serverVersionSupport.isFindByCDTSupported()) { + dave.setIntMap(Map.of("1", 2, "3", 4)); + repository.save(dave); + + carter.setFriend(dave); + repository.save(carter); + assertThat(carter.getFriend().getIntMap()).isNotNull(); + assertThat(dave.getFriend()).isNull(); + + List result = repository.findByFriendIntMapIsNull(); + + assertThat(result) + .contains(dave) + .doesNotContain(carter); + TestUtils.setFriendsToNull(repository, carter); + } + } + @Test void findByPOJOIsNull() { - Assertions.assertThat(stefan.getAddress()).isNull(); - Assertions.assertThat(carter.getAddress()).isNotNull(); - Assertions.assertThat(dave.getAddress()).isNotNull(); - Assertions.assertThat(repository.findByAddressIsNull()) + assertThat(stefan.getAddress()).isNull(); + assertThat(carter.getAddress()).isNotNull(); + assertThat(dave.getAddress()).isNotNull(); + assertThat(repository.findByAddressIsNull()) .contains(stefan) .doesNotContain(carter, dave); stefan.setAddress(new Address(null, null, null, null)); repository.save(stefan); - Assertions.assertThat(repository.findByAddressIsNull()).doesNotContain(stefan); + assertThat(repository.findByAddressIsNull()).doesNotContain(stefan); stefan.setAddress(null); // cleanup repository.save(stefan); } + + @Test + void findByNestedPojoIsNull() { + if (serverVersionSupport.isFindByCDTSupported()) { + assertThat(dave.getAddress()).isNotNull(); + assertThat(donny.getAddress()).isNull(); + + carter.setFriend(dave); + repository.save(carter); + stefan.setFriend(donny); + repository.save(stefan); + + List result = repository.findByFriendAddressIsNull(); + + assertThat(result) + .contains(stefan) + .doesNotContain(carter); + TestUtils.setFriendsToNull(repository, carter, stefan); + } + } } diff --git a/src/test/java/org/springframework/data/aerospike/sample/PersonRepository.java b/src/test/java/org/springframework/data/aerospike/sample/PersonRepository.java index cf6617b76..9cad7a073 100644 --- a/src/test/java/org/springframework/data/aerospike/sample/PersonRepository.java +++ b/src/test/java/org/springframework/data/aerospike/sample/PersonRepository.java @@ -166,7 +166,7 @@ public interface PersonRepository

extends AerospikeRepository< /** * Find all entities that satisfy the condition "have address in the given range" *

- * Information about ordering + * Information about ordering * * @param from lower limit for the map value, inclusive * @param to upper limit for the map value, exclusive @@ -197,41 +197,20 @@ public interface PersonRepository

extends AerospikeRepository< List

findByAddressExists(); - List

findByAddressZipCodeExists(); - List

findByAddressIsNotNull(); List

findByAddressIsNull(); - List

findByAddressZipCodeIsNull(); - - /** - * Find all entities that satisfy the condition "have a friend who has bestFriend with the address with zipCode - * which is not null" (find by nested POJO field) - */ - List

findByFriendBestFriendAddressZipCodeIsNull(); - - /** - * Find all entities that satisfy the condition "have address with existing zipCode" - */ - List

findByAddressZipCodeIsNotNull(); - /** * Find all entities that satisfy the condition "have Address with fewer elements or with a corresponding key-value * lower in ordering than in the given argument" (find by POJO). *

- * Information about ordering + * Information about ordering * * @param address - Address to compare with */ List

findByAddressLessThan(Address address); - List

findByAddressZipCode(@NotNull String zipCode); - - List

findByAddressZipCodeContaining(String str); - - List

findByAddressZipCodeNotContaining(String str); - List

findByFirstNameContaining(String str); List

findByFirstNameNotContaining(String str); @@ -371,7 +350,7 @@ List

findByAgeOrLastNameLikeAndFirstNameLike(QueryParam age, QueryParam lastN * Find all entities that satisfy the condition "have integers list with more elements or with a corresponding * element higher in ordering than in the given argument" (find by list). *

- * Information about ordering + * Information about ordering * * @param list - List to compare with */ @@ -381,12 +360,22 @@ List

findByAgeOrLastNameLikeAndFirstNameLike(QueryParam age, QueryParam lastN * Find all entities that satisfy the condition "have strings set with more elements or with a corresponding element * higher in ordering than in the given argument" (find by collection). *

- * Information about ordering + * Information about ordering * * @param collection - Collection to compare with */ List

findByIntSetGreaterThanEqual(Collection collection); + /** + * Find all entities that satisfy the condition "have strings set with more elements or with a corresponding element + * higher in ordering than in the given argument" (find by collection). + *

+ * Information about ordering + * + * @param collection - Collection to compare with + */ + List

findByIntsGreaterThanEqual(Collection collection); + /** * Find all entities containing the given map element (key or value depending on the given criterion) * @@ -399,8 +388,8 @@ List

findByAgeOrLastNameLikeAndFirstNameLike(QueryParam age, QueryParam lastN * Find all entities containing the given map element (key or value depending on the given criterion) * * @param criterionPair {@link AerospikeQueryCriterion#KEY_VALUE_PAIR} - * @param key map key - * @param value map value + * @param key map key + * @param value map value */ List

findByStringMapContaining(AerospikeQueryCriterion criterionPair, String key, String value); @@ -429,7 +418,7 @@ List

findByAgeOrLastNameLikeAndFirstNameLike(QueryParam age, QueryParam lastN List

findByStringMapContaining(AerospikeQueryCriterion criterion, AerospikeNullQueryCriterion nullParameter); /** - * Find all entities that satisfy the condition "have the given map key and the value equal to the given string" + * Find all entities that satisfy the condition "does not have the given map key or does not have the given value" * * @param criterionPair {@link AerospikeQueryCriterion#KEY_VALUE_PAIR} * @param key Map key @@ -576,13 +565,502 @@ List

findByAgeOrLastNameLikeAndFirstNameLike(QueryParam age, QueryParam lastN List

findByFriendAgeBetween(int from, int to); /** - * Find all entities that satisfy the condition "have a friend with the address equal to the given argument" (find - * by inner POJO) + * Find all entities that satisfy the condition "have address with zipCode (find by nested simple property)" + */ + + List

findByAddressZipCodeExists(); + + /** + * Find all entities that satisfy the condition "have address with zipCode which is null (i.e. friend's first name + * does not exist)" (find by nested simple property) + */ + List

findByAddressZipCodeIsNull(); + + /** + * Find all entities that satisfy the condition "have a friend who has bestFriend with the address with zipCode + * which is not null" (find by deeply nested simple property) + */ + List

findByFriendBestFriendAddressZipCodeIsNull(); + + /** + * Find all entities that satisfy the condition "have address with zipCode which is not null (i.e. address's zipCode + * exists)" (find by nested simple property) + */ + List

findByAddressZipCodeIsNotNull(); + + /** + * Find all entities that satisfy the condition "have a friend with the ints list equal to the given argument" (find + * by nested Collection) + * + * @param zipCode - String to check for equality + */ + List

findByAddressZipCode(String zipCode); + + /** + * Find all entities that satisfy the condition "have address with zipCode not equal to the given argument" (find by + * nested simple property) + * + * @param zipCode - String to check for equality + */ + List

findByAddressZipCodeIsNot(String zipCode); + + /** + * Find all entities that satisfy the condition "have address with zipCode greater than or equal to the given + * argument" (find by nested simple property) + *

+ * Information about ordering + * + * @param zipCode - String to compare + */ + List

findByAddressZipCodeGreaterThanEqual(String zipCode); + + /** + * Find all entities that satisfy the condition "have address with zipCode greater than the given argument" (find by + * nested simple property) + *

+ * Information about ordering + * + * @param zipCode - String to compare + */ + List

findByAddressZipCodeGreaterThan(String zipCode); + + /** + * Find all entities that satisfy the condition "have address with zipCode less than or equal to the given argument" + * (find by nested simple property) + *

+ * Information about ordering + * + * @param zipCode - String to compare + */ + List

findByAddressZipCodeLessThanEqual(String zipCode); + + /** + * Find all entities that satisfy the condition "have address with zipCode less than the given argument" (find by + * nested simple property) + *

+ * Information about ordering + * + * @param zipCode - String to compare + */ + List

findByAddressZipCodeLessThan(String zipCode); + + /** + * Find all entities that satisfy the condition "have address with zipCode between the given arguments" (find by + * nested simple property) + *

+ * Information about ordering + * + * @param lowerLimit - lower limit, inclusive + * @param upperLimit - upper limit, exclusive + */ + List

findByAddressZipCodeBetween(String lowerLimit, String upperLimit); + + /** + * Find all entities that satisfy the condition "have address with zipCode equal to one of the values in the given + * list" (find by nested simple property) + * + * @param list - list of possible values + */ + List

findByAddressZipCodeIn(List list); + + /** + * Find all entities that satisfy the condition "have address with zipCode equal to neither of the values in the + * given list" (find by nested simple property) + * + * @param list - list of possible values + */ + List

findByAddressZipCodeNotIn(List list); + + /** + * Find all entities that satisfy the condition "have address with zipCode that contains the given substring" (find + * by nested simple property) + * + * @param string substring to check + */ + List

findByAddressZipCodeContaining(String string); + + /** + * Find all entities that satisfy the condition "have address with zipCode that does not contain the given string" + * (find by nested simple property) + * + * @param string substring to check + */ + List

findByAddressZipCodeNotContaining(String string); + + /** + * Find all entities that satisfy the condition "have a friend with ints list (find by nested Collection)" + */ + List

findByFriendIntsExists(); + + /** + * Find all entities that satisfy the condition "have a friend with ints list which is null (i.e. friend's ints list + * does not exist)" (find by nested Collection) + */ + List

findByFriendIntsIsNull(); + + /** + * Find all entities that satisfy the condition "have a friend with ints list which is not null (i.e. friend's ints + * list exists)" (find by nested Collection) + */ + List

findByFriendIntsIsNotNull(); + + /** + * Find all entities that satisfy the condition "have a friend with ints list equal to the given argument" (find by + * nested Collection) + * + * @param ints - List of integers to check for equality + */ + List

findByFriendInts(List ints); + + /** + * Find all entities that satisfy the condition "have a friend with ints list not equal to the given argument" (find + * by nested Collection) + * + * @param ints - List of integers to check for equality + */ + List

findByFriendIntsIsNot(List ints); + + /** + * Find all entities that satisfy the condition "have a friend with ints list greater than or equal to the given + * argument" (find by nested Collection) + *

+ * Information about ordering + * + * @param ints - List of integers to compare + */ + List

findByFriendIntsGreaterThanEqual(List ints); + + /** + * Find all entities that satisfy the condition "have a friend with ints list greater than the given argument" (find + * by nested Collection) + *

+ * Information about ordering + * + * @param ints - List of integers to compare + */ + List

findByFriendIntsGreaterThan(List ints); + + /** + * Find all entities that satisfy the condition "have a friend with ints list less than or equal to the given + * argument" (find by nested Collection) + *

+ * Information about ordering + * + * @param ints - List of integers to compare + */ + List

findByFriendIntsLessThanEqual(List ints); + + /** + * Find all entities that satisfy the condition "have a friend with ints list less than the given argument" (find by + * nested Collection) + *

+ * Information about ordering + * + * @param ints - List of integers to compare + */ + List

findByFriendIntsLessThan(List ints); + + /** + * Find all entities that satisfy the condition "have a friend with ints list between the given arguments" (find by + * nested Collection) + *

+ * Information about ordering + * + * @param lowerLimit - lower limit, inclusive + * @param upperLimit - upper limit, exclusive + */ + List

findByFriendIntsBetween(List lowerLimit, List upperLimit); + + /** + * Find all entities that satisfy the condition "have a friend with ints list equal to one of the values in the + * given list" (find by nested Collection) + * + * @param list - list of possible values + */ + List

findByFriendIntsIn(List> list); + + /** + * Find all entities that satisfy the condition "have a friend with ints list equal to neither of the values in the + * given list" (find by nested Collection) + * + * @param list - list of possible values + */ + List

findByFriendIntsNotIn(List> list); + + /** + * Find all entities that satisfy the condition "have a friend with ints list that contains the given integer" (find + * by nested Collection) + * + * @param integer number to check + */ + List

findByFriendIntsContaining(int integer); + + /** + * Find all entities that satisfy the condition "have a friend with ints list that does not contain the given + * integer" (find by nested Collection) + * + * @param integer number to check + */ + List

findByFriendIntsNotContaining(int integer); + + /** + * Find all entities that satisfy the condition "have a friend with intMap (find by nested Map)" + */ + List

findByFriendIntMapExists(); + + /** + * Find all entities that satisfy the condition "have a friend with intMap which is null (i.e. friend's intMap does + * not exist)" (find by nested Map) + */ + List

findByFriendIntMapIsNull(); + + /** + * Find all entities that satisfy the condition "have a friend with intMap which is not null (i.e. friend's intMap + * exists)" (find by nested Map) + */ + List

findByFriendIntMapIsNotNull(); + + /** + * Find all entities that satisfy the condition "have a friend with intMap equal to the given argument" (find by + * nested Map) + * + * @param intMap - Map to check for equality + */ + List

findByFriendIntMap(Map intMap); + + /** + * Find all entities that satisfy the condition "have a friend with intMap not equal to the given argument" (find by + * nested Map) + * + * @param intMap - Map to check for equality + */ + List

findByFriendIntMapIsNot(Map intMap); + + /** + * Find all entities that satisfy the condition "have a friend with intMap greater than or equal to the given + * argument" (find by nested Map) + *

+ * Information about ordering + * + * @param intMap - Map to compare + */ + List

findByFriendIntMapGreaterThanEqual(Map intMap); + + /** + * Find all entities that satisfy the condition "have a friend with intMap greater than the given argument" (find by + * nested Map) + *

+ * Information about ordering + * + * @param intMap - Map to compare + */ + List

findByFriendIntMapGreaterThan(Map intMap); + + /** + * Find all entities that satisfy the condition "have a friend with intMap less than or equal to the given argument" + * (find by nested Map) + *

+ * Information about ordering + * + * @param intMap - Map to compare + */ + List

findByFriendIntMapLessThanEqual(Map intMap); + + /** + * Find all entities that satisfy the condition "have a friend with intMap less than the given argument" (find by + * nested Map) + *

+ * Information about ordering + * + * @param intMap - Map to compare + */ + List

findByFriendIntMapLessThan(Map intMap); + + /** + * Find all entities that satisfy the condition "have a friend with intMap between the given arguments" (find by + * nested Map) + *

+ * Information about ordering + * + * @param lowerLimit - lower limit, inclusive + * @param upperLimit - upper limit, exclusive + */ + List

findByFriendIntMapBetween(Map lowerLimit, Map upperLimit); + + /** + * Find all entities that satisfy the condition "have a friend with intMap equal to one of the values in the given + * list" (find by nested Map) + * + * @param list - list of possible values + */ + List

findByFriendIntMapIn(List> list); + + /** + * Find all entities that satisfy the condition "have a friend with intMap equal to neither of the values in the + * given list" (find by nested Map) + * + * @param list - list of possible values + */ + List

findByFriendIntMapNotIn(List> list); + + /** + * Find all entities that satisfy the condition "have a friend with intMap that contains the given integer" (find by + * nested Map) + * + * @param criterionPair {@link AerospikeQueryCriterion#KEY_VALUE_PAIR} + * @param key Map key + * @param value Integer to check whether map value equals it + */ + List

findByFriendIntMapContaining(AerospikeQueryCriterion criterionPair, String key, @NotNull Integer value); + + /** + * Find all entities that satisfy the condition "have a friend with intMap that contains the given value - key or + * value depending on the criterion parameter" (find by nested Map) + * + * @param criterion {@link AerospikeQueryCriterion#KEY or AerospikeQueryCriterion#VALUE} + * @param element Map key or value + */ + List

findByFriendStringMapContaining(AerospikeQueryCriterion criterion, String element); + + /** + * Find all entities that satisfy the condition "have a friend with intMap that does not contain the given value - + * key or value depending on the criterion parameter" (find by nested Map) + * + * @param criterion {@link AerospikeQueryCriterion#KEY or AerospikeQueryCriterion#VALUE} + * @param element Map key or value + */ + List

findByFriendStringMapNotContaining(AerospikeQueryCriterion criterion, String element); + + /** + * Find all entities that satisfy the condition "have a friend with intMap that contains null value" (find by nested + * Map) + * + * @param criterion {@link AerospikeQueryCriterion#VALUE} + * @param nullParameter {@link AerospikeNullQueryCriterion#NULL_PARAM} + */ + List

findByFriendStringMapContaining(AerospikeQueryCriterion criterion, + AerospikeNullQueryCriterion nullParameter); + + /** + * Find all entities that satisfy the condition "have a friend with intMap that does not contain null value" (find + * by nested Map) + * + * @param criterion {@link AerospikeQueryCriterion#VALUE} + * @param nullParameter {@link AerospikeNullQueryCriterion#NULL_PARAM} + */ + List

findByFriendStringMapNotContaining(AerospikeQueryCriterion criterion, + AerospikeNullQueryCriterion nullParameter); + + /** + * Find all entities that satisfy the condition "does not have the given map key or does not have the given value" + * (find by nested Map) + * + * @param criterionPair {@link AerospikeQueryCriterion#KEY_VALUE_PAIR} + * @param key Map key + * @param value Integer value to check + */ + List

findByFriendIntMapNotContaining(AerospikeQueryCriterion criterionPair, String key, @NotNull Integer value); + + /** + * Find all entities that satisfy the condition "have a friend with address (find by nested Map)" + */ + List

findByFriendAddressExists(); + + /** + * Find all entities that satisfy the condition "have a friend with address which is null (i.e. friend's address + * does not exist)" (find by nested Map) + */ + List

findByFriendAddressIsNull(); + + /** + * Find all entities that satisfy the condition "have a friend with address which is not null (i.e. friend's address + * exists)" (find by nested Map) + */ + List

findByFriendAddressIsNotNull(); + + /** + * Find all entities that satisfy the condition "have a friend with address equal to the given argument" (find by + * nested POJO) * * @param address - Address to check for equality */ List

findByFriendAddress(Address address); + /** + * Find all entities that satisfy the condition "have a friend with address not equal to the given argument" (find + * by nested Map) + * + * @param address - Address to check for equality + */ + List

findByFriendAddressIsNot(Address address); + + /** + * Find all entities that satisfy the condition "have a friend with address greater than or equal to the given + * argument" (find by nested Map) + *

+ * Information about ordering + * + * @param address - Address to compare + */ + List

findByFriendAddressGreaterThanEqual(Address address); + + /** + * Find all entities that satisfy the condition "have a friend with address greater than the given argument" (find + * by nested Map) + *

+ * Information about ordering + * + * @param address - Address to compare + */ + List

findByFriendAddressGreaterThan(Address address); + + /** + * Find all entities that satisfy the condition "have a friend with address less than or equal to the given + * argument" (find by nested Map) + *

+ * Information about ordering + * + * @param address - Address to compare + */ + List

findByFriendAddressLessThanEqual(Address address); + + /** + * Find all entities that satisfy the condition "have a friend with address less than the given argument" (find by + * nested Map) + *

+ * Information about ordering + * + * @param address - Address to compare + */ + List

findByFriendAddressLessThan(Address address); + + /** + * Find all entities that satisfy the condition "have a friend with address between the given arguments" (find by + * nested Map) + *

+ * Information about ordering + * + * @param lowerLimit - lower limit, inclusive + * @param upperLimit - upper limit, exclusive + */ + List

findByFriendAddressBetween(Address lowerLimit, Address upperLimit); + + /** + * Find all entities that satisfy the condition "have a friend with address equal to one of the values in the given + * list" (find by nested Map) + * + * @param list - list of possible values + */ + List

findByFriendAddressIn(List

list); + + /** + * Find all entities that satisfy the condition "have a friend with address equal to neither of the values in the + * given list" (find by nested Map) + * + * @param list - list of possible values + */ + List

findByFriendAddressNotIn(List

list); + /** * Find all entities that satisfy the condition "have a friend with the address with zipCode equal to the given * argument" (find by nested POJO field) @@ -679,8 +1157,6 @@ List

findByAgeOrLastNameLikeAndFirstNameLike(QueryParam age, QueryParam lastN */ List

findByStringsNotContaining(AerospikeNullQueryCriterion nullParameter); - List

findByStringsNotContaining(); - /** * Find all entities that satisfy the condition "have the list which contains the given integer" *

@@ -691,29 +1167,6 @@ List

findByAgeOrLastNameLikeAndFirstNameLike(QueryParam age, QueryParam lastN */ List

findByIntsContaining(int integer); - /** - * Find all entities that satisfy the condition "have the list which contains the given integers" - *

- * List name in this case is Ints - *

- * - * @param integer1 number to check - * @param integer2 number to check - */ - List

findByIntsContaining(int integer1, int integer2); - - /** - * Find all entities that satisfy the condition "have the list which contains the given integers" - *

- * List name in this case is Ints - *

- * - * @param integer1 number to check - * @param integer2 number to check - * @param integer3 number to check - */ - List

findByIntsContaining(int integer1, int integer2, int integer3); - /** * Find all entities that satisfy the condition "have the array which contains the given integer" *

@@ -724,17 +1177,6 @@ List

findByAgeOrLastNameLikeAndFirstNameLike(QueryParam age, QueryParam lastN */ List

findByIntArrayContaining(int integer); - /** - * Find all entities that satisfy the condition "have the array which contains the given integers" - *

- * Array name in this case is IntArray - *

- * - * @param integer1 number to check - * @param integer2 number to check - */ - List

findByIntArrayContaining(int integer1, int integer2); - /** * Find all entities that satisfy the condition "have the list which contains the given boolean" *