Skip to content

Commit

Permalink
support for findBy...IsNull/isNotNull/Exists, cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
agrgr committed Jul 30, 2023
1 parent 569b064 commit 66235fe
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
if (getValue1(qualifierMap) instanceof Value.NullValue || getValue1(qualifierMap).getObject() != null) {
return findNotNullByMapKey(qualifierMap);
return findExistingByMapKey(qualifierMap);
} else {
return getFilterExpMapValNotEqOrFail(qualifierMap, Exp::ne);
}
Expand Down Expand Up @@ -778,10 +778,32 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
return null; // String secondary index does not support "contains" queries
}
},
MAP_VAL_NOT_NULL_BY_KEY {
MAP_VAL_EXISTS_BY_KEY {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
return findNotNullByMapKey(qualifierMap);
return FilterOperation.findExistingByMapKey(qualifierMap);
}

@Override
public Filter sIndexFilter(Map<String, Object> qualifierMap) {
return null;
}
},
MAP_VAL_NOT_EXISTS_BY_KEY {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
return FilterOperation.findNotExistingByMapKey(qualifierMap);
}

@Override
public Filter sIndexFilter(Map<String, Object> qualifierMap) {
return null;
}
},
MAP_VAL_IS_NOT_NULL_BY_KEY {
@Override
public Exp filterExp(Map<String, Object> qualifierMap) {
return findExistingByMapKey(qualifierMap);
}

@Override
Expand Down Expand Up @@ -1276,14 +1298,22 @@ public Filter sIndexFilter(Map<String, Object> qualifierMap) {
}
};

private static Exp findNotNullByMapKey(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))),
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))),
Exp.val(0));
}

