Skip to content

Commit 238f138

Browse files
committed
feature: Add support for UUIDs and ULIDs as TAG indexable field (resolves gh-364)
1 parent 6835844 commit 238f138

File tree

7 files changed

+223
-2
lines changed

7 files changed

+223
-2
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.redis.om.spring;
22

3+
import com.github.f4b6a3.ulid.Ulid;
34
import com.google.gson.GsonBuilder;
45
import com.google.gson.annotations.JsonAdapter;
56
import com.redis.om.spring.annotations.*;
@@ -207,8 +208,9 @@ private List<SchemaField> findIndexFields(java.lang.reflect.Field field, String
207208
} else if (indexed.schemaFieldType() == SchemaFieldType.AUTODETECT) {
208209
//
209210
// Any Character class, Boolean or Enum with AUTODETECT -> Tag Search Field
211+
// Also UUID and Ulid (classes whose toString() is a valid text representation of the value)
210212
//
211-
if (CharSequence.class.isAssignableFrom(fieldType) || (fieldType == Boolean.class)) {
213+
if (CharSequence.class.isAssignableFrom(fieldType) || (fieldType == Boolean.class) || (fieldType == UUID.class) || (fieldType == Ulid.class)) {
212214
fields.add(indexAsTagFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.separator(),
213215
indexed.arrayIndex(), indexed.alias()));
214216
} else if (fieldType.isEnum()) {
@@ -252,6 +254,8 @@ else if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fi
252254
fields.add(indexAsNumericFieldFor(field, true, prefix, indexed.sortable(), indexed.noindex(), indexed.alias()));
253255
} else if (collectionType == Point.class) {
254256
fields.add(indexAsGeoFieldFor(field, true, prefix, indexed.alias()));
257+
} else if (collectionType == UUID.class || collectionType == Ulid.class) {
258+
fields.add(indexAsTagFieldFor(field, true, prefix, indexed.sortable(), indexed.separator(), 0, indexed.alias()));
255259
} else {
256260
// Index nested JSON fields
257261
logger.debug(String.format("Found nested field on field of type: %s", field.getType()));
@@ -551,7 +555,9 @@ private List<SchemaField> getNestedField(String fieldPrefix, java.lang.reflect.F
551555
continue;
552556
} else if (subField.isAnnotationPresent(Indexed.class)) {
553557
boolean subFieldIsTagField = (subField.isAnnotationPresent(Indexed.class)
554-
&& (CharSequence.class.isAssignableFrom(subField.getType()) || (subField.getType() == Boolean.class)
558+
&& (CharSequence.class.isAssignableFrom(subField.getType())
559+
|| (subField.getType() == Boolean.class)
560+
|| (subField.getType() == UUID.class)
555561
|| (maybeCollectionType.isPresent() && (CharSequence.class.isAssignableFrom(maybeCollectionType.get())
556562
|| (maybeCollectionType.get() == Boolean.class)))));
557563
if (subFieldIsTagField) {

redis-om-spring/src/main/java/com/redis/om/spring/metamodel/indexed/TagField.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,26 @@ public NotEqualPredicate<E, T> notEq(String... values) {
3030
return new NotEqualPredicate<>(searchFieldAccessor, Arrays.asList(values));
3131
}
3232

33+
public NotEqualPredicate<E, T> notEq(Object... values) {
34+
return new NotEqualPredicate<>(searchFieldAccessor, Arrays.stream(values).map(Object::toString).toList());
35+
}
36+
3337
public InPredicate<E, ?> in(String... values) {
3438
return new InPredicate<>(searchFieldAccessor, Arrays.asList(values));
3539
}
3640

41+
public InPredicate<E, ?> in(Object... values) {
42+
return new InPredicate<>(searchFieldAccessor, Arrays.stream(values).map(Object::toString).toList());
43+
}
44+
3745
public ContainsAllPredicate<E, ?> containsAll(String... values) {
3846
return new ContainsAllPredicate<>(searchFieldAccessor, Arrays.asList(values));
3947
}
4048

49+
public ContainsAllPredicate<E, ?> containsAll(Object... values) {
50+
return new ContainsAllPredicate<>(searchFieldAccessor, Arrays.stream(values).map(Object::toString).toList());
51+
}
52+
4153
public NotEqualPredicate<E, T> containsNone(T value) {
4254
return new NotEqualPredicate<>(searchFieldAccessor, value);
4355
}

redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/ComplexDocumentSearchTest.java

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.redis.om.spring.annotations.document;
22

3+
import com.github.f4b6a3.ulid.Ulid;
34
import com.google.common.collect.Lists;
45
import com.redis.om.spring.AbstractBaseDocumentTest;
56
import com.redis.om.spring.annotations.document.fixtures.*;
67
import com.redis.om.spring.repository.query.Sort;
78
import com.redis.om.spring.search.stream.EntityStream;
9+
import com.redis.om.spring.search.stream.SearchStream;
810
import org.junit.jupiter.api.BeforeEach;
911
import org.junit.jupiter.api.Test;
1012
import org.springframework.beans.factory.annotation.Autowired;
@@ -17,6 +19,7 @@
1719
import java.time.LocalDateTime;
1820
import java.util.List;
1921
import java.util.Set;
22+
import java.util.UUID;
2023
import java.util.stream.Collectors;
2124

2225
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,9 +36,29 @@
3336
@Autowired
3437
ComplexRepository complexRepository;
3538

39+
@Autowired
40+
WithNestedListOfUUIDsRepository withNestedListOfUUIDsRepository;
41+
42+
@Autowired
43+
WithNestedListOfUlidsRepository withNestedListOfUlidsRepository;
44+
3645
@Autowired
3746
EntityStream es;
3847

48+
UUID uuid1 = UUID.fromString("297c358e-08df-4e9e-8e0e-c5fd6e2b5b2d");
49+
UUID uuid2 = UUID.fromString("72a93375-30ae-4075-86cf-b07c62800713");
50+
UUID uuid3 = UUID.fromString("d6b65b6c-3b93-44b7-8f30-8695028ba36f");
51+
UUID uuid4 = UUID.fromString("2b3b6517-5a4c-48da-a2a7-7a9ef2162c6d");
52+
UUID uuid5 = UUID.fromString("3b4c23cc-f9b6-4df1-b368-5ec82e32b8f9");
53+
UUID uuid6 = UUID.fromString("01be53c2-29e6-468f-9fe8-ad082d738c65");
54+
55+
Ulid ulid1 = Ulid.from(uuid1);
56+
Ulid ulid2 = Ulid.from(uuid2);
57+
Ulid ulid3 = Ulid.from(uuid3);
58+
Ulid ulid4 = Ulid.from(uuid4);
59+
Ulid ulid5 = Ulid.from(uuid5);
60+
Ulid ulid6 = Ulid.from(uuid6);
61+
3962
@BeforeEach
4063
void setup() {
4164
repository.deleteAll();
@@ -108,6 +131,19 @@ void setup() {
108131
Complex complex3 = Complex.of("complex3", List.of(HasAList.of(List.of("Pandiculation", "Taradiddle", "Ratoon")), HasAList.of(List.of("Yarborough", "Wabbit", "Erinaceous"))));
109132

110133
complexRepository.saveAll(List.of(complex1, complex2, complex3));
134+
135+
// complex deep nested with uuids
136+
WithNestedListOfUUIDs withNestedListOfUUIDs1 = WithNestedListOfUUIDs.of("withNestedListOfUUIDs1", List.of(uuid1, uuid2, uuid3));
137+
WithNestedListOfUUIDs withNestedListOfUUIDs2 = WithNestedListOfUUIDs.of("withNestedListOfUUIDs2", List.of(uuid4, uuid5, uuid6));
138+
139+
withNestedListOfUUIDsRepository.saveAll(List.of(withNestedListOfUUIDs1, withNestedListOfUUIDs2));
140+
141+
// complex deep nested with ulids
142+
WithNestedListOfUlids withNestedListOfUlids1 = WithNestedListOfUlids.of("withNestedListOfUlids1", List.of(ulid1, ulid2, ulid3));
143+
WithNestedListOfUlids withNestedListOfUlids2 = WithNestedListOfUlids.of("withNestedListOfUlids2", List.of(ulid4, ulid5, ulid6));
144+
145+
withNestedListOfUlidsRepository.saveAll(List.of(withNestedListOfUlids1, withNestedListOfUlids2));
146+
111147
}
112148

113149
@Test
@@ -362,4 +398,111 @@ void testListInsideAListTagSearch() {
362398
() -> assertThat(withTaradiddle).extracting("id").containsExactlyInAnyOrder("complex2", "complex3") //
363399
);
364400
}
401+
402+
// UUID Tests
403+
@Test void testFindByNestedListOfUUIDsValuesIn() {
404+
SearchStream<WithNestedListOfUUIDs> stream1 = es.of(WithNestedListOfUUIDs.class);
405+
406+
List<WithNestedListOfUUIDs> results1 = stream1 //
407+
.filter(WithNestedListOfUUIDs$.UUIDS.in(uuid1)).collect(Collectors.toList());
408+
409+
List<String> ids1 = results1.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
410+
assertThat(ids1).contains("withNestedListOfUUIDs1");
411+
412+
SearchStream<WithNestedListOfUUIDs> stream2 = es.of(WithNestedListOfUUIDs.class);
413+
414+
List<WithNestedListOfUUIDs> results2 = stream2 //
415+
.filter(WithNestedListOfUUIDs$.UUIDS.in(uuid1, uuid4)).collect(Collectors.toList());
416+
417+
List<String> ids2 = results2.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
418+
assertThat(ids2).contains("withNestedListOfUUIDs1", "withNestedListOfUUIDs2");
419+
420+
SearchStream<WithNestedListOfUUIDs> stream3 = es.of(WithNestedListOfUUIDs.class);
421+
422+
List<WithNestedListOfUUIDs> results3 = stream3 //
423+
.filter(WithNestedListOfUUIDs$.UUIDS.in(uuid1, uuid4)).collect(Collectors.toList());
424+
425+
List<String> ids3 = results3.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
426+
assertThat(ids3).contains("withNestedListOfUUIDs1", "withNestedListOfUUIDs2");
427+
}
428+
429+
@Test void testFindByNestedListOfUUIDsValuesNotEq() {
430+
SearchStream<WithNestedListOfUUIDs> stream1 = es.of(WithNestedListOfUUIDs.class);
431+
432+
List<WithNestedListOfUUIDs> results1 = stream1 //
433+
.filter(WithNestedListOfUUIDs$.UUIDS.notEq(uuid1, uuid2)).collect(Collectors.toList());
434+
435+
List<String> ids1 = results1.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
436+
assertThat(ids1).containsExactly("withNestedListOfUUIDs2");
437+
438+
SearchStream<WithNestedListOfUUIDs> stream2 = es.of(WithNestedListOfUUIDs.class);
439+
440+
List<WithNestedListOfUUIDs> results2 = stream2 //
441+
.filter(WithNestedListOfUUIDs$.UUIDS.notEq(uuid4, uuid5)).collect(Collectors.toList());
442+
443+
List<String> ids2 = results2.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
444+
assertThat(ids2).containsExactly("withNestedListOfUUIDs1");
445+
446+
SearchStream<WithNestedListOfUUIDs> stream3 = es.of(WithNestedListOfUUIDs.class);
447+
448+
List<WithNestedListOfUUIDs> results3 = stream3 //
449+
.filter(WithNestedListOfUUIDs$.UUIDS.notEq(uuid1, uuid4)).collect(Collectors.toList());
450+
451+
List<String> ids3 = results3.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
452+
assertThat(ids3).isEmpty();
453+
}
454+
455+
// ULIDs Tests
456+
457+
@Test void testFindByNestedListOfUlidsValuesIn() {
458+
SearchStream<WithNestedListOfUlids> stream1 = es.of(WithNestedListOfUlids.class);
459+
460+
List<WithNestedListOfUlids> results1 = stream1 //
461+
.filter(WithNestedListOfUlids$.ULIDS.in(ulid1)).collect(Collectors.toList());
462+
463+
List<String> ids1 = results1.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
464+
assertThat(ids1).contains("withNestedListOfUlids1");
465+
466+
SearchStream<WithNestedListOfUlids> stream2 = es.of(WithNestedListOfUlids.class);
467+
468+
List<WithNestedListOfUlids> results2 = stream2 //
469+
.filter(WithNestedListOfUlids$.ULIDS.in(ulid1, ulid4)).collect(Collectors.toList());
470+
471+
List<String> ids2 = results2.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
472+
assertThat(ids2).contains("withNestedListOfUlids1", "withNestedListOfUlids2");
473+
474+
SearchStream<WithNestedListOfUlids> stream3 = es.of(WithNestedListOfUlids.class);
475+
476+
List<WithNestedListOfUlids> results3 = stream3 //
477+
.filter(WithNestedListOfUlids$.ULIDS.in(ulid1, ulid4)).collect(Collectors.toList());
478+
479+
List<String> ids3 = results3.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
480+
assertThat(ids3).contains("withNestedListOfUlids1", "withNestedListOfUlids2");
481+
}
482+
483+
@Test void testFindByNestedListOfUlidsValuesNotEq() {
484+
SearchStream<WithNestedListOfUlids> stream1 = es.of(WithNestedListOfUlids.class);
485+
486+
List<WithNestedListOfUlids> results1 = stream1 //
487+
.filter(WithNestedListOfUlids$.ULIDS.notEq(ulid1, ulid2)).collect(Collectors.toList());
488+
489+
List<String> ids1 = results1.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
490+
assertThat(ids1).containsExactly("withNestedListOfUlids2");
491+
492+
SearchStream<WithNestedListOfUlids> stream2 = es.of(WithNestedListOfUlids.class);
493+
494+
List<WithNestedListOfUlids> results2 = stream2 //
495+
.filter(WithNestedListOfUlids$.ULIDS.notEq(ulid4, ulid5)).collect(Collectors.toList());
496+
497+
List<String> ids2 = results2.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
498+
assertThat(ids2).containsExactly("withNestedListOfUlids1");
499+
500+
SearchStream<WithNestedListOfUlids> stream3 = es.of(WithNestedListOfUlids.class);
501+
502+
List<WithNestedListOfUlids> results3 = stream3 //
503+
.filter(WithNestedListOfUlids$.ULIDS.notEq(ulid1, ulid4)).collect(Collectors.toList());
504+
505+
List<String> ids3 = results3.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
506+
assertThat(ids3).isEmpty();
507+
}
365508
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.redis.om.spring.annotations.document.fixtures;
2+
3+
import com.redis.om.spring.annotations.Document;
4+
import com.redis.om.spring.annotations.Indexed;
5+
import lombok.Data;
6+
import lombok.NonNull;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.data.annotation.Id;
9+
10+
import java.util.List;
11+
import java.util.UUID;
12+
13+
@Document
14+
@Data
15+
@RequiredArgsConstructor(staticName = "of")
16+
public class WithNestedListOfUUIDs {
17+
@Id
18+
@NonNull
19+
private String id;
20+
21+
@Indexed
22+
@NonNull
23+
private List<UUID> uuids;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.redis.om.spring.annotations.document.fixtures;
2+
3+
import com.redis.om.spring.repository.RedisDocumentRepository;
4+
5+
public interface WithNestedListOfUUIDsRepository extends RedisDocumentRepository<WithNestedListOfUUIDs,String> {
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.redis.om.spring.annotations.document.fixtures;
2+
3+
import com.github.f4b6a3.ulid.Ulid;
4+
import com.redis.om.spring.annotations.Document;
5+
import com.redis.om.spring.annotations.Indexed;
6+
import lombok.Data;
7+
import lombok.NonNull;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.data.annotation.Id;
10+
11+
import java.util.List;
12+
13+
@Document
14+
@Data
15+
@RequiredArgsConstructor(staticName = "of")
16+
public class WithNestedListOfUlids {
17+
@Id
18+
@NonNull
19+
private String id;
20+
21+
@Indexed
22+
@NonNull
23+
private List<Ulid> ulids;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.redis.om.spring.annotations.document.fixtures;
2+
3+
import com.redis.om.spring.repository.RedisDocumentRepository;
4+
5+
public interface WithNestedListOfUlidsRepository extends RedisDocumentRepository<WithNestedListOfUlids,String> {
6+
}

0 commit comments

Comments
 (0)