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: regressionA regression from a previous release

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions