Skip to content

Commit

Permalink
2 steps to look for specific key's value equal or not equal to null i…
Browse files Browse the repository at this point in the history
…n a Map, fixes, cleanup
  • Loading branch information
agrgr committed Aug 1, 2023
1 parent 4eda2bb commit f76cd15
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -483,11 +483,11 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
MAP_VAL_NOTEQ_BY_KEY {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
if (getValue1(qualifierMap) instanceof Value.NullValue || getValue1(qualifierMap).getObject() != null) {
return findExistingByMapKey(qualifierMap);
} else {
// if (getValue1(qualifierMap) instanceof Value.NullValue || getValue1(qualifierMap).getObject() != null) {
// return findExistingByMapKey(qualifierMap);
// } else {
return getFilterExpMapValNotEqOrFail(qualifierMap, Exp::ne);
}
// }
}

@Override
Expand Down Expand Up @@ -781,7 +781,7 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
MAP_VAL_EXISTS_BY_KEY {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
return FilterOperation.findExistingByMapKey(qualifierMap);
return mapKeysContain(qualifierMap);
}

@Override
Expand All @@ -792,7 +792,7 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
MAP_VAL_NOT_EXISTS_BY_KEY {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
return findNotExistingByMapKey(qualifierMap);
return mapKeysNotContain(qualifierMap);
}

@Override
Expand All @@ -803,8 +803,17 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
MAP_VAL_IS_NOT_NULL_BY_KEY {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
return findExistingByMapKey(qualifierMap);
}
String[] dotPathArray = getDotPathArray(getDotPath(qualifierMap),
"MAP_VAL_IS_NULL_BY_KEY: dotPath was not set");
if (dotPathArray.length > 1) {
// in case it is a field of an object set to null the key does not get added to a Map,
// so it is enough to look for Maps with the given key
return mapKeysContain(qualifierMap);
} else {
// currently querying for a specific Map key with not null value is not supported,
// it is recommended to use querying for an existing key and then filtering key:!=null
return getMapValEqOrFail(qualifierMap, Exp::eq, "MAP_VAL_IS_NULL_BY_KEY");
} }

