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 65eeb1da..892d5847 100644 --- a/src/main/java/org/springframework/data/aerospike/query/FilterOperation.java +++ b/src/main/java/org/springframework/data/aerospike/query/FilterOperation.java @@ -48,7 +48,9 @@ import static com.aerospike.client.command.ParticleType.MAP; import static com.aerospike.client.command.ParticleType.STRING; import static org.springframework.data.aerospike.query.qualifier.QualifierKey.*; +import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.convertToStringListExclStart; import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.getDotPathArray; +import static org.springframework.data.aerospike.repository.query.AerospikeQueryCreatorUtils.resolveCtxList; import static org.springframework.data.aerospike.util.FilterOperationRegexpBuilder.getContaining; import static org.springframework.data.aerospike.util.FilterOperationRegexpBuilder.getEndsWith; import static org.springframework.data.aerospike.util.FilterOperationRegexpBuilder.getNotContaining; @@ -1082,7 +1084,7 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { - return collectionContains(IndexCollectionType.MAPKEYS, qualifierMap); + return cdtContains(IndexCollectionType.MAPKEYS, qualifierMap); } }, /** @@ -1118,7 +1120,7 @@ public Exp filterExp(Map qualifierMap) { @Override public Filter sIndexFilter(Map qualifierMap) { - return collectionContains(IndexCollectionType.MAPVALUES, qualifierMap); + return cdtContains(IndexCollectionType.MAPVALUES, qualifierMap); } }, /** @@ -1255,7 +1257,7 @@ public Filter sIndexFilter(Map qualifierMap) { return null; } - return collectionContains(IndexCollectionType.LIST, qualifierMap); + return cdtContains(IndexCollectionType.LIST, qualifierMap); } }, /** @@ -1937,10 +1939,6 @@ protected static Value getKey(Map qualifierMap) { return Value.get(qualifierMap.get(KEY)); } - protected static String getKeyAsString(Map qualifierMap) { - return (String) qualifierMap.get(KEY); - } - protected static Value getNestedKey(Map qualifierMap) { return Value.get(qualifierMap.get(NESTED_KEY)); } @@ -1977,9 +1975,14 @@ protected static ServerVersionSupport getServerVersionSupport(Map qualifierMap); - protected Filter collectionContains(IndexCollectionType collectionType, Map qualifierMap) { + protected Filter cdtContains(IndexCollectionType collectionType, Map qualifierMap) { Value val = getValue(qualifierMap); int valType = val.getType(); + String[] dotPathArray = getDotPathArray(getDotPath(qualifierMap)); + if (dotPathArray != null && dotPathArray.length > 1) { + List ctxList = convertToStringListExclStart(dotPathArray); + qualifierMap.put(CTX_ARRAY, resolveCtxList(ctxList)); + } return switch (valType) { // TODO: Add Bytes and Double Support (will fail on old mode - no results) case INTEGER -> Filter.contains(getBinName(qualifierMap), collectionType, val.toLong(), 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 47633be8..71a2521e 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 @@ -41,7 +41,7 @@ protected static Qualifier setQualifier(QueryQualifierBuilder qb, String binName qb.setDotPath(dotPath); String[] dotPathArr = getDotPathArray(dotPath); if (dotPathArr != null && dotPathArr.length > 2) { - List ctxList = getCtxFromDotPathArray(dotPathArr); + List ctxList = convertToStringListExclStartAndEnd(dotPathArr); qb.setCtxArray(resolveCtxList(ctxList)); } } @@ -49,7 +49,7 @@ protected static Qualifier setQualifier(QueryQualifierBuilder qb, String binName return qb.build(); } - private static CTX[] resolveCtxList(List ctxList) { + public static CTX[] resolveCtxList(List ctxList) { return ctxList.stream() .filter(not(String::isEmpty)) .map(AerospikeContextDslResolverUtils::toCtx) @@ -69,10 +69,29 @@ public static String[] getDotPathArray(List dotPathList) { return null; } - protected static List getCtxFromDotPathArray(@NonNull String[] dotPathArr) { - return Arrays.stream(dotPathArr) + /** + * Convert a String array into String List excluding the first and the last elements + * + * @param array String array + * @return String List + */ + protected static List convertToStringListExclStartAndEnd(@NonNull String[] array) { + return Arrays.stream(array) + .skip(1) // first element is bin name + .limit(array.length - 2L) // last element is the key we already have + .collect(Collectors.toList()); + } + + /** + * Convert a String array into String List excluding the first element + * + * @param array String array + * @return String List + */ + public static List convertToStringListExclStart(@NonNull String[] array) { + return Arrays.stream(array) .skip(1) // first element is bin name - .limit(dotPathArr.length - 2L) // last element is the key we already have + .limit(array.length - 1L) .collect(Collectors.toList()); } diff --git a/src/test/java/org/springframework/data/aerospike/core/sync/AerospikeTemplateQueryAggregationTests.java b/src/test/java/org/springframework/data/aerospike/core/sync/AerospikeTemplateQueryAggregationTests.java index 6ad80814..193641cb 100644 --- a/src/test/java/org/springframework/data/aerospike/core/sync/AerospikeTemplateQueryAggregationTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/sync/AerospikeTemplateQueryAggregationTests.java @@ -68,8 +68,7 @@ public void setUp() { template.save(thirdPerson); // Create index - additionalAerospikeTestOperations.createIndex(Person.class, - "person_age_index", "age", IndexType.NUMERIC); + additionalAerospikeTestOperations.createIndex(Person.class, "person_age_index", "age", IndexType.NUMERIC); } @AfterAll diff --git a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/find/ContainingTests.java b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/find/ContainingTests.java index 71dbfa51..8d328565 100644 --- a/src/test/java/org/springframework/data/aerospike/repository/query/blocking/find/ContainingTests.java +++ b/src/test/java/org/springframework/data/aerospike/repository/query/blocking/find/ContainingTests.java @@ -165,6 +165,18 @@ void findByCollectionContainingPOJO() { repository.save(leroi2); } + @Test + void findByNestedCollectionContainingList() { + if (serverVersionSupport.isFindByCDTSupported()) { + List> listOfLists1 = List.of(List.of(100)); + stefan.setListOfIntLists(listOfLists1); + repository.save(stefan); + + List persons = repository.findByListOfIntListsContaining(List.of(100)); + assertThat(persons).contains(stefan); + } + } + @Test void findByMapKeysContainingString() { assertThat(donny.getStringMap()).containsKey("key1"); 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 93ce5bdc..c5e6576b 100644 --- a/src/test/java/org/springframework/data/aerospike/sample/PersonRepository.java +++ b/src/test/java/org/springframework/data/aerospike/sample/PersonRepository.java @@ -1420,7 +1420,12 @@ List

findByFriendStringMapNotContaining(AerospikeQueryCriterion criterion, /** * Find all entities that satisfy the condition "have the list of lists which is greater than the given list". *

- * ListOfIntLists is the name of the list of lists + * ListOfIntLists is the name of the list of lists. + *

+ * Note: only the upper level ListOfLists will be compared even if the parameter has different number of levels. + * So findByListOfListsGreaterThan(List.of(1)) and findByListOfListsGreaterThan(List.of(List.of(List.of(1)))) + * will compare with the given parameter only the upper level ListOfLists itself + * *

*

* Information about ordering @@ -1429,6 +1434,13 @@ List

findByFriendStringMapNotContaining(AerospikeQueryCriterion criterion, */ List

findByListOfIntListsGreaterThan(List> list); + /** + * Find all entities that satisfy the condition "contain the given list". + *

+ * @param list List to contain + */ + List

findByListOfIntListsContaining(List list); + /** * Find all entities that satisfy the condition "have map in the given range" *