From 7205e1cb04c1506b0793d0301983bf923254a962 Mon Sep 17 00:00:00 2001 From: Brian Sam-Bodden Date: Mon, 20 May 2024 22:34:52 -0700 Subject: [PATCH] feature: introduce @IndexingOptions annotation to control index lifecycle --- .../spring/annotations/IndexCreationMode.java | 7 +++ .../spring/annotations/IndexingOptions.java | 10 ++++ .../om/spring/indexing/RediSearchIndexer.java | 47 ++++++++++++++++++- .../SimpleRedisDocumentRepository.java | 2 +- .../SimpleRedisEnhancedRepository.java | 2 +- .../document/model/ModelDropAndRecreate.java | 21 +++++++++ .../document/model/ModelSkipAlways.java | 21 +++++++++ .../document/model/ModelSkipIfExist.java | 21 +++++++++ .../ModelDropAndRecreateRepository.java | 7 +++ .../repository/ModelSkipAlwaysRepository.java | 7 +++ .../ModelSkipIfExistsRepository.java | 7 +++ .../spring/indexing/IndexingOptionsTest.java | 42 +++++++++++++++++ 12 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 redis-om-spring/src/main/java/com/redis/om/spring/annotations/IndexCreationMode.java create mode 100644 redis-om-spring/src/main/java/com/redis/om/spring/annotations/IndexingOptions.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelDropAndRecreate.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelSkipAlways.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelSkipIfExist.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelDropAndRecreateRepository.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelSkipAlwaysRepository.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelSkipIfExistsRepository.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/indexing/IndexingOptionsTest.java diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/annotations/IndexCreationMode.java b/redis-om-spring/src/main/java/com/redis/om/spring/annotations/IndexCreationMode.java new file mode 100644 index 00000000..d36bcd9e --- /dev/null +++ b/redis-om-spring/src/main/java/com/redis/om/spring/annotations/IndexCreationMode.java @@ -0,0 +1,7 @@ +package com.redis.om.spring.annotations; + +public enum IndexCreationMode { + SKIP_IF_EXIST, + SKIP_ALWAYS, + DROP_AND_RECREATE +} diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/annotations/IndexingOptions.java b/redis-om-spring/src/main/java/com/redis/om/spring/annotations/IndexingOptions.java new file mode 100644 index 00000000..9f299b99 --- /dev/null +++ b/redis-om-spring/src/main/java/com/redis/om/spring/annotations/IndexingOptions.java @@ -0,0 +1,10 @@ +package com.redis.om.spring.annotations; + +import java.lang.annotation.*; + +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE }) +public @interface IndexingOptions { + IndexCreationMode creationMode() default IndexCreationMode.SKIP_IF_EXIST; +} diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java b/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java index 13763d02..f8ec0eaa 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java @@ -26,6 +26,7 @@ import org.springframework.data.util.TypeInformation; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; +import redis.clients.jedis.exceptions.JedisDataException; import redis.clients.jedis.search.FTCreateParams; import redis.clients.jedis.search.FieldName; import redis.clients.jedis.search.IndexDataType; @@ -92,6 +93,7 @@ public void createIndexFor(Class cl) { boolean isDocument = idxType == IndexDataType.JSON; Optional document = isDocument ? Optional.of(cl.getAnnotation(Document.class)) : Optional.empty(); Optional hash = !isDocument ? Optional.of(cl.getAnnotation(RedisHash.class)) : Optional.empty(); + Optional maybeIndexingOptions = Optional.ofNullable(cl.getAnnotation(IndexingOptions.class)); String indexName = ""; Optional maybeScoreField; @@ -137,7 +139,30 @@ public void createIndexFor(Class cl) { List fields = searchFields.stream().map(SearchField::getSchemaField).toList(); entityClassToSchema.put(cl, searchFields); entityClassToIndexName.put(cl, indexName); - opsForSearch.createIndex(params, fields); + if (maybeIndexingOptions.isPresent()) { + IndexingOptions options = maybeIndexingOptions.get(); + switch (options.creationMode()) { + case SKIP_IF_EXIST: + opsForSearch.createIndex(params, fields); + logger.info(String.format("Created index %s...", indexName)); + break; + case DROP_AND_RECREATE: + if (indexExistsFor(cl)) { + opsForSearch.dropIndex(); + logger.info(String.format("Dropped index %s", indexName)); + } + opsForSearch.createIndex(params, fields); + logger.info(String.format("Created index %s", indexName)); + break; + case SKIP_ALWAYS: + // do nothing and like it! + logger.info(String.format("Skipped index creation for %s", cl.getSimpleName())); + break; + } + } else { + opsForSearch.createIndex(params, fields); + logger.info(String.format("Created index %s", indexName)); + } } catch (Exception e) { logger.warn(String.format(SKIPPING_INDEX_CREATION, indexName, e.getMessage())); } @@ -197,10 +222,28 @@ public String getKeyspaceForEntityClass(Class entityClass) { return keyspace; } - public boolean indexExistsFor(Class entityClass) { + public boolean indexDefinitionExistsFor(Class entityClass) { return indexedEntityClasses.contains(entityClass); } + public boolean indexExistsFor(Class entityClass) { + try { + return getIndexInfo(entityClass) != null; + } catch (JedisDataException jde) { + if (jde.getMessage().contains("Unknown index name")) { + return false; + } else { + throw jde; + } + } + } + + Map getIndexInfo(Class entityClass) { + String indexName = entityClassToIndexName.get(entityClass); + SearchOperations opsForSearch = rmo.opsForSearch(indexName); + return opsForSearch.getInfo(); + } + public List getSchemaFor(Class entityClass) { return entityClassToSchema.get(entityClass); } diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisDocumentRepository.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisDocumentRepository.java index d03c5b0c..0a17fe3d 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisDocumentRepository.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisDocumentRepository.java @@ -386,7 +386,7 @@ public Page findAll(Pageable pageable) { return new PageImpl<>(result, Pageable.unpaged(), result.size()); } - if (indexer.indexExistsFor(metadata.getJavaType())) { + if (indexer.indexDefinitionExistsFor(metadata.getJavaType())) { Optional maybeSearchIndex = indexer.getIndexName(metadata.getJavaType()); if (maybeSearchIndex.isPresent()) { String searchIndex = maybeSearchIndex.get(); diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisEnhancedRepository.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisEnhancedRepository.java index cb62434a..cc580889 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisEnhancedRepository.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisEnhancedRepository.java @@ -190,7 +190,7 @@ public Page findAll(Pageable pageable) { return new PageImpl<>(result, Pageable.unpaged(), result.size()); } - if (indexer.indexExistsFor(metadata.getJavaType())) { + if (indexer.indexDefinitionExistsFor(metadata.getJavaType())) { Optional maybeSearchIndex = indexer.getIndexName(metadata.getJavaType()); if (maybeSearchIndex.isPresent()) { String searchIndex = maybeSearchIndex.get(); diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelDropAndRecreate.java b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelDropAndRecreate.java new file mode 100644 index 00000000..e48aec09 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelDropAndRecreate.java @@ -0,0 +1,21 @@ +package com.redis.om.spring.fixtures.document.model; + +import com.redis.om.spring.annotations.Document; +import com.redis.om.spring.annotations.Indexed; +import com.redis.om.spring.annotations.IndexingOptions; +import lombok.Data; +import lombok.NonNull; +import org.springframework.data.annotation.Id; + +import static com.redis.om.spring.annotations.IndexCreationMode.DROP_AND_RECREATE; + +@Data +@Document +@IndexingOptions(creationMode = DROP_AND_RECREATE) +public class ModelDropAndRecreate { + @Id + private String id; + @NonNull + @Indexed + private String name; +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelSkipAlways.java b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelSkipAlways.java new file mode 100644 index 00000000..54fb5d59 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelSkipAlways.java @@ -0,0 +1,21 @@ +package com.redis.om.spring.fixtures.document.model; + +import com.redis.om.spring.annotations.Document; +import com.redis.om.spring.annotations.Indexed; +import com.redis.om.spring.annotations.IndexingOptions; +import lombok.Data; +import lombok.NonNull; +import org.springframework.data.annotation.Id; + +import static com.redis.om.spring.annotations.IndexCreationMode.SKIP_ALWAYS; + +@Data +@Document +@IndexingOptions(creationMode = SKIP_ALWAYS) +public class ModelSkipAlways { + @Id + private String id; + @NonNull + @Indexed + private String name; +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelSkipIfExist.java b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelSkipIfExist.java new file mode 100644 index 00000000..cef527c4 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/ModelSkipIfExist.java @@ -0,0 +1,21 @@ +package com.redis.om.spring.fixtures.document.model; + +import com.redis.om.spring.annotations.Document; +import com.redis.om.spring.annotations.Indexed; +import com.redis.om.spring.annotations.IndexingOptions; +import lombok.Data; +import lombok.NonNull; +import org.springframework.data.annotation.Id; + +import static com.redis.om.spring.annotations.IndexCreationMode.SKIP_IF_EXIST; + +@Data +@Document +@IndexingOptions(creationMode = SKIP_IF_EXIST) +public class ModelSkipIfExist { + @Id + private String id; + @NonNull + @Indexed + private String name; +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelDropAndRecreateRepository.java b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelDropAndRecreateRepository.java new file mode 100644 index 00000000..0f18f001 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelDropAndRecreateRepository.java @@ -0,0 +1,7 @@ +package com.redis.om.spring.fixtures.document.repository; + +import com.redis.om.spring.fixtures.document.model.ModelDropAndRecreate; +import com.redis.om.spring.repository.RedisDocumentRepository; + +public interface ModelDropAndRecreateRepository extends RedisDocumentRepository { +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelSkipAlwaysRepository.java b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelSkipAlwaysRepository.java new file mode 100644 index 00000000..3ecdba51 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelSkipAlwaysRepository.java @@ -0,0 +1,7 @@ +package com.redis.om.spring.fixtures.document.repository; + +import com.redis.om.spring.fixtures.document.model.ModelSkipAlways; +import com.redis.om.spring.repository.RedisDocumentRepository; + +public interface ModelSkipAlwaysRepository extends RedisDocumentRepository { +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelSkipIfExistsRepository.java b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelSkipIfExistsRepository.java new file mode 100644 index 00000000..a4a42d56 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/ModelSkipIfExistsRepository.java @@ -0,0 +1,7 @@ +package com.redis.om.spring.fixtures.document.repository; + +import com.redis.om.spring.fixtures.document.model.ModelSkipIfExist; +import com.redis.om.spring.repository.RedisDocumentRepository; + +public interface ModelSkipIfExistsRepository extends RedisDocumentRepository { +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/indexing/IndexingOptionsTest.java b/redis-om-spring/src/test/java/com/redis/om/spring/indexing/IndexingOptionsTest.java new file mode 100644 index 00000000..c3843afc --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/indexing/IndexingOptionsTest.java @@ -0,0 +1,42 @@ +package com.redis.om.spring.indexing; + +import com.redis.om.spring.AbstractBaseDocumentTest; +import com.redis.om.spring.fixtures.document.model.ModelDropAndRecreate; +import com.redis.om.spring.fixtures.document.model.ModelSkipAlways; +import com.redis.om.spring.fixtures.document.model.ModelSkipIfExist; +import com.redis.om.spring.fixtures.document.repository.ModelDropAndRecreateRepository; +import com.redis.om.spring.fixtures.document.repository.ModelSkipAlwaysRepository; +import com.redis.om.spring.fixtures.document.repository.ModelSkipIfExistsRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IndexingOptionsTest extends AbstractBaseDocumentTest { + @Autowired + ModelSkipIfExistsRepository modelSkipIfExistRepository; + @Autowired + ModelSkipAlwaysRepository modelSkipAlwaysRepository; + @Autowired + ModelDropAndRecreateRepository modelDropAndRecreateRepository; + + @Test + @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) + void testSkipIfExists() { + assertThat(indexer.indexExistsFor(ModelSkipIfExist.class)).isTrue(); + } + + @Test + @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) + void testSkipAlways() { + assertThat(indexer.indexExistsFor(ModelSkipAlways.class)).isFalse(); + } + + @Test + @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) + void testDropAndRecreate() { + assertThat(indexer.indexExistsFor(ModelDropAndRecreate.class)).isTrue(); + } + +}