@Override
public Filter sIndexFilter(Map<String, Object> qualifierMap) {
Expand All @@ -814,7 +823,17 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
MAP_VAL_IS_NULL_BY_KEY {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
return getMapValEqOrFail(qualifierMap, Exp::eq, "MAP_VAL_IS_NULL_BY_KEY");
String[] dotPathArray = getDotPathArray(getDotPath(qualifierMap),
"MAP_VAL_IS_NULL_BY_KEY: dotPath was not set");
if (dotPathArray.length > 1) {
// in case it is a field of an object set to null the key does not get added to a Map,
// so it is enough to look for Maps without the given key
return mapKeysNotContain(qualifierMap);
} else {
// currently querying for a specific Map key with explicit null value is not supported,
// it is recommended to use querying for an existing key and then filtering key:null
return getMapValEqOrFail(qualifierMap, Exp::eq, "MAP_VAL_IS_NULL_BY_KEY");
}
}

@Override
Expand All @@ -825,21 +844,7 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
MAP_KEYS_CONTAIN {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {

Exp value = switch (getValue1(qualifierMap).getType()) {
case INTEGER -> Exp.val(getValue1(qualifierMap).toLong());
case STRING -> Exp.val(getValue1(qualifierMap).toString());
case JBLOB -> getConvertedValue1Exp(qualifierMap);
case LIST -> Exp.val((List<?>) getValue1(qualifierMap).getObject());
case MAP -> Exp.val(getConvertedMap(qualifierMap, FilterOperation::getValue1));
default -> throw new UnsupportedOperationException(
"MAP_KEYS_CONTAIN FilterExpression unsupported type: got " +
getValue1(qualifierMap).getClass().getSimpleName());
};

return Exp.gt(
MapExp.getByKey(MapReturnType.COUNT, Exp.Type.INT, value, Exp.mapBin(getField(qualifierMap))),
Exp.val(0));
return mapKeysContain(qualifierMap);
}

@Override
Expand All @@ -850,23 +855,7 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
MAP_KEYS_NOT_CONTAIN {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {

Exp value = switch (getValue1(qualifierMap).getType()) {
case INTEGER -> Exp.val(getValue1(qualifierMap).toLong());
case STRING -> Exp.val(getValue1(qualifierMap).toString());
case JBLOB -> getConvertedValue1Exp(qualifierMap);
case LIST -> Exp.val((List<?>) getValue1(qualifierMap).getObject());
case MAP -> Exp.val(getConvertedMap(qualifierMap, FilterOperation::getValue1));
default -> throw new UnsupportedOperationException(
"MAP_KEYS_CONTAIN FilterExpression unsupported type: got " +
getValue1(qualifierMap).getClass().getSimpleName());
};

Exp mapIsNull = Exp.not(Exp.binExists(getField(qualifierMap)));
Exp mapKeysNotContaining = Exp.eq(
MapExp.getByKey(MapReturnType.COUNT, Exp.Type.INT, value, Exp.mapBin(getField(qualifierMap))),
Exp.val(0));
return Exp.or(mapIsNull, mapKeysNotContaining);
return mapKeysNotContain(qualifierMap);
}

@Override
Expand All @@ -877,21 +866,7 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
MAP_VALUES_CONTAIN {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
Exp value = switch (getValue1(qualifierMap).getType()) {
case INTEGER -> Exp.val(getValue1(qualifierMap).toLong());
case STRING -> Exp.val(getValue1(qualifierMap).toString());
case JBLOB -> getConvertedValue1Exp(qualifierMap);
case LIST -> Exp.val((List<?>) getValue1(qualifierMap).getObject());
case MAP -> Exp.val(getConvertedMap(qualifierMap, FilterOperation::getValue1));
case NULL -> Exp.nil();
default -> throw new UnsupportedOperationException(
"MAP_VALUES_CONTAIN FilterExpression unsupported type: got " +
getValue1(qualifierMap).getClass().getSimpleName());
};

return Exp.gt(
MapExp.getByValue(MapReturnType.COUNT, value, Exp.mapBin(getField(qualifierMap))),
Exp.val(0));
return mapValuesContain(qualifierMap);
}

@Override
Expand All @@ -902,23 +877,7 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
MAP_VALUES_NOT_CONTAIN {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
Exp value = switch (getValue1(qualifierMap).getType()) {
case INTEGER -> Exp.val(getValue1(qualifierMap).toLong());
case STRING -> Exp.val(getValue1(qualifierMap).toString());
case JBLOB -> getConvertedValue1Exp(qualifierMap);
case LIST -> Exp.val((List<?>) getValue1(qualifierMap).getObject());
case MAP -> Exp.val(getConvertedMap(qualifierMap, FilterOperation::getValue1));
case NULL -> Exp.nil();
default -> throw new UnsupportedOperationException(
"MAP_VALUES_CONTAIN FilterExpression unsupported type: got " +
getValue1(qualifierMap).getClass().getSimpleName());
};

Exp mapIsNull = Exp.not(Exp.binExists(getField(qualifierMap)));
Exp mapValuesNotContaining = Exp.eq(
MapExp.getByValue(MapReturnType.COUNT, value, Exp.mapBin(getField(qualifierMap))),
Exp.val(0));
return Exp.or(mapIsNull, mapValuesNotContaining);
return mapValuesNotContain(qualifierMap);
}

@Override
Expand Down Expand Up @@ -1298,19 +1257,54 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
}
};

private static Exp findExistingByMapKey(Map<String, Object> qualifierMap) {
String key = getValue2(qualifierMap).toString();
return Exp.gt(
MapExp.getByKey(MapReturnType.COUNT, Exp.Type.INT, Exp.val(key),
Exp.mapBin(getField(qualifierMap))),
private static Exp mapKeysNotContain(Map<String, Object> qualifierMap) {
String errMsg = "MAP_KEYS_NOT_CONTAIN FilterExpression unsupported type: got " +
getValue1(qualifierMap).getClass().getSimpleName();
return mapKeysCount(qualifierMap, Exp::eq, errMsg);
}

private static Exp mapKeysContain(Map<String, Object> qualifierMap) {
String errMsg = "MAP_KEYS_CONTAIN FilterExpression unsupported type: got " +
getValue1(qualifierMap).getClass().getSimpleName();
return mapKeysCount(qualifierMap, Exp::gt, errMsg);
}

private static Exp mapKeysCount(Map<String, Object> qualifierMap, BinaryOperator<Exp> operator, String errMsg) {
Exp value = getValue1Exp(qualifierMap, errMsg);
return operator.apply(
MapExp.getByKey(MapReturnType.COUNT, Exp.Type.INT, value, Exp.mapBin(getField(qualifierMap))),
Exp.val(0));

}

private static Exp findNotExistingByMapKey(Map<String, Object> qualifierMap) {
String key = getValue2(qualifierMap).toString();
return Exp.eq(
MapExp.getByKey(MapReturnType.COUNT, Exp.Type.INT, Exp.val(key),
Exp.mapBin(getField(qualifierMap))),
private static Exp mapValuesNotContain(Map<String, Object> qualifierMap) {
String errMsg = "MAP_VALUES_NOT_CONTAIN FilterExpression unsupported type: got " +
getValue1(qualifierMap).getClass().getSimpleName();
return mapValuesCount(qualifierMap, Exp::eq, errMsg);
}

private static Exp mapValuesContain(Map<String, Object> qualifierMap) {
String errMsg = "MAP_VALUES_CONTAIN FilterExpression unsupported type: got " +
getValue1(qualifierMap).getClass().getSimpleName();
return mapValuesCount(qualifierMap, Exp::gt, errMsg);
}

private static Exp getValue1Exp(Map<String, Object> qualifierMap, String errMsg) {
return switch (getValue1(qualifierMap).getType()) {
case INTEGER -> Exp.val(getValue1(qualifierMap).toLong());
case STRING -> Exp.val(getValue1(qualifierMap).toString());
case JBLOB -> getConvertedValue1Exp(qualifierMap);
case LIST -> Exp.val((List<?>) getValue1(qualifierMap).getObject());
case MAP -> Exp.val(getConvertedMap(qualifierMap, FilterOperation::getValue1));
case NULL -> Exp.nil();
default -> throw new UnsupportedOperationException(errMsg);
};
}

private static Exp mapValuesCount(Map<String, Object> qualifierMap, BinaryOperator<Exp> operator, String errMsg) {
Exp value = getValue1Exp(qualifierMap, errMsg);
return operator.apply(
MapExp.getByValue(MapReturnType.COUNT, value, Exp.mapBin(getField(qualifierMap))),
Exp.val(0));
}

Expand Down Expand Up @@ -1450,8 +1444,6 @@ yield getMapValEqExp(qualifierMap, expType, convertedValue, dotPathArr, operator
useCtx);
case MAP -> getMapValEqExp(qualifierMap, Exp.Type.MAP, Exp.val(getConvertedMap(qualifierMap, value1)),
dotPathArr, operator, useCtx);
case ParticleType.NULL -> getMapValEqExp(qualifierMap, Exp.Type.NIL, value1, dotPathArr, operator,
useCtx);
default -> throw new UnsupportedOperationException(
opName + " FilterExpression unsupported type: " + value1.getClass().getSimpleName());
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import java.util.Optional;
import java.util.stream.Collectors;

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.LIST_VAL_CONTAINING;
import static org.springframework.data.aerospike.query.FilterOperation.MAP_KEYS_CONTAIN;
import static org.springframework.data.aerospike.query.FilterOperation.MAP_KEYS_NOT_CONTAIN;
Expand Down Expand Up @@ -124,8 +126,9 @@ private AerospikeCriteria create(Part part, AerospikePersistentProperty property
case NOT_IN -> getCriteria(part, property, v1, null, parameters, FilterOperation.NOT_IN);
case TRUE -> getCriteria(part, property, true, null, parameters, FilterOperation.EQ);
case FALSE -> getCriteria(part, property, false, null, parameters, FilterOperation.EQ);
case EXISTS, IS_NOT_NULL -> getCriteria(part, property, null, null, parameters, FilterOperation.IS_NOT_NULL);
case IS_NULL -> getCriteria(part, property, null, null, parameters, FilterOperation.IS_NULL);
case EXISTS, IS_NOT_NULL ->
getCriteria(part, property, null, null, parameters, FilterOperation.IS_NOT_NULL);
case IS_NULL -> getCriteria(part, property, null, null, parameters, IS_NULL);
default -> throw new IllegalArgumentException("Unsupported keyword '" + part.getType() + "'");
};
}
Expand Down Expand Up @@ -240,6 +243,8 @@ public AerospikeCriteria getCriteria(Part part, AerospikePersistentProperty prop
if (part.getProperty().hasNext()) { // if it is a POJO field (a simple field or an inner POJO)
if (op == FilterOperation.BETWEEN) {
value3 = Value.get(value2); // contains upper limit
} else if (op == IS_NOT_NULL || op == IS_NULL) {
value1 = Value.get(property.getFieldName()); // contains key (field name)
}
op = getCorrespondingMapValueFilterOperationOrFail(op);
value2 = Value.get(property.getFieldName()); // VALUE2 contains key (field name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,13 +597,11 @@ void findByIsNull() {
repository.save(stefan);
assertThat(repository.findByAddressIsNull()).doesNotContain(stefan);
assertThat(repository.findByAddressZipCodeIsNull()).contains(stefan).doesNotContain(carter, dave);
assertThat(repository.findByAddressZipCode(null)).contains(stefan).doesNotContain(carter, dave);

dave.setBestFriend(stefan);
repository.save(dave);
carter.setFriend(dave);
repository.save(carter);
assertThat(repository.findByFriendBestFriendAddressZipCode(null)).contains(carter);
assertThat(repository.findByFriendBestFriendAddressZipCodeIsNull()).contains(carter);

stefan.setAddress(null); // cleanup
Expand All @@ -614,8 +612,15 @@ void findByIsNull() {
stringMap.put("key", null);
stefan.setStringMap(stringMap);
repository.save(stefan);
assertThat(repository.findByStringMapContaining("key", (String) null)).contains(stefan); // key-specific
assertThat(repository.findByStringMapContaining(null, VALUE)).contains(stefan); // among all map values
assertThat(repository.findByStringMapContaining(null, VALUE)).contains(stefan); // among map values

// Currently getting key-specific results for a Map requires 2 steps:
// firstly query for all entities with existing map key
List<Person> personsWithMapKeyExists = repository.findByStringMapContaining("key", KEY);
// and then leave only the records that have the key's value == null
List<Person> personsWithMapValueNull = personsWithMapKeyExists.stream()
.filter(person -> person.getStringMap().get("key") == null).toList();
assertThat(personsWithMapValueNull).contains(stefan);

List<String> strings = new ArrayList<>();
strings.add(null);
Expand All @@ -639,13 +644,11 @@ void findByIsNotNull() {
repository.save(stefan);
assertThat(repository.findByAddressIsNotNull()).contains(stefan); // Address is not null
assertThat(repository.findByAddressZipCodeIsNotNull()).contains(stefan); // zipCode is not null
assertThat(repository.findByAddressZipCodeIsNot(null)).contains(stefan);

stefan.setAddress(new Address(null, null, null, null));
repository.save(stefan);
assertThat(repository.findByAddressIsNotNull()).contains(stefan); // Address is not null
assertThat(repository.findByAddressZipCodeIsNotNull()).doesNotContain(stefan); // zipCode is null
assertThat(repository.findByAddressZipCodeIsNot(null)).doesNotContain(stefan);

stefan.setAddress(null); // cleanup
repository.save(stefan);
Expand All @@ -654,13 +657,12 @@ void findByIsNotNull() {
stringMap.put("key", "str");
stefan.setStringMap(stringMap);
repository.save(stefan);
assertThat(repository.findByStringMapNotContaining(null, VALUE)).contains(stefan); // looks at all values
assertThat(repository.findByStringMapNotContaining(null, VALUE)).contains(stefan); // among map values

// Currently only for a Map "IsNotNull" differs from "Exists" as map values can both exist and store null
// Getting key-specific results requires post-processing:
// firstly getting all entities with existing map key
// Currently getting key-specific results for a Map requires 2 steps:
// firstly query for all entities with existing map key
List<Person> personsWithMapKeyExists = repository.findByStringMapContaining("key", KEY);
// and then filtering those that have the key's value equal to null
// and then leave only the records that have the key's value != null
List<Person> personsWithMapValueNotNull = personsWithMapKeyExists.stream()
.filter(person -> person.getStringMap().get("key") != null).toList();
assertThat(personsWithMapValueNotNull).contains(stefan);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,7 @@ public interface PersonRepository<P extends Person> extends AerospikeRepository<
*/
List<P> findByAddressLessThan(Address address);

List<P> findByAddressZipCode(String zipCode);

/**
* Find all entities that satisfy the condition "have address with zipCode which is not equal to the given
* argument"
*/
List<P> findByAddressZipCodeIsNot(String zipCode);
List<P> findByAddressZipCode(@NotNull String zipCode);

List<P> findByAddressZipCodeContaining(String str);

Expand Down Expand Up @@ -497,7 +491,7 @@ public interface PersonRepository<P extends Person> extends AerospikeRepository<
* @param key Map key
* @param value String to check if map value equals it
*/
List<P> findByStringMapContaining(String key, String value);
List<P> findByStringMapContaining(String key, @NotNull String value);

/**
* Find all entities that satisfy the condition "have the given map key3 and the value3 equal to the given strings"
Expand Down Expand Up @@ -693,7 +687,7 @@ List<P> findByStringMapContaining(String key1, String value1, String key2, Strin
*
* @param zipCode - Zip code to check for equality
*/
List<P> findByFriendBestFriendAddressZipCode(String zipCode);
List<P> findByFriendBestFriendAddressZipCode(@NotNull String zipCode);

/**
* Find all entities that satisfy the condition "have a friend who has bestFriend with the address with apartment
Expand Down

0 comments on commit f76cd15

Please sign in to comment.