Skip to content

Commit 1b0fef4

Browse files
committed
feature: Add search over object collections to EntityStreams - resolves gh-180
1 parent 2381be3 commit 1b0fef4

File tree

8 files changed

+348
-34
lines changed

8 files changed

+348
-34
lines changed

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

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class RediSearchIndexer {
4747
private final Map<String, Class<?>> keyspaceToEntityClass = new ConcurrentHashMap<>();
4848
private final Map<Class<?>, String> entityClassToKeySpace = new ConcurrentHashMap<>();
4949
private final List<Class<?>> indexedEntityClasses = new ArrayList<>();
50-
private final Map<Class<?>,Schema> entityClassToSchema = new ConcurrentHashMap<>();
50+
private final Map<Class<?>, Schema> entityClassToSchema = new ConcurrentHashMap<>();
5151

5252
private static final Log logger = LogFactory.getLog(RediSearchIndexer.class);
5353

@@ -97,7 +97,8 @@ public void createIndexFor(Class<?> cl) {
9797
indexName = cl.getName() + "Idx";
9898
logger.info(String.format("Found @%s annotated class: %s", idxType, cl.getName()));
9999

100-
final List<java.lang.reflect.Field> allClassFields = com.redis.om.spring.util.ObjectUtils.getDeclaredFieldsTransitively(cl);
100+
final List<java.lang.reflect.Field> allClassFields = com.redis.om.spring.util.ObjectUtils
101+
.getDeclaredFieldsTransitively(cl);
101102

102103
List<Field> fields = new ArrayList<>();
103104

@@ -115,7 +116,8 @@ public void createIndexFor(Class<?> cl) {
115116
if (maybeIdField.isPresent()) {
116117
java.lang.reflect.Field idField = maybeIdField.get();
117118
// Only auto-index the @Id if not already indexed by the user (gh-135)
118-
if (!idField.isAnnotationPresent(Indexed.class) && !idField.isAnnotationPresent(Searchable.class) && (fields.stream().noneMatch(f -> f.name.equals(idField.getName())))) {
119+
if (!idField.isAnnotationPresent(Indexed.class) && !idField.isAnnotationPresent(Searchable.class)
120+
&& (fields.stream().noneMatch(f -> f.name.equals(idField.getName())))) {
119121
if (Number.class.isAssignableFrom(idField.getType())) {
120122
fields
121123
.add(indexAsNumericFieldFor(maybeIdField.get(), idxType == IndexDefinition.Type.JSON, "", true,
@@ -239,12 +241,15 @@ private List<Field> findIndexFields(java.lang.reflect.Field field, String prefix
239241
// Any Character class, Enums or Boolean -> Tag Search Field
240242
//
241243
if (CharSequence.class.isAssignableFrom(fieldType) || (fieldType == Boolean.class) || (fieldType.isEnum())) {
242-
fields.add(indexAsTagFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.separator(), indexed.arrayIndex()));
244+
fields.add(indexAsTagFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.separator(),
245+
indexed.arrayIndex()));
243246
}
244247
//
245248
// Any Numeric class -> Numeric Search Field
246249
//
247-
else if (Number.class.isAssignableFrom(fieldType) || (fieldType == LocalDateTime.class) || (field.getType() == LocalDate.class) || (field.getType() == Date.class) || (field.getType() == Instant.class)) {
250+
else if (Number.class.isAssignableFrom(fieldType) || (fieldType == LocalDateTime.class)
251+
|| (field.getType() == LocalDate.class) || (field.getType() == Date.class)
252+
|| (field.getType() == Instant.class)) {
248253
fields.add(indexAsNumericFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.noindex()));
249254
}
250255
//
@@ -263,7 +268,8 @@ else if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fi
263268
Class<?> collectionType = maybeCollectionType.get();
264269

265270
if (CharSequence.class.isAssignableFrom(collectionType) || (collectionType == Boolean.class)) {
266-
fields.add(indexAsTagFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.separator(), indexed.arrayIndex()));
271+
fields.add(indexAsTagFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.separator(),
272+
indexed.arrayIndex()));
267273
// Index nested fields
268274
} else if (isDocument) {
269275
if (Number.class.isAssignableFrom(collectionType)) {
@@ -291,25 +297,27 @@ else if (fieldType == Point.class) {
291297
// Recursively explore the fields for Index annotated fields
292298
//
293299
else {
294-
for (java.lang.reflect.Field subfield : com.redis.om.spring.util.ObjectUtils.getDeclaredFieldsTransitively(field.getType())) {
295-
String subfieldPrefix = (prefix == null || prefix.isBlank()) ?
296-
field.getName() :
297-
String.join(".", prefix, field.getName());
300+
for (java.lang.reflect.Field subfield : com.redis.om.spring.util.ObjectUtils
301+
.getDeclaredFieldsTransitively(field.getType())) {
302+
String subfieldPrefix = (prefix == null || prefix.isBlank()) ? field.getName()
303+
: String.join(".", prefix, field.getName());
298304
fields.addAll(findIndexFields(subfield, subfieldPrefix, isDocument));
299305
}
300306
}
301307
} else { // Schema field type hardcoded/set in @Indexed
302308
switch (indexed.schemaFieldType()) {
303309
case TAG ->
304-
fields.add(indexAsTagFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.separator(), indexed.arrayIndex()));
305-
case NUMERIC -> fields.add(indexAsNumericFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.noindex()));
310+
fields.add(indexAsTagFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.separator(),
311+
indexed.arrayIndex()));
312+
case NUMERIC ->
313+
fields.add(indexAsNumericFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.noindex()));
306314
case GEO -> fields.add(indexAsGeoFieldFor(field, true, prefix, indexed.sortable(), indexed.noindex()));
307315
case VECTOR -> fields.add(indexAsVectorFieldFor(field, isDocument, prefix, indexed));
308316
case NESTED -> {
309-
for (java.lang.reflect.Field subfield : com.redis.om.spring.util.ObjectUtils.getDeclaredFieldsTransitively(field.getType())) {
310-
String subfieldPrefix = (prefix == null || prefix.isBlank()) ?
311-
field.getName() :
312-
String.join(".", prefix, field.getName());
317+
for (java.lang.reflect.Field subfield : com.redis.om.spring.util.ObjectUtils
318+
.getDeclaredFieldsTransitively(field.getType())) {
319+
String subfieldPrefix = (prefix == null || prefix.isBlank()) ? field.getName()
320+
: String.join(".", prefix, field.getName());
313321
fields.addAll(findIndexFields(subfield, subfieldPrefix, isDocument));
314322
}
315323
}
@@ -370,14 +378,15 @@ private Field indexAsTagFieldFor(java.lang.reflect.Field field, boolean isDocume
370378
return new TagField(fieldName, ti.separator(), false);
371379
}
372380

373-
private Field indexAsVectorFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix, Indexed indexed) {
381+
private Field indexAsVectorFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix,
382+
Indexed indexed) {
374383
TypeInformation<?> typeInfo = TypeInformation.of(field.getType());
375384
String fieldPrefix = getFieldPrefix(prefix, isDocument);
376385

377386
String fieldPostfix = (isDocument && typeInfo.isCollectionLike() && !field.isAnnotationPresent(JsonAdapter.class))
378387
? "[*]"
379388
: "";
380-
String fieldName =fieldPrefix + field.getName() + fieldPostfix;
389+
String fieldName = fieldPrefix + field.getName() + fieldPostfix;
381390

382391
Map<String, Object> attributes = new HashMap<>();
383392
attributes.put("TYPE", indexed.type().toString());
@@ -416,14 +425,15 @@ private Field indexAsVectorFieldFor(java.lang.reflect.Field field, boolean isDoc
416425
return vectorField;
417426
}
418427

419-
private Field indexAsVectorFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix, VectorIndexed vi) {
428+
private Field indexAsVectorFieldFor(java.lang.reflect.Field field, boolean isDocument, String prefix,
429+
VectorIndexed vi) {
420430
TypeInformation<?> typeInfo = TypeInformation.of(field.getType());
421431
String fieldPrefix = getFieldPrefix(prefix, isDocument);
422432

423433
String fieldPostfix = (isDocument && typeInfo.isCollectionLike() && !field.isAnnotationPresent(JsonAdapter.class))
424434
? "[*]"
425435
: "";
426-
String fieldName =fieldPrefix + field.getName() + fieldPostfix;
436+
String fieldName = fieldPrefix + field.getName() + fieldPostfix;
427437

428438
Map<String, Object> attributes = new HashMap<>();
429439
attributes.put("TYPE", vi.type().toString());
@@ -566,7 +576,8 @@ private List<Field> getNestedField(String fieldPrefix, java.lang.reflect.Field f
566576
Type genericType = field.getGenericType();
567577
if (genericType instanceof ParameterizedType pt) {
568578
Class<?> actualTypeArgument = (Class<?>) pt.getActualTypeArguments()[0];
569-
List<java.lang.reflect.Field> subDeclaredFields = com.redis.om.spring.util.ObjectUtils.getDeclaredFieldsTransitively(actualTypeArgument);
579+
List<java.lang.reflect.Field> subDeclaredFields = com.redis.om.spring.util.ObjectUtils
580+
.getDeclaredFieldsTransitively(actualTypeArgument);
570581
String tempPrefix = "";
571582
if (prefix == null) {
572583
prefix = field.getName();
@@ -613,6 +624,22 @@ else if (Number.class.isAssignableFrom(subField.getType()) || (subField.getType(
613624
logger.info(String.format("Creating nested relationships: %s -> %s", field.getName(), subField.getName()));
614625
fieldList.add(new Field(fieldName, FieldType.NUMERIC));
615626
}
627+
} else if (subField.isAnnotationPresent(Searchable.class)) {
628+
Searchable searchable = subField.getAnnotation(Searchable.class);
629+
tempPrefix = field.getName() + "[0:].";
630+
631+
FieldName fieldName = FieldName.of(fieldPrefix + tempPrefix + subField.getName());
632+
fieldName = fieldName.as(QueryUtils.searchIndexFieldAliasFor(subField, prefix));
633+
634+
logger
635+
.info(String.format("Creating TEXT nested relationships: %s -> %s", field.getName(), subField.getName()));
636+
637+
String phonetic = ObjectUtils.isEmpty(searchable.phonetic()) ? null : searchable.phonetic();
638+
639+
fieldList.add(new TextField(fieldName, searchable.weight(), searchable.sortable(), searchable.nostem(),
640+
searchable.noindex(), phonetic));
641+
642+
continue;
616643
}
617644
getNestedField(fieldPrefix + tempPrefix, subField, prefix, fieldList);
618645
}

0 commit comments

Comments
 (0)