From 777a1cb91b0d11bb62703d9a856aeac55fb990f0 Mon Sep 17 00:00:00 2001 From: Brian Sam-Bodden Date: Thu, 6 Jun 2024 16:30:52 -0700 Subject: [PATCH] feature: Ability to modify/filter the ID portion of the Key (Redis Cluster Hashtags) (resolves gh-454) --- .../spring/RedisEnhancedKeyValueAdapter.java | 18 ++ .../om/spring/RedisJSONKeyValueAdapter.java | 32 ++- .../com/redis/om/spring/id/IdAsHashTag.java | 8 + .../java/com/redis/om/spring/id/IdFilter.java | 18 ++ .../redis/om/spring/id/IdentifierFilter.java | 5 + .../om/spring/indexing/RediSearchIndexer.java | 31 +++ .../repository/RedisDocumentRepository.java | 3 + .../repository/RedisEnhancedRepository.java | 4 + .../SimpleRedisDocumentRepository.java | 19 ++ .../SimpleRedisEnhancedRepository.java | 20 ++ .../document/DocumentWithIdFilterTest.java | 217 +++++++++++++++++ .../hash/RedisHashWithIdFilterTest.java | 218 ++++++++++++++++++ .../document/model/DocWithHashTagId.java | 24 ++ .../DocWithHashTagIdRepository.java | 8 + .../hash/model/HashWithHashTagId.java | 23 ++ .../HashWithHashTagIdRepository.java | 9 + 16 files changed, 648 insertions(+), 9 deletions(-) create mode 100644 redis-om-spring/src/main/java/com/redis/om/spring/id/IdAsHashTag.java create mode 100644 redis-om-spring/src/main/java/com/redis/om/spring/id/IdFilter.java create mode 100644 redis-om-spring/src/main/java/com/redis/om/spring/id/IdentifierFilter.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/DocumentWithIdFilterTest.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/annotations/hash/RedisHashWithIdFilterTest.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/DocWithHashTagId.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/DocWithHashTagIdRepository.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/fixtures/hash/model/HashWithHashTagId.java create mode 100644 redis-om-spring/src/test/java/com/redis/om/spring/fixtures/hash/repository/HashWithHashTagIdRepository.java diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/RedisEnhancedKeyValueAdapter.java b/redis-om-spring/src/main/java/com/redis/om/spring/RedisEnhancedKeyValueAdapter.java index cd5b4a28..a8fa6807 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/RedisEnhancedKeyValueAdapter.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/RedisEnhancedKeyValueAdapter.java @@ -3,6 +3,7 @@ import com.redis.om.spring.audit.EntityAuditor; import com.redis.om.spring.convert.MappingRedisOMConverter; import com.redis.om.spring.convert.RedisOMCustomConversions; +import com.redis.om.spring.id.IdentifierFilter; import com.redis.om.spring.indexing.RediSearchIndexer; import com.redis.om.spring.ops.RedisModulesOperations; import com.redis.om.spring.ops.search.SearchOperations; @@ -486,4 +487,21 @@ void addFieldToRemove(byte[] field) { fieldsToRemove.add(field); } } + + /** + * Creates a new {@link byte[] key} using the given {@link String keyspace} and {@link String id}. + * + * @param keyspace {@link String name} of the Redis {@literal keyspace}. + * @param id {@link String} identifying the key. + * @return a {@link byte[]} constructed from the {@link String keyspace} and {@link String id}. + */ + public byte[] createKey(String keyspace, String id) { + // handle IdFilters + var maybeIdentifierFilter = indexer.getIdentifierFilterFor(keyspace); + if (maybeIdentifierFilter.isPresent()) { + IdentifierFilter filter = (IdentifierFilter) maybeIdentifierFilter.get(); + id = filter.filter(id); + } + return toBytes(keyspace + ":" + id); + } } diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/RedisJSONKeyValueAdapter.java b/redis-om-spring/src/main/java/com/redis/om/spring/RedisJSONKeyValueAdapter.java index 4f361c02..8f18f67a 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/RedisJSONKeyValueAdapter.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/RedisJSONKeyValueAdapter.java @@ -5,6 +5,7 @@ import com.google.gson.reflect.TypeToken; import com.redis.om.spring.audit.EntityAuditor; import com.redis.om.spring.convert.RedisOMCustomConversions; +import com.redis.om.spring.id.IdentifierFilter; import com.redis.om.spring.indexing.RediSearchIndexer; import com.redis.om.spring.ops.RedisModulesOperations; import com.redis.om.spring.ops.json.JSONOperations; @@ -41,7 +42,6 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; -import static com.redis.om.spring.util.ObjectUtils.getKey; import static com.redis.om.spring.util.ObjectUtils.isPrimitiveOfType; public class RedisJSONKeyValueAdapter extends RedisKeyValueAdapter { @@ -63,14 +63,14 @@ public class RedisJSONKeyValueAdapter extends RedisKeyValueAdapter { * @param redisOps must not be {@literal null}. * @param rmo must not be {@literal null}. * @param mappingContext must not be {@literal null}. - * @param keyspaceToIndexMap must not be {@literal null}. + * @param indexer must not be {@literal null}. */ @SuppressWarnings("unchecked") public RedisJSONKeyValueAdapter( // RedisOperations redisOps, // RedisModulesOperations rmo, // RedisMappingContext mappingContext, // - RediSearchIndexer keyspaceToIndexMap, // + RediSearchIndexer indexer, // GsonBuilder gsonBuilder, // FeatureExtractor featureExtractor, // RedisOMProperties redisOMProperties) { @@ -79,7 +79,7 @@ public RedisJSONKeyValueAdapter( // this.redisJSONOperations = modulesOperations.opsForJSON(); this.redisOperations = redisOps; this.mappingContext = mappingContext; - this.indexer = keyspaceToIndexMap; + this.indexer = indexer; this.auditor = new EntityAuditor(this.redisOperations); this.gsonBuilder = gsonBuilder; this.featureExtractor = featureExtractor; @@ -98,7 +98,7 @@ public Object put(Object id, Object item, String keyspace) { logger.debug(String.format("%s, %s, %s", id, item, keyspace)); @SuppressWarnings("unchecked") JSONOperations ops = (JSONOperations) redisJSONOperations; - String key = getKey(keyspace, id); + String key = createKeyAsString(keyspace, id); processVersion(key, item); auditor.processEntity(key, item); @@ -129,7 +129,8 @@ public Object put(Object id, Object item, String keyspace) { @Nullable @Override public T get(Object id, String keyspace, Class type) { - return get(getKey(keyspace, id), type); + String key = createKeyAsString(keyspace, id); + return get(key, type); } @Nullable @@ -192,7 +193,8 @@ public T delete(Object id, String keyspace, Class type) { @SuppressWarnings("unchecked") JSONOperations ops = (JSONOperations) redisJSONOperations; T entity = get(id, keyspace, type); if (entity != null) { - ops.del(getKey(keyspace, id), Path2.ROOT_PATH); + String key = createKeyAsString(keyspace, id); + ops.del(key, Path2.ROOT_PATH); } return entity; @@ -243,8 +245,8 @@ public long count(String keyspace) { */ @Override public boolean contains(Object id, String keyspace) { - Boolean exists = redisOperations.execute( - (RedisCallback) connection -> connection.keyCommands().exists(toBytes(getKey(keyspace, id)))); + Boolean exists = redisOperations.execute((RedisCallback) connection -> connection.keyCommands() + .exists(toBytes(createKeyAsString(keyspace, id)))); return exists != null && exists; } @@ -360,4 +362,16 @@ private Number getEntityVersion(String key, String versionProperty) { Long[] dbVersionArray = (Long[]) ops.get(key, type, Path2.of("$." + versionProperty)); return dbVersionArray != null ? dbVersionArray[0] : null; } + + public String createKeyAsString(String keyspace, Object id) { + String format = keyspace.endsWith(":") ? "%s%s" : "%s:%s"; + + // handle IdFilters + var maybeIdentifierFilter = indexer.getIdentifierFilterFor(keyspace); + if (maybeIdentifierFilter.isPresent()) { + IdentifierFilter filter = (IdentifierFilter) maybeIdentifierFilter.get(); + id = filter.filter(id.toString()); + } + return String.format(format, keyspace, id); + } } diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/id/IdAsHashTag.java b/redis-om-spring/src/main/java/com/redis/om/spring/id/IdAsHashTag.java new file mode 100644 index 00000000..1b2d08ce --- /dev/null +++ b/redis-om-spring/src/main/java/com/redis/om/spring/id/IdAsHashTag.java @@ -0,0 +1,8 @@ +package com.redis.om.spring.id; + +public class IdAsHashTag implements IdentifierFilter { + @Override + public String filter(Object id) { + return "{" + id.toString() + "}"; + } +} diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/id/IdFilter.java b/redis-om-spring/src/main/java/com/redis/om/spring/id/IdFilter.java new file mode 100644 index 00000000..19590da1 --- /dev/null +++ b/redis-om-spring/src/main/java/com/redis/om/spring/id/IdFilter.java @@ -0,0 +1,18 @@ +package com.redis.om.spring.id; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** + * Filters an identifier before reading/saving to Redis. + * + * @author Brian Sam-Bodden + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { FIELD, METHOD, ANNOTATION_TYPE }) +public @interface IdFilter { + Class> value(); +} diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/id/IdentifierFilter.java b/redis-om-spring/src/main/java/com/redis/om/spring/id/IdentifierFilter.java new file mode 100644 index 00000000..6609af67 --- /dev/null +++ b/redis-om-spring/src/main/java/com/redis/om/spring/id/IdentifierFilter.java @@ -0,0 +1,5 @@ +package com.redis.om.spring.id; + +public interface IdentifierFilter { + String filter(ID id); +} 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 b7fca610..1fda6af6 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 @@ -5,6 +5,8 @@ import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; import com.redis.om.spring.annotations.*; +import com.redis.om.spring.id.IdFilter; +import com.redis.om.spring.id.IdentifierFilter; import com.redis.om.spring.ops.RedisModulesOperations; import com.redis.om.spring.ops.search.SearchOperations; import com.redis.om.spring.repository.query.QueryUtils; @@ -32,6 +34,7 @@ import redis.clients.jedis.search.IndexDataType; import redis.clients.jedis.search.schemafields.*; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.time.Instant; @@ -50,6 +53,7 @@ public class RediSearchIndexer { private final Map> keyspaceToEntityClass = new ConcurrentHashMap<>(); private final Map, String> entityClassToKeySpace = new ConcurrentHashMap<>(); private final Map, String> entityClassToIndexName = new ConcurrentHashMap<>(); + private final Map, IdentifierFilter> entityClassToIdentifierFilter = new ConcurrentHashMap<>(); private final List> indexedEntityClasses = new ArrayList<>(); private final Map, List> entityClassToSchema = new ConcurrentHashMap<>(); private final Map, String>, String> entityClassFieldToAlias = new ConcurrentHashMap<>(); @@ -220,6 +224,18 @@ public Class getEntityClassForKeyspace(String keyspace) { return keyspaceToEntityClass.get(getKeyspace(keyspace)); } + public Optional> getIdentifierFilterFor(Class entityClass) { + if (entityClass != null && entityClassToIdentifierFilter.containsKey(entityClass)) { + return Optional.of(entityClassToIdentifierFilter.get(entityClass)); + } else { + return Optional.empty(); + } + } + + public Optional> getIdentifierFilterFor(String keyspace) { + return getIdentifierFilterFor(keyspaceToEntityClass.get(keyspace.endsWith(":") ? keyspace : keyspace + ":")); + } + public String getKeyspaceForEntityClass(Class entityClass) { String keyspace = entityClassToKeySpace.get(entityClass); if (keyspace == null) { @@ -778,6 +794,21 @@ private Optional createIndexedFieldForIdField(Class cl, List) identifierFilterClass.getDeclaredConstructor().newInstance(); + entityClassToIdentifierFilter.put(cl, identifierFilter); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException idFilterInstantiationException) { + logger.error(String.format("Could not instantiate IdFilter of type %s applied to class %s", + identifierFilterClass.getSimpleName(), cl), idFilterInstantiationException); + } + } + } return result; } diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/repository/RedisDocumentRepository.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/RedisDocumentRepository.java index 43e2e012..1008d0cb 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/repository/RedisDocumentRepository.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/repository/RedisDocumentRepository.java @@ -9,6 +9,7 @@ import redis.clients.jedis.json.Path2; import java.io.IOException; +import java.util.concurrent.TimeUnit; @NoRepositoryBean public interface RedisDocumentRepository extends KeyValueRepository, QueryByExampleExecutor { @@ -32,6 +33,8 @@ public interface RedisDocumentRepository extends KeyValueRepository bulkLoad(String file) throws IOException; S update(S entity); diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/repository/RedisEnhancedRepository.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/RedisEnhancedRepository.java index c5c32c76..779294df 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/repository/RedisEnhancedRepository.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/repository/RedisEnhancedRepository.java @@ -7,6 +7,8 @@ import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.query.QueryByExampleExecutor; +import java.util.concurrent.TimeUnit; + @NoRepositoryBean public interface RedisEnhancedRepository extends KeyValueRepository, QueryByExampleExecutor { @@ -27,5 +29,7 @@ public interface RedisEnhancedRepository extends KeyValueRepository template = modulesOperations.template(); + return Boolean.TRUE.equals(template.expire(getKey(id), expiration, timeUnit)); + } + @Override public List saveAll(Iterable entities) { Assert.notNull(entities, "The given Iterable of entities must not be null!"); @@ -241,10 +248,22 @@ public String getKeyspace() { } private String getKey(Object id) { + var maybeIdentifierFilter = indexer.getIdentifierFilterFor(metadata.getJavaType()); + if (maybeIdentifierFilter.isPresent()) { + IdentifierFilter filter = (IdentifierFilter) maybeIdentifierFilter.get(); + id = filter.filter(id.toString()); + } return getKeyspace() + id.toString(); } public byte[] createKey(String keyspace, String id) { + // handle IdFilters + var maybeIdentifierFilter = indexer.getIdentifierFilterFor(keyspace); + if (maybeIdentifierFilter.isPresent()) { + IdentifierFilter filter = (IdentifierFilter) maybeIdentifierFilter.get(); + id = filter.filter(id); + } + return this.mappingConverter.toBytes(keyspace.endsWith(":") ? keyspace + id : keyspace + ":" + id); } 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 8758b879..a9f3609e 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 @@ -5,6 +5,7 @@ import com.redis.om.spring.RedisOMProperties; import com.redis.om.spring.audit.EntityAuditor; import com.redis.om.spring.convert.MappingRedisOMConverter; +import com.redis.om.spring.id.IdentifierFilter; import com.redis.om.spring.id.ULIDIdentifierGenerator; import com.redis.om.spring.indexing.RediSearchIndexer; import com.redis.om.spring.metamodel.MetamodelField; @@ -40,6 +41,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -144,6 +146,12 @@ public Long getExpiration(ID id) { return template.getExpire(getKey(id)); } + @Override + public boolean setExpiration(ID id, Long expiration, TimeUnit timeUnit) { + RedisTemplate template = modulesOperations.template(); + return Boolean.TRUE.equals(template.expire(getKey(id), expiration, timeUnit)); + } + /* (non-Javadoc) * * @see org.springframework.data.repository.CrudRepository#findAll() */ @@ -220,6 +228,11 @@ public String getKeyspace() { } private String getKey(Object id) { + var maybeIdentifierFilter = indexer.getIdentifierFilterFor(metadata.getJavaType()); + if (maybeIdentifierFilter.isPresent()) { + IdentifierFilter filter = (IdentifierFilter) maybeIdentifierFilter.get(); + id = filter.filter(id.toString()); + } return getKeyspace() + id.toString(); } @@ -266,6 +279,13 @@ public List saveAll(Iterable entities) { } public byte[] createKey(String keyspace, String id) { + // handle IdFilters + var maybeIdentifierFilter = indexer.getIdentifierFilterFor(keyspace); + if (maybeIdentifierFilter.isPresent()) { + IdentifierFilter filter = (IdentifierFilter) maybeIdentifierFilter.get(); + id = filter.filter(id); + } + return this.mappingConverter.toBytes(keyspace.endsWith(":") ? keyspace + id : keyspace + ":" + id); } diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/DocumentWithIdFilterTest.java b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/DocumentWithIdFilterTest.java new file mode 100644 index 00000000..4cabfa1e --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/document/DocumentWithIdFilterTest.java @@ -0,0 +1,217 @@ +package com.redis.om.spring.annotations.document; + +import com.redis.om.spring.AbstractBaseDocumentTest; +import com.redis.om.spring.fixtures.document.model.DocWithHashTagId; +import com.redis.om.spring.fixtures.document.model.DocWithHashTagId$; +import com.redis.om.spring.fixtures.document.repository.DocWithHashTagIdRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SuppressWarnings("SpellCheckingInspection") +class DocumentWithIdFilterTest extends AbstractBaseDocumentTest { + @Autowired + DocWithHashTagIdRepository repository; + + @AfterEach + public void cleanUp() { + repository.deleteAll(); + } + + @Test + void testRepositorySaveAll() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + + assertThat(template.hasKey("dwht:{" + dwht1.getId() + "}")).isTrue(); + assertThat(template.hasKey("dwht:{" + dwht2.getId() + "}")).isTrue(); + assertThat(template.hasKey("dwht:{" + dwht3.getId() + "}")).isTrue(); + + assertThat(template.hasKey("dwht:" + dwht1.getId())).isFalse(); + assertThat(template.hasKey("dwht:" + dwht2.getId())).isFalse(); + assertThat(template.hasKey("dwht:" + dwht3.getId())).isFalse(); + } + + @Test + void testRepositorySave() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.save(dwht1); + repository.save(dwht2); + repository.save(dwht3); + + assertThat(template.hasKey("dwht:{" + dwht1.getId() + "}")).isTrue(); + assertThat(template.hasKey("dwht:{" + dwht2.getId() + "}")).isTrue(); + assertThat(template.hasKey("dwht:{" + dwht3.getId() + "}")).isTrue(); + + assertThat(template.hasKey("dwht:" + dwht1.getId())).isFalse(); + assertThat(template.hasKey("dwht:" + dwht2.getId())).isFalse(); + assertThat(template.hasKey("dwht:" + dwht3.getId())).isFalse(); + } + + @Test + void testRepositoryCount() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + assertEquals(3, repository.count()); + } + + @Test + void testRepositoryFindById() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + + Optional maybedwht1 = repository.findById(dwht1.getId()); + Optional maybedwht2 = repository.findById(dwht1.getId()); + Optional maybedwht3 = repository.findById(dwht1.getId()); + + assertTrue(maybedwht1.isPresent()); + assertTrue(maybedwht2.isPresent()); + assertTrue(maybedwht3.isPresent()); + } + + @Test + void testFindAll() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + + List all = repository.findAll(); + assertThat(all).hasSize(3); + } + + + @Test + void testDeleteOne() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + + assertThat(repository.count()).isEqualTo(3); + repository.delete(dwht1); + assertThat(repository.count()).isEqualTo(2); + } + + @Test + void testGetIds() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + Iterable ids = repository.getIds(); + assertThat(ids).hasSize(3); + assertThat(ids).contains(dwht1.getId(), dwht2.getId(), dwht3.getId()); + } + + @Test + void testGetFieldsByIds() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + + Iterable ids = List.of(dwht1.getId(), dwht2.getId(), dwht3.getId()); + Iterable companyNames = repository.getFieldsByIds(ids, DocWithHashTagId$.NAME); + assertThat(companyNames).containsExactly(dwht1.getName(), dwht2.getName(), dwht3.getName()); + } + + @Test + void testGetId() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + + Iterable ids = List.of(dwht1.getId(), dwht2.getId(), dwht3.getId()); + assertThat(repository.getIds()).containsAll(ids); + } + + @Test + void testExpirationGetSet() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + repository.setExpiration(dwht1.getId(), 10L, TimeUnit.MINUTES); + + assertThat(repository.getExpiration(dwht1.getId())).isEqualTo(10L * 60); + } + + @Test + void testUpdateField() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + repository.updateField(dwht1, DocWithHashTagId$.NAME, "Rocinante"); + Optional maybedwht1 = repository.findById(dwht1.getId()); + assertAll( // + () -> assertThat(maybedwht1).isPresent(), // + () -> assertThat(maybedwht1.get().getName()).isEqualTo("Rocinante")// + ); + } + + @Test + void testDeleteAllById() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + Iterable ids = List.of(dwht1.getId(), dwht2.getId(), dwht3.getId()); + repository.deleteAllById(ids); + assertThat(repository.count()).isEqualTo(0); + } + + @Test + void testDeleteAll() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + assertThat(repository.count()).isEqualTo(3); + repository.deleteAll(); + assertThat(repository.count()).isEqualTo(0); + } + + @Test + void testDeleteById() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + assertThat(repository.count()).isEqualTo(3); + repository.deleteById(dwht1.getId()); + repository.deleteById(dwht2.getId()); + assertThat(repository.count()).isEqualTo(1); + } + + @Test + void testFindAllById() { + DocWithHashTagId dwht1 = DocWithHashTagId.of("dwht1"); + DocWithHashTagId dwht2 = DocWithHashTagId.of("dwht2"); + DocWithHashTagId dwht3 = DocWithHashTagId.of("dwht3"); + repository.saveAll(Set.of(dwht1, dwht2, dwht3)); + + Iterable ids = List.of(dwht1.getId(), dwht3.getId()); + assertThat(repository.findAllById(ids)).contains(dwht1, dwht3); + } + + +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/annotations/hash/RedisHashWithIdFilterTest.java b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/hash/RedisHashWithIdFilterTest.java new file mode 100644 index 00000000..86fd5180 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/annotations/hash/RedisHashWithIdFilterTest.java @@ -0,0 +1,218 @@ +package com.redis.om.spring.annotations.hash; + +import com.redis.om.spring.AbstractBaseEnhancedRedisTest; +import com.redis.om.spring.fixtures.document.model.Company; +import com.redis.om.spring.fixtures.hash.model.HashWithHashTagId; +import com.redis.om.spring.fixtures.hash.model.HashWithHashTagId$; +import com.redis.om.spring.fixtures.hash.repository.HashWithHashTagIdRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@SuppressWarnings("SpellCheckingInspection") +class RedisHashWithIdFilterTest extends AbstractBaseEnhancedRedisTest { + @Autowired + HashWithHashTagIdRepository repository; + + @AfterEach + public void cleanUp() { + repository.deleteAll(); + } + + @Test + void testRepositorySaveAll() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + + assertThat(template.hasKey("hwht:{" + hwht1.getId() + "}")).isTrue(); + assertThat(template.hasKey("hwht:{" + hwht2.getId() + "}")).isTrue(); + assertThat(template.hasKey("hwht:{" + hwht3.getId() + "}")).isTrue(); + + assertThat(template.hasKey("hwht:" + hwht1.getId())).isFalse(); + assertThat(template.hasKey("hwht:" + hwht2.getId())).isFalse(); + assertThat(template.hasKey("hwht:" + hwht3.getId())).isFalse(); + } + + @Test + void testRepositorySave() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.save(hwht1); + repository.save(hwht2); + repository.save(hwht3); + + assertThat(template.hasKey("hwht:{" + hwht1.getId() + "}")).isTrue(); + assertThat(template.hasKey("hwht:{" + hwht2.getId() + "}")).isTrue(); + assertThat(template.hasKey("hwht:{" + hwht3.getId() + "}")).isTrue(); + + assertThat(template.hasKey("hwht:" + hwht1.getId())).isFalse(); + assertThat(template.hasKey("hwht:" + hwht2.getId())).isFalse(); + assertThat(template.hasKey("hwht:" + hwht3.getId())).isFalse(); + } + + @Test + void testRepositoryCount() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + assertEquals(3, repository.count()); + } + + @Test + void testRepositoryFindById() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + + Optional maybehwht1 = repository.findById(hwht1.getId()); + Optional maybehwht2 = repository.findById(hwht1.getId()); + Optional maybehwht3 = repository.findById(hwht1.getId()); + + assertTrue(maybehwht1.isPresent()); + assertTrue(maybehwht2.isPresent()); + assertTrue(maybehwht3.isPresent()); + } + + @Test + void testFindAll() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + + List all = repository.findAll(); + assertThat(all).hasSize(3); + } + + + @Test + void testDeleteOne() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + + assertThat(repository.count()).isEqualTo(3); + repository.delete(hwht1); + assertThat(repository.count()).isEqualTo(2); + } + + @Test + void testGetIds() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + Iterable ids = repository.getIds(); + assertThat(ids).hasSize(3); + assertThat(ids).contains(hwht1.getId(), hwht2.getId(), hwht3.getId()); + } + + @Test + void testGetFieldsByIds() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + + Iterable ids = List.of(hwht1.getId(), hwht2.getId(), hwht3.getId()); + Iterable companyNames = repository.getFieldsByIds(ids, HashWithHashTagId$.NAME); + assertThat(companyNames).containsExactly(hwht1.getName(), hwht2.getName(), hwht3.getName()); + } + + @Test + void testGetId() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + + Iterable ids = List.of(hwht1.getId(), hwht2.getId(), hwht3.getId()); + assertThat(repository.getIds()).containsAll(ids); + } + + @Test + void testExpirationGetSet() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + repository.setExpiration(hwht1.getId(), 10L, TimeUnit.MINUTES); + + assertThat(repository.getExpiration(hwht1.getId())).isEqualTo(10L * 60); + } + + @Test + void testUpdateField() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + repository.updateField(hwht1, HashWithHashTagId$.NAME, "Rocinante"); + Optional maybehwht1 = repository.findById(hwht1.getId()); + assertAll( // + () -> assertThat(maybehwht1).isPresent(), // + () -> assertThat(maybehwht1.get().getName()).isEqualTo("Rocinante")// + ); + } + + @Test + void testDeleteAllById() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + Iterable ids = List.of(hwht1.getId(), hwht2.getId(), hwht3.getId()); + repository.deleteAllById(ids); + assertThat(repository.count()).isEqualTo(0); + } + + @Test + void testDeleteAll() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + assertThat(repository.count()).isEqualTo(3); + repository.deleteAll(); + assertThat(repository.count()).isEqualTo(0); + } + + @Test + void testDeleteById() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + assertThat(repository.count()).isEqualTo(3); + repository.deleteById(hwht1.getId()); + repository.deleteById(hwht2.getId()); + assertThat(repository.count()).isEqualTo(1); + } + + @Test + void testFindAllById() { + HashWithHashTagId hwht1 = HashWithHashTagId.of("hwht1"); + HashWithHashTagId hwht2 = HashWithHashTagId.of("hwht2"); + HashWithHashTagId hwht3 = HashWithHashTagId.of("hwht3"); + repository.saveAll(Set.of(hwht1, hwht2, hwht3)); + + Iterable ids = List.of(hwht1.getId(), hwht3.getId()); + assertThat(repository.findAllById(ids)).contains(hwht1, hwht3); + } + + +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/DocWithHashTagId.java b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/DocWithHashTagId.java new file mode 100644 index 00000000..706bfa84 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/model/DocWithHashTagId.java @@ -0,0 +1,24 @@ +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.id.IdAsHashTag; +import com.redis.om.spring.id.IdFilter; +import lombok.*; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +@Data +@RequiredArgsConstructor(staticName = "of") +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(force = true) +@Document("dwht") +public class DocWithHashTagId { + @Id + @IdFilter(value = IdAsHashTag.class) + private String id; + + @Indexed + @NonNull + private String name; +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/DocWithHashTagIdRepository.java b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/DocWithHashTagIdRepository.java new file mode 100644 index 00000000..f145f249 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/document/repository/DocWithHashTagIdRepository.java @@ -0,0 +1,8 @@ +package com.redis.om.spring.fixtures.document.repository; + +import com.redis.om.spring.fixtures.document.model.DocWithHashTagId; +import com.redis.om.spring.repository.RedisEnhancedRepository; + +@SuppressWarnings("unused") +public interface DocWithHashTagIdRepository extends RedisEnhancedRepository { +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/hash/model/HashWithHashTagId.java b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/hash/model/HashWithHashTagId.java new file mode 100644 index 00000000..64a16a42 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/hash/model/HashWithHashTagId.java @@ -0,0 +1,23 @@ +package com.redis.om.spring.fixtures.hash.model; + +import com.redis.om.spring.annotations.Indexed; +import com.redis.om.spring.id.IdAsHashTag; +import com.redis.om.spring.id.IdFilter; +import lombok.*; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +@Data +@RequiredArgsConstructor(staticName = "of") +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(force = true) +@RedisHash("hwht") +public class HashWithHashTagId { + @Id + @IdFilter(value = IdAsHashTag.class) + private String id; + + @Indexed + @NonNull + private String name; +} diff --git a/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/hash/repository/HashWithHashTagIdRepository.java b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/hash/repository/HashWithHashTagIdRepository.java new file mode 100644 index 00000000..0770ecf8 --- /dev/null +++ b/redis-om-spring/src/test/java/com/redis/om/spring/fixtures/hash/repository/HashWithHashTagIdRepository.java @@ -0,0 +1,9 @@ +package com.redis.om.spring.fixtures.hash.repository; + + +import com.redis.om.spring.fixtures.hash.model.HashWithHashTagId; +import com.redis.om.spring.repository.RedisEnhancedRepository; + +@SuppressWarnings("unused") +public interface HashWithHashTagIdRepository extends RedisEnhancedRepository { +}