Polishing.

Add support for MINID trimming. Add tests. Extract Jedis XAddParams conversion to StreamConverters.

Reorder methods. Simplify StreamRecord creation.

See #2247
This commit is contained in:
Mark Paluch
2022-03-09 09:46:06 +01:00
parent 9b8bca689a
commit c63e2e2900
9 changed files with 220 additions and 80 deletions

View File

@@ -199,19 +199,20 @@ public interface ReactiveStreamCommands {
class AddStreamRecord extends KeyCommand {
private final ByteBufferRecord record;
private final @Nullable Long maxlen;
private final boolean nomkstream;
private final @Nullable Long maxlen;
private final boolean approximateTrimming;
private final @Nullable RecordId minId;
private AddStreamRecord(ByteBufferRecord record, @Nullable Long maxlen, boolean nomkstream,
boolean approximateTrimming) {
boolean approximateTrimming, @Nullable RecordId minId) {
super(record.getStream());
this.record = record;
this.maxlen = maxlen;
this.nomkstream = nomkstream;
this.approximateTrimming = approximateTrimming;
this.minId = minId;
}
/**
@@ -224,7 +225,7 @@ public interface ReactiveStreamCommands {
Assert.notNull(record, "Record must not be null!");
return new AddStreamRecord(record, null, false, false);
return new AddStreamRecord(record, null, false, false, null);
}
/**
@@ -237,7 +238,7 @@ public interface ReactiveStreamCommands {
Assert.notNull(body, "Body must not be null!");
return new AddStreamRecord(StreamRecords.rawBuffer(body), null, false, false);
return new AddStreamRecord(StreamRecords.rawBuffer(body), null, false, false, null);
}
/**
@@ -247,16 +248,7 @@ public interface ReactiveStreamCommands {
* @return a new {@link ReactiveGeoCommands.GeoAddCommand} with {@literal key} applied.
*/
public AddStreamRecord to(ByteBuffer key) {
return new AddStreamRecord(record.withStreamKey(key), maxlen, false, false);
}
/**
* Limit the size of the stream to the given maximum number of elements.
*
* @return new instance of {@link AddStreamRecord}.
*/
public AddStreamRecord maxlen(long maxlen) {
return new AddStreamRecord(record, maxlen, false, false);
return new AddStreamRecord(record.withStreamKey(key), maxlen, nomkstream, approximateTrimming, minId);
}
/**
@@ -266,7 +258,7 @@ public interface ReactiveStreamCommands {
* @since 2.6
*/
public AddStreamRecord makeNoStream() {
return new AddStreamRecord(record, maxlen, true, false);
return new AddStreamRecord(record, maxlen, true, approximateTrimming, minId);
}
/**
@@ -277,7 +269,27 @@ public interface ReactiveStreamCommands {
* @since 2.6
*/
public AddStreamRecord makeNoStream(boolean makeNoStream) {
return new AddStreamRecord(record, maxlen, makeNoStream, false);
return new AddStreamRecord(record, maxlen, makeNoStream, approximateTrimming, minId);
}
/**
* Limit the size of the stream to the given maximum number of elements.
*
* @return new instance of {@link AddStreamRecord}.
*/
public AddStreamRecord maxlen(long maxlen) {
return new AddStreamRecord(record, maxlen, nomkstream, approximateTrimming, minId);
}
/**
* Apply {@code MINID} trimming strategy, that evicts entries with IDs lower than the one specified.
*
* @param minId the minimum record Id to retain.
* @return new instance of {@link AddStreamRecord}.
* @since 2.7
*/
public AddStreamRecord minId(RecordId minId) {
return new AddStreamRecord(record, maxlen, nomkstream, approximateTrimming, minId);
}
/**
@@ -286,14 +298,7 @@ public interface ReactiveStreamCommands {
* @return new instance of {@link AddStreamRecord}.
*/
public AddStreamRecord approximateTrimming(boolean approximateTrimming) {
return new AddStreamRecord(record, maxlen, nomkstream, approximateTrimming);
}
/**
* @return {@literal true} if {@literal approximateTrimming} is set.
*/
public boolean isApproximateTrimming() {
return approximateTrimming;
return new AddStreamRecord(record, maxlen, nomkstream, approximateTrimming, minId);
}
/**
@@ -307,6 +312,14 @@ public interface ReactiveStreamCommands {
return record;
}
/**
* @return {@literal true} if {@literal NOMKSTREAM} is set.
* @since 2.6
*/
public boolean isNoMkStream() {
return nomkstream;
}
/**
* Limit the size of the stream to the given maximum number of elements.
*
@@ -327,11 +340,28 @@ public interface ReactiveStreamCommands {
}
/**
* @return {@literal true} if {@literal NOMKSTREAM} is set.
* @since 2.6
* @return {@literal true} if {@literal approximateTrimming} is set.
* @since 2.7
*/
public boolean isNoMkStream() {
return nomkstream;
public boolean isApproximateTrimming() {
return approximateTrimming;
}
/**
* @return the minimum record Id to retain during trimming.
* @since 2.7
*/
@Nullable
public RecordId getMinId() {
return minId;
}
/**
* @return {@literal true} if {@literal MINID} is set.
* @since 2.7
*/
public boolean hasMinId() {
return minId != null;
}
}
@@ -1223,7 +1253,7 @@ public interface ReactiveStreamCommands {
}
public GroupCommand makeStream(boolean mkStream) {
return new GroupCommand(getKey(), action, groupName, consumerName, offset,mkStream);
return new GroupCommand(getKey(), action, groupName, consumerName, offset, mkStream);
}
public GroupCommand at(ReadOffset offset) {
@@ -1291,8 +1321,8 @@ public interface ReactiveStreamCommands {
* @since 2.3
*/
default Mono<String> xGroupCreate(ByteBuffer key, String groupName, ReadOffset readOffset, boolean mkStream) {
return xGroup(Mono.just(GroupCommand.createGroup(groupName).forStream(key).at(readOffset).makeStream(mkStream))).next()
.map(CommandResponse::getOutput);
return xGroup(Mono.just(GroupCommand.createGroup(groupName).forStream(key).at(readOffset).makeStream(mkStream)))
.next().map(CommandResponse::getOutput);
}
/**

View File

@@ -121,16 +121,19 @@ public interface RedisStreamCommands {
*/
class XAddOptions {
private static final XAddOptions NONE = new XAddOptions(null, false, false);
private static final XAddOptions NONE = new XAddOptions(null, false, false, null);
private final @Nullable Long maxlen;
private final boolean nomkstream;
private final boolean approximateTrimming;
private final @Nullable RecordId minId;
private XAddOptions(@Nullable Long maxlen, boolean nomkstream, boolean approximateTrimming) {
private XAddOptions(@Nullable Long maxlen, boolean nomkstream, boolean approximateTrimming,
@Nullable RecordId minId) {
this.maxlen = maxlen;
this.nomkstream = nomkstream;
this.approximateTrimming = approximateTrimming;
this.minId = minId;
}
/**
@@ -140,15 +143,6 @@ public interface RedisStreamCommands {
return NONE;
}
/**
* Limit the size of the stream to the given maximum number of elements.
*
* @return new instance of {@link XAddOptions}.
*/
public static XAddOptions maxlen(long maxlen) {
return new XAddOptions(maxlen, false, false);
}
/**
* Disable creation of stream if it does not already exist.
*
@@ -156,7 +150,7 @@ public interface RedisStreamCommands {
* @since 2.6
*/
public static XAddOptions makeNoStream() {
return new XAddOptions(null, true, false);
return new XAddOptions(null, true, false, null);
}
/**
@@ -167,7 +161,27 @@ public interface RedisStreamCommands {
* @since 2.6
*/
public static XAddOptions makeNoStream(boolean makeNoStream) {
return new XAddOptions(null, makeNoStream, false);
return new XAddOptions(null, makeNoStream, false, null);
}
/**
* Limit the size of the stream to the given maximum number of elements.
*
* @return new instance of {@link XAddOptions}.
*/
public static XAddOptions maxlen(long maxlen) {
return new XAddOptions(maxlen, false, false, null);
}
/**
* Apply {@code MINID} trimming strategy, that evicts entries with IDs lower than the one specified.
*
* @param minId the minimum record Id to retain.
* @return new instance of {@link XAddOptions}.
* @since 2.7
*/
public XAddOptions minId(RecordId minId) {
return new XAddOptions(maxlen, nomkstream, approximateTrimming, minId);
}
/**
@@ -176,14 +190,15 @@ public interface RedisStreamCommands {
* @return new instance of {@link XAddOptions}.
*/
public XAddOptions approximateTrimming(boolean approximateTrimming) {
return new XAddOptions(null, nomkstream, approximateTrimming);
return new XAddOptions(maxlen, nomkstream, approximateTrimming, minId);
}
/**
* @return {@literal true} if {@literal approximateTrimming} is set.
* @return {@literal true} if {@literal NOMKSTREAM} is set.
* @since 2.6
*/
public boolean isApproximateTrimming() {
return approximateTrimming;
public boolean isNoMkStream() {
return nomkstream;
}
/**
@@ -204,11 +219,27 @@ public interface RedisStreamCommands {
}
/**
* @return {@literal true} if {@literal NOMKSTREAM} is set.
* @since 2.6
* @return {@literal true} if {@literal approximateTrimming} is set.
*/
public boolean isNoMkStream() {
return nomkstream;
public boolean isApproximateTrimming() {
return approximateTrimming;
}
/**
* @return the minimum record Id to retain during trimming.
* @since 2.7
*/
@Nullable
public RecordId getMinId() {
return minId;
}
/**
* @return {@literal true} if {@literal MINID} is set.
* @since 2.7
*/
public boolean hasMinId() {
return minId != null;
}
@Override
@@ -216,20 +247,28 @@ public interface RedisStreamCommands {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
if (!(o instanceof XAddOptions)) {
return false;
}
XAddOptions that = (XAddOptions) o;
if (this.nomkstream != that.nomkstream) return false;
if (this.approximateTrimming != that.approximateTrimming) return false;
return ObjectUtils.nullSafeEquals(this.maxlen, that.maxlen);
if (nomkstream != that.nomkstream) {
return false;
}
if (approximateTrimming != that.approximateTrimming) {
return false;
}
if (!ObjectUtils.nullSafeEquals(maxlen, that.maxlen)) {
return false;
}
return ObjectUtils.nullSafeEquals(minId, that.minId);
}
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(this.maxlen);
result = 31 * result + ObjectUtils.nullSafeHashCode(this.nomkstream);
result = 31 * result + ObjectUtils.nullSafeHashCode(this.approximateTrimming);
int result = ObjectUtils.nullSafeHashCode(maxlen);
result = 31 * result + (nomkstream ? 1 : 0);
result = 31 * result + (approximateTrimming ? 1 : 0);
result = 31 * result + ObjectUtils.nullSafeHashCode(minId);
return result;
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.redis.connection.jedis;
import static org.springframework.data.redis.connection.jedis.StreamConverters.*;
import redis.clients.jedis.BuilderFactory;
import redis.clients.jedis.params.XAddParams;
import java.util.Arrays;
import java.util.Collections;
@@ -72,15 +73,11 @@ class JedisClusterStreamCommands implements RedisStreamCommands {
Assert.notNull(record, "Record must not be null!");
Assert.notNull(record.getStream(), "Stream must not be null!");
byte[] id = JedisConverters.toBytes(record.getId().getValue());
long maxLength = Long.MAX_VALUE;
if (options.hasMaxlen()) {
maxLength = options.getMaxlen();
}
XAddParams params = StreamConverters.toXAddParams(record, options);
try {
return RecordId.of(JedisConverters
.toString(connection.getCluster().xadd(record.getStream(), id, record.getValue(), maxLength, false)));
return RecordId
.of(JedisConverters.toString(connection.getCluster().xadd(record.getStream(), record.getValue(), params)));
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}

View File

@@ -71,20 +71,10 @@ class JedisStreamCommands implements RedisStreamCommands {
Assert.notNull(record, "Record must not be null!");
Assert.notNull(record.getStream(), "Stream must not be null!");
XAddParams xAddParams = new XAddParams();
xAddParams.id(record.getId().getValue());
if (options.hasMaxlen()) {
xAddParams.maxLen(options.getMaxlen());
}
if (options.isNoMkStream()) {
xAddParams.noMkStream();
}
if (options.isApproximateTrimming()) {
xAddParams.approximateTrimming();
}
XAddParams params = StreamConverters.toXAddParams(record, options);
return connection.invoke()
.from(BinaryJedis::xadd, MultiKeyPipelineBase::xadd, record.getStream(), record.getValue(), xAddParams)
.from(BinaryJedis::xadd, MultiKeyPipelineBase::xadd, record.getStream(), record.getValue(), params)
.get(it -> RecordId.of(JedisConverters.toString(it)));
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.redis.connection.jedis;
import redis.clients.jedis.StreamEntry;
import redis.clients.jedis.StreamEntryID;
import redis.clients.jedis.StreamPendingEntry;
import redis.clients.jedis.params.XAddParams;
import redis.clients.jedis.util.SafeEncoder;
import java.time.Duration;
@@ -30,8 +31,10 @@ import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.RedisStreamCommands;
import org.springframework.data.redis.connection.stream.ByteRecord;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.PendingMessage;
import org.springframework.data.redis.connection.stream.PendingMessages;
import org.springframework.data.redis.connection.stream.RecordId;
@@ -166,4 +169,28 @@ class StreamConverters {
return new PendingMessages(groupName, messages).withinRange(range);
}
static XAddParams toXAddParams(MapRecord<byte[], byte[], byte[]> record, RedisStreamCommands.XAddOptions options) {
XAddParams params = XAddParams.xAddParams();
params.id(record.getId().getValue());
if (options.hasMaxlen()) {
params.maxLen(options.getMaxlen());
}
if (options.hasMinId()) {
params.minId(options.getMinId().getValue());
}
if (options.isNoMkStream()) {
params.noMkStream();
}
if (options.isApproximateTrimming()) {
params.approximateTrimming();
}
return params;
}
}

View File

@@ -103,6 +103,9 @@ class LettuceReactiveStreamCommands implements ReactiveStreamCommands {
if (command.hasMaxlen()) {
args.maxlen(command.getMaxlen());
}
if (command.hasMinId()) {
args.minId(command.getMinId().getValue());
}
args.nomkstream(command.isNoMkStream());
args.approximateTrimming(command.isApproximateTrimming());
@@ -138,7 +141,6 @@ class LettuceReactiveStreamCommands implements ReactiveStreamCommands {
Flux<ByteBufferRecord> result = cmd.xclaim(command.getKey(), from, args, ids)
.map(it -> StreamRecords.newRecord().in(it.getStream()).withId(it.getId()).ofBuffer(it.getBody()));
return new CommandResponse<>(command, result);
}));
}

View File

@@ -83,6 +83,9 @@ class LettuceStreamCommands implements RedisStreamCommands {
if (options.hasMaxlen()) {
args.maxlen(options.getMaxlen());
}
if (options.hasMinId()) {
args.minId(options.getMinId().toString());
}
args.nomkstream(options.isNoMkStream());
args.approximateTrimming(options.isApproximateTrimming());

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.redis.connection.stream;
import java.util.Map;
/**
* A {@link Record} within the stream backed by a collection of {@link String} {@literal field/value} paris.
*
@@ -34,6 +36,17 @@ public interface StringRecord extends MapRecord<String, String, String> {
*/
StringRecord withStreamKey(String key);
/**
* Create a {@link StringRecord} from a {@link Map} of {@link String strings}.
*
* @param source must not be {@literal null}.
* @return new instance of {@link StringRecord}.
* @since 2.7
*/
static StringRecord of(Map<String, String> source) {
return StreamRecords.newRecord().ofStrings(source);
}
/**
* Convert a {@link MapRecord} of {@link String strings} into a {@link StringRecord}.
*