From 238f13867c69e9513fe915c7fef6a0d170e47ed3 Mon Sep 17 00:00:00 2001 From: Brian Sam-Bodden Date: Fri, 12 Jan 2024 10:03:09 -0700 Subject: [PATCH] feature: Add support for UUIDs and ULIDs as TAG indexable field (resolves gh-364) --- .../redis/om/spring/RediSearchIndexer.java | 10 +- .../om/spring/metamodel/indexed/TagField.java | 12 ++ .../document/ComplexDocumentSearchTest.java | 143 ++++++++++++++++++ .../fixtures/WithNestedListOfUUIDs.java | 24 +++ .../WithNestedListOfUUIDsRepository.java | 6 + .../fixtures/WithNestedListOfUlids.java | 24 +++ .../WithNestedListOfUlidsRepository.java | 6 + 7 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUUIDs.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUUIDsRepository.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUlids.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUlidsRepository.java diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/RediSearchIndexer.java b/redis-om-spring/src/main/java/com/redis/om/spring/RediSearchIndexer.java index 963fd733..3f2f7a91 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/RediSearchIndexer.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/RediSearchIndexer.java @@ -1,5 +1,6 @@ package com.redis.om.spring; +import com.github.f4b6a3.ulid.Ulid; import com.google.gson.GsonBuilder; import com.google.gson.annotations.JsonAdapter; import com.redis.om.spring.annotations.*; @@ -207,8 +208,9 @@ private List findIndexFields(java.lang.reflect.Field field, String } else if (indexed.schemaFieldType() == SchemaFieldType.AUTODETECT) { // // Any Character class, Boolean or Enum with AUTODETECT -> Tag Search Field + // Also UUID and Ulid (classes whose toString() is a valid text representation of the value) // - if (CharSequence.class.isAssignableFrom(fieldType) || (fieldType == Boolean.class)) { + if (CharSequence.class.isAssignableFrom(fieldType) || (fieldType == Boolean.class) || (fieldType == UUID.class) || (fieldType == Ulid.class)) { fields.add(indexAsTagFieldFor(field, isDocument, prefix, indexed.sortable(), indexed.separator(), indexed.arrayIndex(), indexed.alias())); } else if (fieldType.isEnum()) { @@ -252,6 +254,8 @@ else if (Set.class.isAssignableFrom(fieldType) || List.class.isAssignableFrom(fi fields.add(indexAsNumericFieldFor(field, true, prefix, indexed.sortable(), indexed.noindex(), indexed.alias())); } else if (collectionType == Point.class) { fields.add(indexAsGeoFieldFor(field, true, prefix, indexed.alias())); + } else if (collectionType == UUID.class || collectionType == Ulid.class) { + fields.add(indexAsTagFieldFor(field, true, prefix, indexed.sortable(), indexed.separator(), 0, indexed.alias())); } else { // Index nested JSON fields logger.debug(String.format("Found nested field on field of type: %s", field.getType())); @@ -551,7 +555,9 @@ private List getNestedField(String fieldPrefix, java.lang.reflect.F continue; } else if (subField.isAnnotationPresent(Indexed.class)) { boolean subFieldIsTagField = (subField.isAnnotationPresent(Indexed.class) - && (CharSequence.class.isAssignableFrom(subField.getType()) || (subField.getType() == Boolean.class) + && (CharSequence.class.isAssignableFrom(subField.getType()) + || (subField.getType() == Boolean.class) + || (subField.getType() == UUID.class) || (maybeCollectionType.isPresent() && (CharSequence.class.isAssignableFrom(maybeCollectionType.get()) || (maybeCollectionType.get() == Boolean.class))))); if (subFieldIsTagField) { diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/metamodel/indexed/TagField.java b/redis-om-spring/src/main/java/com/redis/om/spring/metamodel/indexed/TagField.java index 7fd18897..0fcaacbb 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/metamodel/indexed/TagField.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/metamodel/indexed/TagField.java @@ -30,14 +30,26 @@ public NotEqualPredicate notEq(String... values) { return new NotEqualPredicate<>(searchFieldAccessor, Arrays.asList(values)); } + public NotEqualPredicate notEq(Object... values) { + return new NotEqualPredicate<>(searchFieldAccessor, Arrays.stream(values).map(Object::toString).toList()); + } + public InPredicate in(String... values) { return new InPredicate<>(searchFieldAccessor, Arrays.asList(values)); } + public InPredicate in(Object... values) { + return new InPredicate<>(searchFieldAccessor, Arrays.stream(values).map(Object::toString).toList()); + } + public ContainsAllPredicate containsAll(String... values) { return new ContainsAllPredicate<>(searchFieldAccessor, Arrays.asList(values)); } + public ContainsAllPredicate containsAll(Object... values) { + return new ContainsAllPredicate<>(searchFieldAccessor, Arrays.stream(values).map(Object::toString).toList()); + } + public NotEqualPredicate containsNone(T value) { return new NotEqualPredicate<>(searchFieldAccessor, value); } diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/ComplexDocumentSearchTest.java b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/ComplexDocumentSearchTest.java index 18b2e8f2..02129e92 100644 --- a/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/ComplexDocumentSearchTest.java +++ b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/ComplexDocumentSearchTest.java @@ -1,10 +1,12 @@ package com.redis.om.spring.annotations.document; +import com.github.f4b6a3.ulid.Ulid; import com.google.common.collect.Lists; import com.redis.om.spring.AbstractBaseDocumentTest; import com.redis.om.spring.annotations.document.fixtures.*; import com.redis.om.spring.repository.query.Sort; import com.redis.om.spring.search.stream.EntityStream; +import com.redis.om.spring.search.stream.SearchStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -17,6 +19,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; @@ -33,9 +36,29 @@ @Autowired ComplexRepository complexRepository; + @Autowired + WithNestedListOfUUIDsRepository withNestedListOfUUIDsRepository; + + @Autowired + WithNestedListOfUlidsRepository withNestedListOfUlidsRepository; + @Autowired EntityStream es; + UUID uuid1 = UUID.fromString("297c358e-08df-4e9e-8e0e-c5fd6e2b5b2d"); + UUID uuid2 = UUID.fromString("72a93375-30ae-4075-86cf-b07c62800713"); + UUID uuid3 = UUID.fromString("d6b65b6c-3b93-44b7-8f30-8695028ba36f"); + UUID uuid4 = UUID.fromString("2b3b6517-5a4c-48da-a2a7-7a9ef2162c6d"); + UUID uuid5 = UUID.fromString("3b4c23cc-f9b6-4df1-b368-5ec82e32b8f9"); + UUID uuid6 = UUID.fromString("01be53c2-29e6-468f-9fe8-ad082d738c65"); + + Ulid ulid1 = Ulid.from(uuid1); + Ulid ulid2 = Ulid.from(uuid2); + Ulid ulid3 = Ulid.from(uuid3); + Ulid ulid4 = Ulid.from(uuid4); + Ulid ulid5 = Ulid.from(uuid5); + Ulid ulid6 = Ulid.from(uuid6); + @BeforeEach void setup() { repository.deleteAll(); @@ -108,6 +131,19 @@ void setup() { Complex complex3 = Complex.of("complex3", List.of(HasAList.of(List.of("Pandiculation", "Taradiddle", "Ratoon")), HasAList.of(List.of("Yarborough", "Wabbit", "Erinaceous")))); complexRepository.saveAll(List.of(complex1, complex2, complex3)); + + // complex deep nested with uuids + WithNestedListOfUUIDs withNestedListOfUUIDs1 = WithNestedListOfUUIDs.of("withNestedListOfUUIDs1", List.of(uuid1, uuid2, uuid3)); + WithNestedListOfUUIDs withNestedListOfUUIDs2 = WithNestedListOfUUIDs.of("withNestedListOfUUIDs2", List.of(uuid4, uuid5, uuid6)); + + withNestedListOfUUIDsRepository.saveAll(List.of(withNestedListOfUUIDs1, withNestedListOfUUIDs2)); + + // complex deep nested with ulids + WithNestedListOfUlids withNestedListOfUlids1 = WithNestedListOfUlids.of("withNestedListOfUlids1", List.of(ulid1, ulid2, ulid3)); + WithNestedListOfUlids withNestedListOfUlids2 = WithNestedListOfUlids.of("withNestedListOfUlids2", List.of(ulid4, ulid5, ulid6)); + + withNestedListOfUlidsRepository.saveAll(List.of(withNestedListOfUlids1, withNestedListOfUlids2)); + } @Test @@ -362,4 +398,111 @@ void testListInsideAListTagSearch() { () -> assertThat(withTaradiddle).extracting("id").containsExactlyInAnyOrder("complex2", "complex3") // ); } + + // UUID Tests + @Test void testFindByNestedListOfUUIDsValuesIn() { + SearchStream stream1 = es.of(WithNestedListOfUUIDs.class); + + List results1 = stream1 // + .filter(WithNestedListOfUUIDs$.UUIDS.in(uuid1)).collect(Collectors.toList()); + + List ids1 = results1.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList()); + assertThat(ids1).contains("withNestedListOfUUIDs1"); + + SearchStream stream2 = es.of(WithNestedListOfUUIDs.class); + + List results2 = stream2 // + .filter(WithNestedListOfUUIDs$.UUIDS.in(uuid1, uuid4)).collect(Collectors.toList()); + + List ids2 = results2.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList()); + assertThat(ids2).contains("withNestedListOfUUIDs1", "withNestedListOfUUIDs2"); + + SearchStream stream3 = es.of(WithNestedListOfUUIDs.class); + + List results3 = stream3 // + .filter(WithNestedListOfUUIDs$.UUIDS.in(uuid1, uuid4)).collect(Collectors.toList()); + + List ids3 = results3.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList()); + assertThat(ids3).contains("withNestedListOfUUIDs1", "withNestedListOfUUIDs2"); + } + + @Test void testFindByNestedListOfUUIDsValuesNotEq() { + SearchStream stream1 = es.of(WithNestedListOfUUIDs.class); + + List results1 = stream1 // + .filter(WithNestedListOfUUIDs$.UUIDS.notEq(uuid1, uuid2)).collect(Collectors.toList()); + + List ids1 = results1.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList()); + assertThat(ids1).containsExactly("withNestedListOfUUIDs2"); + + SearchStream stream2 = es.of(WithNestedListOfUUIDs.class); + + List results2 = stream2 // + .filter(WithNestedListOfUUIDs$.UUIDS.notEq(uuid4, uuid5)).collect(Collectors.toList()); + + List ids2 = results2.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList()); + assertThat(ids2).containsExactly("withNestedListOfUUIDs1"); + + SearchStream stream3 = es.of(WithNestedListOfUUIDs.class); + + List results3 = stream3 // + .filter(WithNestedListOfUUIDs$.UUIDS.notEq(uuid1, uuid4)).collect(Collectors.toList()); + + List ids3 = results3.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList()); + assertThat(ids3).isEmpty(); + } + + // ULIDs Tests + + @Test void testFindByNestedListOfUlidsValuesIn() { + SearchStream stream1 = es.of(WithNestedListOfUlids.class); + + List results1 = stream1 // + .filter(WithNestedListOfUlids$.ULIDS.in(ulid1)).collect(Collectors.toList()); + + List ids1 = results1.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList()); + assertThat(ids1).contains("withNestedListOfUlids1"); + + SearchStream stream2 = es.of(WithNestedListOfUlids.class); + + List results2 = stream2 // + .filter(WithNestedListOfUlids$.ULIDS.in(ulid1, ulid4)).collect(Collectors.toList()); + + List ids2 = results2.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList()); + assertThat(ids2).contains("withNestedListOfUlids1", "withNestedListOfUlids2"); + + SearchStream stream3 = es.of(WithNestedListOfUlids.class); + + List results3 = stream3 // + .filter(WithNestedListOfUlids$.ULIDS.in(ulid1, ulid4)).collect(Collectors.toList()); + + List ids3 = results3.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList()); + assertThat(ids3).contains("withNestedListOfUlids1", "withNestedListOfUlids2"); + } + + @Test void testFindByNestedListOfUlidsValuesNotEq() { + SearchStream stream1 = es.of(WithNestedListOfUlids.class); + + List results1 = stream1 // + .filter(WithNestedListOfUlids$.ULIDS.notEq(ulid1, ulid2)).collect(Collectors.toList()); + + List ids1 = results1.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList()); + assertThat(ids1).containsExactly("withNestedListOfUlids2"); + + SearchStream stream2 = es.of(WithNestedListOfUlids.class); + + List results2 = stream2 // + .filter(WithNestedListOfUlids$.ULIDS.notEq(ulid4, ulid5)).collect(Collectors.toList()); + + List ids2 = results2.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList()); + assertThat(ids2).containsExactly("withNestedListOfUlids1"); + + SearchStream stream3 = es.of(WithNestedListOfUlids.class); + + List results3 = stream3 // + .filter(WithNestedListOfUlids$.ULIDS.notEq(ulid1, ulid4)).collect(Collectors.toList()); + + List ids3 = results3.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList()); + assertThat(ids3).isEmpty(); + } } diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUUIDs.java b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUUIDs.java new file mode 100644 index 00000000..9a634602 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUUIDs.java @@ -0,0 +1,24 @@ +package com.redis.om.spring.annotations.document.fixtures; + +import com.redis.om.spring.annotations.Document; +import com.redis.om.spring.annotations.Indexed; +import lombok.Data; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.data.annotation.Id; + +import java.util.List; +import java.util.UUID; + +@Document +@Data +@RequiredArgsConstructor(staticName = "of") +public class WithNestedListOfUUIDs { + @Id + @NonNull + private String id; + + @Indexed + @NonNull + private List uuids; +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUUIDsRepository.java b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUUIDsRepository.java new file mode 100644 index 00000000..1dd072b6 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUUIDsRepository.java @@ -0,0 +1,6 @@ +package com.redis.om.spring.annotations.document.fixtures; + +import com.redis.om.spring.repository.RedisDocumentRepository; + +public interface WithNestedListOfUUIDsRepository extends RedisDocumentRepository { +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUlids.java b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUlids.java new file mode 100644 index 00000000..e8823382 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUlids.java @@ -0,0 +1,24 @@ +package com.redis.om.spring.annotations.document.fixtures; + +import com.github.f4b6a3.ulid.Ulid; +import com.redis.om.spring.annotations.Document; +import com.redis.om.spring.annotations.Indexed; +import lombok.Data; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.data.annotation.Id; + +import java.util.List; + +@Document +@Data +@RequiredArgsConstructor(staticName = "of") +public class WithNestedListOfUlids { + @Id + @NonNull + private String id; + + @Indexed + @NonNull + private List ulids; +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUlidsRepository.java b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUlidsRepository.java new file mode 100644 index 00000000..1e9de058 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/fixtures/WithNestedListOfUlidsRepository.java @@ -0,0 +1,6 @@ +package com.redis.om.spring.annotations.document.fixtures; + +import com.redis.om.spring.repository.RedisDocumentRepository; + +public interface WithNestedListOfUlidsRepository extends RedisDocumentRepository { +}