Skip to content

Regression in latest Redis Stream serialization (or deserialization) #3179

@artembilan

Description

@artembilan

This is a test which passes with Spring Data Redis 3.5.1:

	@Test
	void listAsRecordValueInStream() {
		String streamKey = "test-stream";
		List<String> input = Arrays.asList("Hello", "stream", "message");

		ReactiveRedisTemplate<String, ?> template =
				new ReactiveRedisTemplate<>(this.redisConnectionFactory, RedisSerializationContext.string());

		ReactiveStreamOperations<String, Object, Object> streamOperations = template.opsForStream();

		@SuppressWarnings("unchecked")
		Mono<List<?>> result =
				streamOperations.add(StreamRecords.objectBacked(input).withStreamKey(streamKey))
						.thenMany(streamOperations.read(List.class, StreamOffset.fromStart(streamKey)))
						.next()
						.map(Record::getValue);

		StepVerifier.create(result)
				.expectNext(input)
				.verifyComplete();
	}

In debug, on the StreamObjectMapper.toObjectRecord(), I see the value like:

source = {StreamRecords$MapBackedRecord@9126} "MapBackedRecord{recordId=1752698244313-0, kvMap={[0]=Hello, [0]._class=java.lang.String, [1]=stream, [1]._class=java.lang.String, [2]=message, [2]._class=java.lang.String, _class=java.util.Arrays$ArrayList}}"
 stream = "test-stream"
 recordId = {RecordId@9194} "1752698244313-0"
 kvMap = {LinkedHashMap@9195}  size = 7
  "[0]" -> "Hello"
  "[0]._class" -> "java.lang.String"
  "[1]" -> "stream"
  "[1]._class" -> "java.lang.String"
  "[2]" -> "message"
  "[2]._class" -> "java.lang.String"
  "_class" -> "java.util.Arrays$ArrayList"

With latest Spring Data Redis 4.0.0-SNAPSHOT, the test fails like:

	Suppressed: java.lang.IllegalArgumentException: Value must not be null
		at org.springframework.util.Assert.notNull(Assert.java:182)
		at org.springframework.data.redis.connection.stream.Record.of(Record.java:99)
		at org.springframework.data.redis.connection.stream.MapRecord.toObjectRecord(MapRecord.java:140)
		at org.springframework.data.redis.core.StreamObjectMapper.toObjectRecord(StreamObjectMapper.java:137)
		at org.springframework.data.redis.core.ReactiveStreamOperations.map(ReactiveStreamOperations.java:696)
		at org.springframework.data.redis.core.ReactiveStreamOperations.lambda$read$0(ReactiveStreamOperations.java:536)

where in debug at the same spot I see the value as:

record = {StreamRecords$MapBackedRecord@9470} "MapBackedRecord{recordId=1752698947969-0, kvMap={[0]=Hello, [0]._class=java.lang.String, [1]=stream, [1]._class=java.lang.String, [2]=message, [2]._class=java.lang.String}}"
 stream = "test-stream"
 recordId = {RecordId@9523} "1752698947969-0"
 kvMap = {LinkedHashMap@9524}  size = 6
  "[0]" -> "Hello"
  "[0]._class" -> "java.lang.String"
  "[1]" -> "stream"
  "[1]._class" -> "java.lang.String"
  "[2]" -> "message"
  "[2]._class" -> "java.lang.String"

Pay attention that there is no top-level _class entry in the map.

Looks like MappingRedisConverter.write(Object source, RedisData sink) has suffered some changes and there is no typeMapper.writeType(ClassUtils.getUserClass(source), sink.getBucket().getPath()); to put that _class entry into the target KV.
With the latest change we just have some optimization for collections:

		if (source instanceof Collection collection) {
			writeCollection(sink.getKeyspace(), "", collection, TypeInformation.of(Object.class), sink);
			return;
		}

Probably a side effect after: #2168.

The MappingRedisConverter.read(Class<R> type, RedisData source) in 3.5.1 picks up that _class from KV easy.
In 4.0.0-SNAPSHOT, it gets null for that typeMapper.readType(). And I believe just because there is no that _class entry stored in the target KV.

Thanks

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions