Skip to content

Commit

Permalink
feature: composite PKs , QBE for EntityStreams Test (#534)
Browse files Browse the repository at this point in the history
* feature: implement support for composite IDs using @IdClass

* release 0.9.8-SNAPSHOT

* dependencies: move up to Spring Boot/SDR 3.3.7

* feature: add repository methods to get Redis key from an entity (getKeyFor)

* test: add tests for QBE with EntityStreams

* test: add test String interpolation of params in EntityStream aggregations
  • Loading branch information
bsbodden authored Jan 16, 2025
1 parent 0448262 commit 48cfd02
Show file tree
Hide file tree
Showing 41 changed files with 2,073 additions and 94 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ Iterable<MyDoc> allMatches = repository.findAll(example);
<version>${version}</version>
</dependency>
```
> Check below if using Redis OM Spring version greater than `0.9.7`
> Check below if using Redis OM Spring version greater than `0.9.8-SNAPSHOT`
> ⚠️ Redis OM Spring versions greater than `v0.9.1` require the addition
of the [**Spring Milestone Repository**](https://repo.spring.io/milestone) to account
for the recent integration with the [**Spring AI**](https://docs.spring.io/spring-ai/reference/) project. When Spring AI `v1.0.0` is
Expand All @@ -430,10 +430,10 @@ repositories {
}
```

> ⚠️ Redis OM Spring versions greater than `v0.9.7` made OPTIONAL the addition
> ⚠️ Redis OM Spring versions greater than `v0.9.8-SNAPSHOT` made OPTIONAL the addition
of the [**Spring Milestone Repository**](https://repo.spring.io/milestone) to account
for the recent integration with the [**Spring AI**](https://docs.spring.io/spring-ai/reference/) project. When Spring AI `v1.0.0` is
released we will drop this requirement. If you want to opt-in for Vector Similarity Search features, you need to manually add the dependencies below. Check the VSS demo for
released we will drop this requirement. If you want to opt-in for Vector Similarity Search features, you need to manually add the dependencies below. Check the VSS demo for
for a full example.

```xml
Expand Down Expand Up @@ -535,7 +535,7 @@ inherited from the parent poms):
<path>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.9.7</version>
<version>0.9.8-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
</configuration>
Expand Down Expand Up @@ -582,7 +582,7 @@ repositories {

```groovy
ext {
redisOmVersion = '0.9.7'
redisOmVersion = '0.9.8-SNAPSHOT'
}
dependencies {
Expand Down
6 changes: 3 additions & 3 deletions demos/roms-documents/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<version>3.3.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

Expand All @@ -31,7 +31,7 @@
<dependency>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.9.7</version>
<version>0.9.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -141,7 +141,7 @@
<path>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.9.7</version>
<version>0.9.8-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
</configuration>
Expand Down
4 changes: 2 additions & 2 deletions demos/roms-hashes/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<version>3.3.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

Expand All @@ -30,7 +30,7 @@
<dependency>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.9.7</version>
<version>0.9.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
4 changes: 2 additions & 2 deletions demos/roms-permits/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<version>3.3.7</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
Expand Down Expand Up @@ -34,7 +34,7 @@
<dependency>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.9.7</version>
<version>0.9.8-SNAPSHOT</version>
</dependency>

<dependency>
Expand Down
6 changes: 3 additions & 3 deletions demos/roms-vss/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<version>3.3.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

Expand All @@ -24,7 +24,7 @@
<maven.test.source>21</maven.test.source>
<maven.test.target>21</maven.test.target>
<maven.deploy.skip>true</maven.deploy.skip>
<spring.version>3.3.2</spring.version>
<spring.version>3.3.7</spring.version>
<spring-ai.version>1.0.0-M2</spring-ai.version>
<djl.starter.version>0.26</djl.starter.version>
<djl.version>0.27.0</djl.version>
Expand Down Expand Up @@ -54,7 +54,7 @@
<dependency>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.9.7</version>
<version>0.9.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring-parent</artifactId>
<version>0.9.7</version>
<version>0.9.8-SNAPSHOT</version>
<name>redis-om-spring-parent</name>
<packaging>pom</packaging>

Expand Down
11 changes: 8 additions & 3 deletions redis-om-spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>com.redis.om</groupId>
<artifactId>redis-om-spring</artifactId>
<version>0.9.7</version>
<version>0.9.8-SNAPSHOT</version>
<packaging>jar</packaging>

<name>redis-om-spring</name>
Expand Down Expand Up @@ -61,8 +61,8 @@
<maven.test.source>21</maven.test.source>
<maven.test.target>21</maven.test.target>
<java.version>21</java.version>
<spring.version>3.3.2</spring.version>
<sdr.version>3.3.2</sdr.version>
<spring.version>3.3.7</spring.version>
<sdr.version>3.3.7</sdr.version>
<jedis.version>5.0.2</jedis.version>
<cdi>2.0-PFD</cdi>
<auto-service.version>1.1.1</auto-service.version>
Expand Down Expand Up @@ -170,6 +170,11 @@
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0</version>
</dependency>
<!-- Spring AI begin -->
<dependency>
<groupId>org.springframework.ai</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
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.mapping.RedisEnhancedMappingContext;
import com.redis.om.spring.mapping.RedisEnhancedPersistentEntity;
import com.redis.om.spring.ops.RedisModulesOperations;
import com.redis.om.spring.ops.search.SearchOperations;
import com.redis.om.spring.vectorize.Embedder;
import jakarta.persistence.IdClass;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.redis.connection.RedisConnection;
Expand All @@ -22,11 +28,11 @@
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
import org.springframework.data.redis.core.mapping.RedisPersistentProperty;
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import redis.clients.jedis.commands.KeyCommands;
import redis.clients.jedis.search.Query;
import redis.clients.jedis.search.SearchResult;

Expand All @@ -38,6 +44,7 @@

public class RedisEnhancedKeyValueAdapter extends RedisKeyValueAdapter {

private static final Log logger = LogFactory.getLog(RedisEnhancedKeyValueAdapter.class);
private final RedisOperations<?, ?> redisOperations;
private final RedisConverter converter;
private final RedisModulesOperations<String> modulesOperations;
Expand All @@ -60,7 +67,7 @@ public RedisEnhancedKeyValueAdapter( //
RediSearchIndexer indexer, //
Embedder embedder, //
RedisOMProperties redisOMProperties) {
this(redisOps, rmo, new RedisMappingContext(), indexer, embedder, redisOMProperties);
this(redisOps, rmo, new RedisEnhancedMappingContext(), indexer, embedder, redisOMProperties);
}

/**
Expand Down Expand Up @@ -137,20 +144,14 @@ public Object put(Object id, Object item, String keyspace) {
if (item instanceof RedisData redisData) {
rdo = redisData;
} else {
String idAsString = converter.getConversionService().convert(id, String.class);
if (idAsString == null) {
idAsString = id.toString();
}
String idAsString = validateKeyForWriting(id, item);
byte[] redisKey = createKey(sanitizeKeyspace(keyspace), idAsString);
auditor.processEntity(redisKey, item);
embedder.processEntity(item);

rdo = new RedisData();
converter.write(item, rdo);
}

if (rdo.getId() == null) {
rdo.setId(converter.getConversionService().convert(id, String.class));
rdo.setId(idAsString);
}

redisOperations.executePipelined((RedisCallback<Object>) connection -> {
Expand Down Expand Up @@ -181,7 +182,7 @@ public Object put(Object id, Object item, String keyspace) {
public <T> T get(Object id, String keyspace, Class<T> type) {

String stringId = asStringValue(id);
String stringKeyspace = sanitizeKeyspace(asStringValue(keyspace));
String stringKeyspace = sanitizeKeyspace(keyspace);

byte[] binId = createKey(stringKeyspace, stringId);

Expand Down Expand Up @@ -291,11 +292,14 @@ public <T> List<T> getAllOf(String keyspace, Class<T> type, long offset, int row
*/
@Override
public <T> T delete(Object id, String keyspace, Class<T> type) {
T o = get(id, keyspace, type);
String stringId = asStringValue(id);
String stringKeyspace = sanitizeKeyspace(keyspace);

T o = get(stringId, stringKeyspace, type);

if (o != null) {

byte[] keyToDelete = createKey(sanitizeKeyspace(asStringValue(keyspace)), asStringValue(id));
byte[] keyToDelete = createKey(stringKeyspace, stringId);

redisOperations.execute((RedisCallback<Void>) connection -> {
connection.keyCommands().unlink(keyToDelete);
Expand Down Expand Up @@ -334,8 +338,8 @@ public long count(String keyspace) {
*/
@Override
public boolean contains(Object id, String keyspace) {
Boolean exists = redisOperations.execute(
(RedisCallback<Boolean>) connection -> connection.keyCommands().exists(toBytes(getKey(keyspace, id))));
Boolean exists = redisOperations.execute((RedisCallback<Boolean>) connection -> connection.keyCommands()
.exists(toBytes(getKey(keyspace, asStringValue(id)))));

return exists != null && exists;
}
Expand All @@ -348,8 +352,9 @@ public void update(PartialUpdate<?> update) {

String keyspace = sanitizeKeyspace(entity.getKeySpace());
Object id = update.getId();
String stringId = asStringValue(id);

byte[] redisKey = createKey(keyspace, converter.getConversionService().convert(id, String.class));
byte[] redisKey = createKey(keyspace, stringId);

RedisData rdo = new RedisData();
this.converter.write(update, rdo);
Expand Down Expand Up @@ -423,11 +428,60 @@ private RedisUpdateObject fetchDeletePathsFromHash(RedisUpdateObject redisUpdate
private String asStringValue(Object value) {
if (value instanceof String valueAsString) {
return valueAsString;
}

// For composite IDs used in @IdClass
if (value != null) {
// Get all persistent entities
for (RedisPersistentEntity<?> entity : converter.getMappingContext().getPersistentEntities()) {
// Find the entity that uses this ID class
IdClass idClassAnn = entity.getType().getAnnotation(IdClass.class);
if (idClassAnn != null && idClassAnn.value().equals(value.getClass())) {
// Found the entity that uses this ID class
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(value);
RedisEnhancedPersistentEntity<?> enhancedEntity = (RedisEnhancedPersistentEntity<?>) entity;

// Build composite key from ID properties in order
List<String> idParts = new ArrayList<>();
for (RedisPersistentProperty idProperty : enhancedEntity.getIdProperties()) {
Object propertyValue = wrapper.getPropertyValue(idProperty.getName());
if (propertyValue != null) {
idParts.add(propertyValue.toString());
}
}
return String.join(":", idParts);
}
}
}

return getConverter().getConversionService().convert(value, String.class);
}

private String validateKeyForWriting(Object id, Object item) {
// Get the mapping context's entity info
RedisEnhancedPersistentEntity<?> entity = (RedisEnhancedPersistentEntity<?>) converter.getMappingContext()
.getRequiredPersistentEntity(item.getClass());

// Handle composite IDs
if (entity.isIdClassComposite()) {
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(item);
List<String> idParts = new ArrayList<>();

for (RedisPersistentProperty idProperty : entity.getIdProperties()) {
Object propertyValue = wrapper.getPropertyValue(idProperty.getName());
if (propertyValue != null) {
idParts.add(propertyValue.toString());
}
}

return String.join(":", idParts);
} else {
return getConverter().getConversionService().convert(value, String.class);
// Regular single ID handling
return converter.getConversionService().convert(id, String.class);
}
}


/**
* Read back and set {@link TimeToLive} for the property.
*
Expand Down Expand Up @@ -504,7 +558,8 @@ public byte[] createKey(String keyspace, String id) {
IdentifierFilter<String> filter = (IdentifierFilter<String>) maybeIdentifierFilter.get();
id = filter.filter(id);
}
return toBytes(keyspace + ":" + id);

return toBytes(keyspace.endsWith(":") ? keyspace + id : keyspace + ":" + id);
}

/**
Expand Down
Loading

0 comments on commit 48cfd02

Please sign in to comment.