Skip to content

Commit 3b31693

Browse files
committed
test: add more tests for Nested Array Indexing feature
1 parent 2cbc90d commit 3b31693

File tree

3 files changed

+90
-64
lines changed

3 files changed

+90
-64
lines changed

redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ else if (fieldType == Point.class) {
591591
field.getName() :
592592
String.join(".", prefix, field.getName());
593593

594-
// For nested fields, automatically create index fields even without explicit annotations
594+
// For nested fields, automatically create index fields when the parent field is annotated with @Indexed(schemaFieldType = SchemaFieldType.NESTED)
595595
fields.addAll(createNestedIndexFields(field, subfield, subfieldPrefix, isDocument));
596596
}
597597
}
@@ -887,6 +887,32 @@ private List<SearchField> getNestedField(String fieldPrefix, java.lang.reflect.F
887887
return fieldList;
888888
}
889889

890+
/**
891+
* Determines the appropriate FieldType for a given Java class.
892+
* This utility method centralizes the logic for mapping Java types to Redis field types.
893+
*/
894+
private enum FieldTypeMapper {
895+
TAG,
896+
NUMERIC,
897+
GEO,
898+
UNSUPPORTED;
899+
900+
static FieldTypeMapper getFieldType(Class<?> fieldType) {
901+
if (CharSequence.class.isAssignableFrom(
902+
fieldType) || fieldType == Boolean.class || fieldType == UUID.class || fieldType == Ulid.class || fieldType
903+
.isEnum()) {
904+
return TAG;
905+
} else if (Number.class.isAssignableFrom(
906+
fieldType) || fieldType == LocalDateTime.class || fieldType == LocalDate.class || fieldType == Date.class || fieldType == Instant.class || fieldType == OffsetDateTime.class) {
907+
return NUMERIC;
908+
} else if (fieldType == Point.class) {
909+
return GEO;
910+
} else {
911+
return UNSUPPORTED;
912+
}
913+
}
914+
}
915+
890916
/**
891917
* Creates index fields for nested array elements automatically.
892918
* This method handles automatic indexing of all fields within nested objects
@@ -900,62 +926,48 @@ private List<SearchField> createNestedIndexFields(java.lang.reflect.Field arrayF
900926

901927
// For nested arrays, the path should be: $.arrayField[*].nestedField
902928
// The prefix already contains the array field name, so we just need [*].nestedField
929+
// JSON documents use a "$" prefix to denote the root of the document, while hash structures do not.
930+
// The `isDocument` flag determines whether the entity is stored as a JSON document or a hash structure in Redis.
931+
// This affects the field path format: JSON documents require "$." as the prefix, while hash structures do not.
903932
String fullFieldPath = isDocument ?
904933
"$." + arrayField.getName() + "[*]." + nestedField.getName() :
905934
arrayField.getName() + "[*]." + nestedField.getName();
906935

907936
logger.info(String.format("Creating automatic nested field index: %s -> %s", arrayField.getName(), fullFieldPath));
908937

909938
// Determine field type and create appropriate index field
910-
if (CharSequence.class.isAssignableFrom(
911-
nestedFieldType) || nestedFieldType == Boolean.class || nestedFieldType == UUID.class || nestedFieldType == Ulid.class) {
912-
913-
// Create TAG field for strings, booleans, UUIDs, and ULIDs
914-
FieldName fieldName = FieldName.of(fullFieldPath);
915-
String alias = QueryUtils.searchIndexFieldAliasFor(nestedField, prefix);
916-
if (alias != null && !alias.isEmpty()) {
917-
fieldName = fieldName.as(alias);
918-
}
919-
920-
fields.add(SearchField.of(arrayField, getTagField(fieldName, "|", false)));
921-
922-
} else if (Number.class.isAssignableFrom(
923-
nestedFieldType) || nestedFieldType == LocalDateTime.class || nestedFieldType == LocalDate.class || nestedFieldType == Date.class || nestedFieldType == Instant.class || nestedFieldType == OffsetDateTime.class) {
924-
925-
// Create NUMERIC field for numbers and dates
926-
FieldName fieldName = FieldName.of(fullFieldPath);
927-
String alias = QueryUtils.searchIndexFieldAliasFor(nestedField, prefix);
928-
if (alias != null && !alias.isEmpty()) {
929-
fieldName = fieldName.as(alias);
939+
FieldTypeMapper fieldTypeMapper = FieldTypeMapper.getFieldType(nestedFieldType);
940+
941+
switch (fieldTypeMapper) {
942+
case TAG -> {
943+
// Create TAG field for strings, booleans, UUIDs, ULIDs, and enums
944+
FieldName fieldName = FieldName.of(fullFieldPath);
945+
String alias = QueryUtils.searchIndexFieldAliasFor(nestedField, prefix);
946+
if (alias != null && !alias.isEmpty()) {
947+
fieldName = fieldName.as(alias);
948+
}
949+
fields.add(SearchField.of(arrayField, getTagField(fieldName, "|", false)));
930950
}
931-
932-
fields.add(SearchField.of(arrayField, NumericField.of(fieldName)));
933-
934-
} else if (nestedFieldType == Point.class) {
935-
936-
// Create GEO field for Point objects
937-
FieldName fieldName = FieldName.of(fullFieldPath);
938-
String alias = QueryUtils.searchIndexFieldAliasFor(nestedField, prefix);
939-
if (alias != null && !alias.isEmpty()) {
940-
fieldName = fieldName.as(alias);
951+
case NUMERIC -> {
952+
// Create NUMERIC field for numbers and dates
953+
FieldName fieldName = FieldName.of(fullFieldPath);
954+
String alias = QueryUtils.searchIndexFieldAliasFor(nestedField, prefix);
955+
if (alias != null && !alias.isEmpty()) {
956+
fieldName = fieldName.as(alias);
957+
}
958+
fields.add(SearchField.of(arrayField, NumericField.of(fieldName)));
941959
}
942-
943-
fields.add(SearchField.of(arrayField, GeoField.of(fieldName)));
944-
945-
} else if (nestedFieldType.isEnum()) {
946-
947-
// Create TAG field for enums
948-
FieldName fieldName = FieldName.of(fullFieldPath);
949-
String alias = QueryUtils.searchIndexFieldAliasFor(nestedField, prefix);
950-
if (alias != null && !alias.isEmpty()) {
951-
fieldName = fieldName.as(alias);
960+
case GEO -> {
961+
// Create GEO field for Point objects
962+
FieldName fieldName = FieldName.of(fullFieldPath);
963+
String alias = QueryUtils.searchIndexFieldAliasFor(nestedField, prefix);
964+
if (alias != null && !alias.isEmpty()) {
965+
fieldName = fieldName.as(alias);
966+
}
967+
fields.add(SearchField.of(arrayField, GeoField.of(fieldName)));
952968
}
953-
954-
fields.add(SearchField.of(arrayField, getTagField(fieldName, "|", false)));
955-
956-
} else {
957-
logger.debug(String.format("Skipping nested field %s of unsupported type %s", nestedField.getName(),
958-
nestedFieldType.getSimpleName()));
969+
case UNSUPPORTED -> logger.debug(String.format("Skipping nested field %s of unsupported type %s", nestedField
970+
.getName(), nestedFieldType.getSimpleName()));
959971
}
960972

961973
return fields;

redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.redis.om.spring.repository.query;
22

33
import java.lang.reflect.Field;
4+
import java.time.Instant;
45
import java.time.LocalDate;
56
import java.time.LocalDateTime;
7+
import java.time.OffsetDateTime;
68
import java.util.*;
79
import java.util.AbstractMap.SimpleEntry;
810
import java.util.Map.Entry;
@@ -103,6 +105,25 @@ public class RediSearchQuery implements RepositoryQuery {
103105
private static final Set<Part.Type> LEXICOGRAPHIC_PART_TYPES = Set.of(Part.Type.GREATER_THAN, Part.Type.LESS_THAN,
104106
Part.Type.GREATER_THAN_EQUAL, Part.Type.LESS_THAN_EQUAL, Part.Type.BETWEEN);
105107

108+
/**
109+
* Determines the Redis field type for a given Java class.
110+
* This utility method centralizes the logic for mapping Java types to Redis field types.
111+
*/
112+
private static FieldType getRedisFieldType(Class<?> fieldType) {
113+
if (CharSequence.class.isAssignableFrom(
114+
fieldType) || fieldType == Boolean.class || fieldType == UUID.class || fieldType == Ulid.class || fieldType
115+
.isEnum()) {
116+
return FieldType.TAG;
117+
} else if (Number.class.isAssignableFrom(
118+
fieldType) || fieldType == LocalDateTime.class || fieldType == LocalDate.class || fieldType == Date.class || fieldType == Instant.class || fieldType == OffsetDateTime.class) {
119+
return FieldType.NUMERIC;
120+
} else if (fieldType == Point.class) {
121+
return FieldType.GEO;
122+
} else {
123+
return null; // Unsupported type
124+
}
125+
}
126+
106127
private final QueryMethod queryMethod;
107128
private final RedisOMProperties redisOMProperties;
108129
private final boolean hasLanguageParameter;
@@ -411,9 +432,9 @@ private List<Pair<String, QueryClause>> extractQueryFields(Class<?> type, Part p
411432
//
412433
// Any Character class, Enums or Boolean -> Tag Search Field
413434
//
414-
if (CharSequence.class.isAssignableFrom(
415-
fieldType) || (fieldType == Boolean.class) || (fieldType == UUID.class) || (fieldType == Ulid.class) || (fieldType
416-
.isEnum())) {
435+
FieldType redisFieldType = getRedisFieldType(fieldType);
436+
437+
if (redisFieldType == FieldType.TAG) {
417438
QueryClause clause;
418439
if (indexAnnotation.lexicographic() && isLexicographicPartType(part.getType())) {
419440
clause = getLexicographicQueryClause(FieldType.TAG, part.getType());
@@ -427,8 +448,7 @@ private List<Pair<String, QueryClause>> extractQueryFields(Class<?> type, Part p
427448
//
428449
// Any Numeric class -> Numeric Search Field
429450
//
430-
else if (Number.class.isAssignableFrom(fieldType) || (fieldType == LocalDateTime.class) || (field
431-
.getType() == LocalDate.class) || (field.getType() == Date.class)) {
451+
else if (redisFieldType == FieldType.NUMERIC) {
432452
qf.add(Pair.of(actualKey, QueryClause.get(FieldType.NUMERIC, part.getType())));
433453
}
434454
//
@@ -457,15 +477,9 @@ else if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fi
457477
String actualNestedKey = (alias != null && !alias.isEmpty()) ? alias : nestedKey;
458478
Class<?> nestedFieldType = ClassUtils.resolvePrimitiveIfNecessary(nestedField.getType());
459479

460-
if (CharSequence.class.isAssignableFrom(
461-
nestedFieldType) || nestedFieldType == Boolean.class || nestedFieldType == UUID.class || nestedFieldType == Ulid.class || nestedFieldType
462-
.isEnum()) {
463-
qf.add(Pair.of(actualNestedKey, QueryClause.get(FieldType.TAG, part.getType())));
464-
} else if (Number.class.isAssignableFrom(
465-
nestedFieldType) || nestedFieldType == LocalDateTime.class || nestedFieldType == LocalDate.class || nestedFieldType == Date.class) {
466-
qf.add(Pair.of(actualNestedKey, QueryClause.get(FieldType.NUMERIC, part.getType())));
467-
} else if (nestedFieldType == Point.class) {
468-
qf.add(Pair.of(actualNestedKey, QueryClause.get(FieldType.GEO, part.getType())));
480+
FieldType nestedRedisFieldType = getRedisFieldType(nestedFieldType);
481+
if (nestedRedisFieldType != null) {
482+
qf.add(Pair.of(actualNestedKey, QueryClause.get(nestedRedisFieldType, part.getType())));
469483
}
470484
}
471485
}
@@ -481,7 +495,7 @@ else if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fi
481495
} else {
482496
qf.add(Pair.of(actualKey, QueryClause.get(FieldType.GEO, part.getType())));
483497
}
484-
} else if (CharSequence.class.isAssignableFrom(collectionType) || (collectionType == Boolean.class)) {
498+
} else if (getRedisFieldType(collectionType) == FieldType.TAG) {
485499
if (isANDQuery) {
486500
qf.add(Pair.of(actualKey, QueryClause.TAG_CONTAINING_ALL));
487501
} else {
@@ -495,7 +509,7 @@ else if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fi
495509
//
496510
// Point
497511
//
498-
else if (fieldType == Point.class) {
512+
else if (redisFieldType == FieldType.GEO) {
499513
qf.add(Pair.of(actualKey, QueryClause.get(FieldType.GEO, part.getType())));
500514
}
501515
//

tests/src/test/java/com/redis/om/spring/annotations/NestedArrayIndexingTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
/**
2828
* Tests for nested array indexing feature (GitHub issue #519).
2929
* This feature enables indexing of nested fields within List<Model> properties
30-
* using @Indexed(schemaFieldType = SchemaField.FieldType.NESTED).
30+
* using @Indexed(schemaFieldType = SchemaFieldType.NESTED).
3131
*/
3232
class NestedArrayIndexingTest extends AbstractBaseDocumentTest {
3333
private static final Logger logger = LoggerFactory.getLogger(NestedArrayIndexingTest.class);

0 commit comments

Comments
 (0)