Skip to content

Commit

Permalink
feature: Add support for UUIDs and ULIDs as TAG indexable field (reso…
Browse files Browse the repository at this point in the history
…lves gh-364)
  • Loading branch information
bsbodden committed Jan 14, 2024
1 parent 6835844 commit 238f138
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -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.*;
Expand Down Expand Up @@ -207,8 +208,9 @@ private List<SchemaField> 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()) {
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -551,7 +555,9 @@ private List<SchemaField> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,26 @@ public NotEqualPredicate<E, T> notEq(String... values) {
return new NotEqualPredicate<>(searchFieldAccessor, Arrays.asList(values));
}

public NotEqualPredicate<E, T> notEq(Object... values) {
return new NotEqualPredicate<>(searchFieldAccessor, Arrays.stream(values).map(Object::toString).toList());
}

public InPredicate<E, ?> in(String... values) {
return new InPredicate<>(searchFieldAccessor, Arrays.asList(values));
}

public InPredicate<E, ?> in(Object... values) {
return new InPredicate<>(searchFieldAccessor, Arrays.stream(values).map(Object::toString).toList());
}

public ContainsAllPredicate<E, ?> containsAll(String... values) {
return new ContainsAllPredicate<>(searchFieldAccessor, Arrays.asList(values));
}

public ContainsAllPredicate<E, ?> containsAll(Object... values) {
return new ContainsAllPredicate<>(searchFieldAccessor, Arrays.stream(values).map(Object::toString).toList());
}

public NotEqualPredicate<E, T> containsNone(T value) {
return new NotEqualPredicate<>(searchFieldAccessor, value);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -362,4 +398,111 @@ void testListInsideAListTagSearch() {
() -> assertThat(withTaradiddle).extracting("id").containsExactlyInAnyOrder("complex2", "complex3") //
);
}

// UUID Tests
@Test void testFindByNestedListOfUUIDsValuesIn() {
SearchStream<WithNestedListOfUUIDs> stream1 = es.of(WithNestedListOfUUIDs.class);

List<WithNestedListOfUUIDs> results1 = stream1 //
.filter(WithNestedListOfUUIDs$.UUIDS.in(uuid1)).collect(Collectors.toList());

List<String> ids1 = results1.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
assertThat(ids1).contains("withNestedListOfUUIDs1");

SearchStream<WithNestedListOfUUIDs> stream2 = es.of(WithNestedListOfUUIDs.class);

List<WithNestedListOfUUIDs> results2 = stream2 //
.filter(WithNestedListOfUUIDs$.UUIDS.in(uuid1, uuid4)).collect(Collectors.toList());

List<String> ids2 = results2.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
assertThat(ids2).contains("withNestedListOfUUIDs1", "withNestedListOfUUIDs2");

SearchStream<WithNestedListOfUUIDs> stream3 = es.of(WithNestedListOfUUIDs.class);

List<WithNestedListOfUUIDs> results3 = stream3 //
.filter(WithNestedListOfUUIDs$.UUIDS.in(uuid1, uuid4)).collect(Collectors.toList());

List<String> ids3 = results3.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
assertThat(ids3).contains("withNestedListOfUUIDs1", "withNestedListOfUUIDs2");
}

@Test void testFindByNestedListOfUUIDsValuesNotEq() {
SearchStream<WithNestedListOfUUIDs> stream1 = es.of(WithNestedListOfUUIDs.class);

List<WithNestedListOfUUIDs> results1 = stream1 //
.filter(WithNestedListOfUUIDs$.UUIDS.notEq(uuid1, uuid2)).collect(Collectors.toList());

List<String> ids1 = results1.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
assertThat(ids1).containsExactly("withNestedListOfUUIDs2");

SearchStream<WithNestedListOfUUIDs> stream2 = es.of(WithNestedListOfUUIDs.class);

List<WithNestedListOfUUIDs> results2 = stream2 //
.filter(WithNestedListOfUUIDs$.UUIDS.notEq(uuid4, uuid5)).collect(Collectors.toList());

List<String> ids2 = results2.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
assertThat(ids2).containsExactly("withNestedListOfUUIDs1");

SearchStream<WithNestedListOfUUIDs> stream3 = es.of(WithNestedListOfUUIDs.class);

List<WithNestedListOfUUIDs> results3 = stream3 //
.filter(WithNestedListOfUUIDs$.UUIDS.notEq(uuid1, uuid4)).collect(Collectors.toList());

List<String> ids3 = results3.stream().map(WithNestedListOfUUIDs::getId).collect(Collectors.toList());
assertThat(ids3).isEmpty();
}

// ULIDs Tests

@Test void testFindByNestedListOfUlidsValuesIn() {
SearchStream<WithNestedListOfUlids> stream1 = es.of(WithNestedListOfUlids.class);

List<WithNestedListOfUlids> results1 = stream1 //
.filter(WithNestedListOfUlids$.ULIDS.in(ulid1)).collect(Collectors.toList());

List<String> ids1 = results1.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
assertThat(ids1).contains("withNestedListOfUlids1");

SearchStream<WithNestedListOfUlids> stream2 = es.of(WithNestedListOfUlids.class);

List<WithNestedListOfUlids> results2 = stream2 //
.filter(WithNestedListOfUlids$.ULIDS.in(ulid1, ulid4)).collect(Collectors.toList());

List<String> ids2 = results2.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
assertThat(ids2).contains("withNestedListOfUlids1", "withNestedListOfUlids2");

SearchStream<WithNestedListOfUlids> stream3 = es.of(WithNestedListOfUlids.class);

List<WithNestedListOfUlids> results3 = stream3 //
.filter(WithNestedListOfUlids$.ULIDS.in(ulid1, ulid4)).collect(Collectors.toList());

List<String> ids3 = results3.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
assertThat(ids3).contains("withNestedListOfUlids1", "withNestedListOfUlids2");
}

@Test void testFindByNestedListOfUlidsValuesNotEq() {
SearchStream<WithNestedListOfUlids> stream1 = es.of(WithNestedListOfUlids.class);

List<WithNestedListOfUlids> results1 = stream1 //
.filter(WithNestedListOfUlids$.ULIDS.notEq(ulid1, ulid2)).collect(Collectors.toList());

List<String> ids1 = results1.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
assertThat(ids1).containsExactly("withNestedListOfUlids2");

SearchStream<WithNestedListOfUlids> stream2 = es.of(WithNestedListOfUlids.class);

List<WithNestedListOfUlids> results2 = stream2 //
.filter(WithNestedListOfUlids$.ULIDS.notEq(ulid4, ulid5)).collect(Collectors.toList());

List<String> ids2 = results2.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
assertThat(ids2).containsExactly("withNestedListOfUlids1");

SearchStream<WithNestedListOfUlids> stream3 = es.of(WithNestedListOfUlids.class);

List<WithNestedListOfUlids> results3 = stream3 //
.filter(WithNestedListOfUlids$.ULIDS.notEq(ulid1, ulid4)).collect(Collectors.toList());

List<String> ids3 = results3.stream().map(WithNestedListOfUlids::getId).collect(Collectors.toList());
assertThat(ids3).isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -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<UUID> uuids;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.redis.om.spring.annotations.document.fixtures;

import com.redis.om.spring.repository.RedisDocumentRepository;

public interface WithNestedListOfUUIDsRepository extends RedisDocumentRepository<WithNestedListOfUUIDs,String> {
}
Original file line number Diff line number Diff line change
@@ -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<Ulid> ulids;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.redis.om.spring.annotations.document.fixtures;

import com.redis.om.spring.repository.RedisDocumentRepository;

public interface WithNestedListOfUlidsRepository extends RedisDocumentRepository<WithNestedListOfUlids,String> {
}

0 comments on commit 238f138

Please sign in to comment.