private static Exp getConvertedValue1Exp(Map<String, Object> qualifierMap) {
Object convertedValue = getConvertedValue(qualifierMap, FilterOperation::getValue1);
Exp exp;
Expand Down Expand Up @@ -1418,10 +1448,8 @@ yield getMapValEqExp(qualifierMap, expType, convertedValue, dotPathArr, operator
}
case LIST -> getMapValEqExp(qualifierMap, Exp.Type.LIST, value1.getObject(), dotPathArr, operator,
useCtx);
case MAP ->
getMapValEqExp(qualifierMap, Exp.Type.MAP, Exp.val(getConvertedMap(qualifierMap, value1)), 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 IllegalArgumentException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
import static org.springframework.data.aerospike.query.FilterOperation.MAP_VALUES_NOT_CONTAIN;
import static org.springframework.data.aerospike.query.FilterOperation.MAP_VAL_CONTAINING_BY_KEY;
import static org.springframework.data.aerospike.query.FilterOperation.MAP_VAL_EQ_BY_KEY;
import static org.springframework.data.aerospike.query.FilterOperation.MAP_VAL_EXISTS_BY_KEY;
import static org.springframework.data.aerospike.query.FilterOperation.MAP_VAL_NOT_EXISTS_BY_KEY;

/**
* @author Peter Milne
Expand Down Expand Up @@ -156,12 +158,16 @@ public AerospikeCriteria getCriteria(Part part, AerospikePersistentProperty prop
parameters.forEachRemaining(params::add);

if (params.size() == 1) { // more than 1 parameter (values) provided, the first is stored in value1
Object nextParam = params.get(0);
Object nextParam = params.get(0); // nextParam is de facto the second
if (op == FilterOperation.CONTAINING) {
if (nextParam instanceof AerospikeMapCriteria onMap) {
switch (onMap) {
case KEY -> op = MAP_KEYS_CONTAIN;
case VALUE -> op = MAP_VALUES_CONTAIN;
case VALUE_EXISTS -> {
op = MAP_VAL_EXISTS_BY_KEY;
value2 = value1;
}
}
} else {
op = FilterOperation.MAP_VAL_EQ_BY_KEY;
Expand All @@ -173,6 +179,10 @@ public AerospikeCriteria getCriteria(Part part, AerospikePersistentProperty prop
switch (onMap) {
case KEY -> op = MAP_KEYS_NOT_CONTAIN;
case VALUE -> op = MAP_VALUES_NOT_CONTAIN;
case VALUE_EXISTS -> {
op = MAP_VAL_NOT_EXISTS_BY_KEY;
value2 = value1;
}
}
} else {
op = FilterOperation.MAP_VAL_NOTEQ_BY_KEY;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ public interface CriteriaDefinition {
String getKey();

enum AerospikeMapCriteria {
KEY,
VALUE,
VALUE_CONTAINING
KEY, VALUE, VALUE_CONTAINING, VALUE_EXISTS
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeMapCriteria.KEY;
import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeMapCriteria.VALUE;
import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeMapCriteria.VALUE_CONTAINING;
import static org.springframework.data.aerospike.repository.query.CriteriaDefinition.AerospikeMapCriteria.VALUE_EXISTS;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class PersonRepositoryQueryTests extends BaseBlockingIntegrationTests {
Expand Down Expand Up @@ -558,7 +559,7 @@ void findByMapKeyValueNotEqual() {
}

@Test
void findByAddressExists() {
void findByExists() {
assertThat(stefan.getAddress()).isNull();
assertThat(carter.getAddress()).isNotNull();
assertThat(dave.getAddress()).isNotNull();
Expand All @@ -575,12 +576,19 @@ void findByAddressExists() {
repository.save(stefan);
assertThat(repository.findByAddressZipCodeExists()).contains(carter, dave, stefan);

Map<String, String> stringMap = new HashMap<>();
stringMap.put("key", null);
stefan.setStringMap(stringMap);
repository.save(stefan);
assertThat(repository.findByStringMapContaining("key", VALUE_EXISTS)).contains(stefan);

stefan.setAddress(null); // cleanup
stefan.setStringMap(null);
repository.save(stefan);
}

@Test
void findByAddressIsNull() {
void findByIsNull() {
assertThat(stefan.getAddress()).isNull();
assertThat(carter.getAddress()).isNotNull();
assertThat(dave.getAddress()).isNotNull();
Expand All @@ -607,8 +615,7 @@ void findByAddressIsNull() {
stringMap.put("key", null);
stefan.setStringMap(stringMap);
repository.save(stefan);
assertThat(repository.findByStringMapContaining("key", (String) null)).contains(stefan); // key-specific
assertThat(repository.findByStringMapContaining("key12345", (String) null)).contains(stefan); // no such key
// assertThat(repository.findByStringMapContaining("key", (String) null)).contains(stefan); // key-specific
assertThat(repository.findByStringMapContaining(null, VALUE)).contains(stefan); // among all values in map

List<String> strings = new ArrayList<>();
Expand All @@ -618,28 +625,28 @@ void findByAddressIsNull() {
assertThat(repository.findByStringsContaining(null)).contains(stefan);

stefan.setStringMap(null); // cleanup
repository.save(stefan);
stefan.setStrings(null);
repository.save(stefan);
}

@Test
void findByAddressIsNotNull() {
void findByIsNotNull() {
assertThat(stefan.getAddress()).isNull();
assertThat(carter.getAddress()).isNotNull();
assertThat(dave.getAddress()).isNotNull();
assertThat(repository.findByAddressIsNotNull()).contains(carter, dave).doesNotContain(stefan);

stefan.setAddress(new Address(null, null, "zipCode", null));
repository.save(stefan);
assertThat(repository.findByAddressIsNotNull()).contains(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 @@ -648,17 +655,39 @@ void findByAddressIsNotNull() {
stringMap.put("key", "str");
stefan.setStringMap(stringMap);
repository.save(stefan);
assertThat(repository.findByStringMapNotContaining("key", (String) null)).contains(stefan);
assertThat(repository.findByStringMapNotContaining(null, VALUE)).contains(stefan);
assertThat(repository.findByStringMapNotContaining(null, VALUE)).contains(stefan); // among all 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
List<Person> personsWithMapKeyExists = repository.findByStringMapContaining("key", VALUE_EXISTS);
// and then filtering those that have the key's value equal to null
List<Person> personsWithMapValueNotNull = personsWithMapKeyExists.stream()
.filter(person -> person.getStringMap().get("key") != null).toList();
assertThat(personsWithMapValueNotNull).contains(stefan);

stringMap.put("key", null);
stefan.setStringMap(stringMap);
repository.save(stefan);
assertThat(repository.findByStringMapNotContaining(null, VALUE)).doesNotContain(stefan);

personsWithMapKeyExists = repository.findByStringMapContaining("key", VALUE_EXISTS);
personsWithMapValueNotNull = personsWithMapKeyExists.stream()
.filter(person -> person.getStringMap().get("key") != null).toList();
assertThat(personsWithMapValueNotNull).doesNotContain(stefan);

List<String> strings = new ArrayList<>();
strings.add("ing");
stefan.setStrings(strings);
repository.save(stefan);
assertThat(repository.findByStringsNotContaining(null)).contains(stefan);

stefan.setStringMap(null); // cleanup
strings.add(null);
stefan.setStrings(strings);
repository.save(stefan);
assertThat(repository.findByStringsNotContaining(null)).doesNotContain(stefan);

stefan.setStringMap(null); // cleanup
stefan.setStrings(null);
repository.save(stefan);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.data.aerospike.sample;

import jakarta.validation.constraints.NotNull;
import org.springframework.data.aerospike.repository.AerospikeRepository;
import org.springframework.data.aerospike.repository.query.CriteriaDefinition;
import org.springframework.data.domain.Page;
Expand Down Expand Up @@ -363,7 +364,7 @@ public interface PersonRepository<P extends Person> extends AerospikeRepository<
* @param key Map key
* @param value String to check whether map value is not equal to it
*/
List<P> findByStringMapNotContaining(String key, String value);
List<P> findByStringMapNotContaining(String key, @NotNull String value);

/**
* Find all entities containing the given map element (key or value depending on the given criterion)
Expand Down

0 comments on commit 66235fe

Please sign in to comment.