DATAREDIS-1084 - Add support for XPENDING.
Original pull request: #512.
This commit is contained in:
committed by
Mark Paluch
parent
18d4ea1db4
commit
ca01670c36
@@ -37,6 +37,8 @@ import org.springframework.data.redis.connection.convert.SetConverter;
|
||||
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.PendingMessages;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
|
||||
import org.springframework.data.redis.connection.stream.ReadOffset;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.data.redis.connection.stream.StreamOffset;
|
||||
@@ -3694,6 +3696,44 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
|
||||
return convertAndReturn(delegate.xLen(serialize(key)), identityConverter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection#xPending(java.lang.String, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessagesSummary xPending(String key, String groupName) {
|
||||
return convertAndReturn(delegate.xPending(serialize(key), groupName), identityConverter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection#xPending(java.lang.String, java.lang.String, java.lang.String, org.springframework.data.domain.Range, java.lang.Long)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessages xPending(String key, String groupName, String consumer,
|
||||
org.springframework.data.domain.Range<String> range, Long count) {
|
||||
return convertAndReturn(delegate.xPending(serialize(key), groupName, consumer, range, count), identityConverter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection#xPending(java.lang.String, java.lang.String, org.springframework.data.domain.Range, java.lang.Long)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessages xPending(String key, String groupName, org.springframework.data.domain.Range<String> range,
|
||||
Long count) {
|
||||
return convertAndReturn(delegate.xPending(serialize(key), groupName, range, count), identityConverter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection#xPending(java.lang.String, org.springframework.data.redis.connection.RedisStreamCommands.XPendingOptions)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessages xPending(String key, String groupName, XPendingOptions options) {
|
||||
return convertAndReturn(delegate.xPending(serialize(key), groupName, options), identityConverter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection#xRange(java.lang.String, org.springframework.data.domain.Range, org.springframework.data.redis.connection.RedisZSetCommands.Limit)
|
||||
@@ -3719,7 +3759,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
|
||||
*/
|
||||
@Override
|
||||
public List<StringRecord> xReadGroupAsString(Consumer consumer, StreamReadOptions readOptions,
|
||||
StreamOffset<String>... streams) {
|
||||
StreamOffset<String>... streams) {
|
||||
|
||||
return convertAndReturn(delegate.xReadGroup(consumer, readOptions, serialize(streams)),
|
||||
listByteMapRecordToStringMapRecordConverter);
|
||||
@@ -3808,6 +3848,24 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
|
||||
return delegate.xLen(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStreamCommands#xPending(byte[], java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessagesSummary xPending(byte[] key, String groupName) {
|
||||
return delegate.xPending(key, groupName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStreamCommands#xPending(byte[], java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessages xPending(byte[] key, String groupName, XPendingOptions options) {
|
||||
return delegate.xPending(key, groupName, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStreamCommands#xRange(byte[], org.springframework.data.domain.Range, org.springframework.data.redis.connection.RedisZSetCommands.Limit)
|
||||
@@ -3832,7 +3890,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
|
||||
*/
|
||||
@Override
|
||||
public List<ByteRecord> xReadGroup(Consumer consumer, StreamReadOptions readOptions,
|
||||
StreamOffset<byte[]>... streams) {
|
||||
StreamOffset<byte[]>... streams) {
|
||||
return delegate.xReadGroup(consumer, readOptions, streams);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ import org.springframework.data.geo.Point;
|
||||
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.PendingMessages;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
|
||||
import org.springframework.data.redis.connection.stream.ReadOffset;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.data.redis.connection.stream.StreamOffset;
|
||||
@@ -486,6 +488,20 @@ public interface DefaultedRedisConnection extends RedisConnection {
|
||||
return streamCommands().xLen(key);
|
||||
}
|
||||
|
||||
/** @deprecated in favor of {@link RedisConnection#streamCommands()}}. */
|
||||
@Override
|
||||
@Deprecated
|
||||
default PendingMessagesSummary xPending(byte[] key, String groupName) {
|
||||
return streamCommands().xPending(key, groupName);
|
||||
}
|
||||
|
||||
/** @deprecated in favor of {@link RedisConnection#streamCommands()}}. */
|
||||
@Override
|
||||
@Deprecated
|
||||
default PendingMessages xPending(byte[] key, String groupName, XPendingOptions options) {
|
||||
return streamCommands().xPending(key, groupName, options);
|
||||
}
|
||||
|
||||
/** @deprecated in favor of {@link RedisConnection#streamCommands()}}. */
|
||||
@Override
|
||||
@Deprecated
|
||||
@@ -525,7 +541,7 @@ public interface DefaultedRedisConnection extends RedisConnection {
|
||||
@Override
|
||||
@Deprecated
|
||||
default List<ByteRecord> xReadGroup(Consumer consumer, StreamReadOptions readOptions,
|
||||
StreamOffset<byte[]>... streams) {
|
||||
StreamOffset<byte[]>... streams) {
|
||||
return streamCommands().xReadGroup(consumer, readOptions, streams);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,13 @@ import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.connection.RedisStreamCommands.XPendingOptions;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
|
||||
import org.springframework.data.redis.connection.stream.ByteBufferRecord;
|
||||
import org.springframework.data.redis.connection.stream.Consumer;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessage;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessages;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
|
||||
import org.springframework.data.redis.connection.stream.ReadOffset;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.data.redis.connection.stream.StreamOffset;
|
||||
@@ -40,6 +44,7 @@ import org.springframework.data.redis.connection.stream.StreamReadOptions;
|
||||
import org.springframework.data.redis.connection.stream.StreamRecords;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Stream-specific Redis commands executed using reactive infrastructure.
|
||||
@@ -410,6 +415,225 @@ public interface ReactiveStreamCommands {
|
||||
*/
|
||||
Flux<NumericResponse<KeyCommand, Long>> xLen(Publisher<KeyCommand> commands);
|
||||
|
||||
/**
|
||||
* Obtain the {@link PendingMessagesSummary} for a given {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @return {@link Mono} emitting a summary of pending messages within the given {@literal consumer group}.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
default Mono<PendingMessagesSummary> xPending(ByteBuffer key, String groupName) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(groupName, "GroupName must not be null!");
|
||||
|
||||
return xPendingSummary(Mono.just(new PendingRecordsCommand(key, groupName, null, Range.unbounded(), null))).next()
|
||||
.map(CommandResponse::getOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link PendingMessagesSummary} for a given {@literal consumer group}.
|
||||
*
|
||||
* @param commands must not be {@literal null}..
|
||||
* @return {@link Flux} emitting a summary of pending messages within the given {@literal consumer group} one by one.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
Flux<CommandResponse<PendingRecordsCommand, PendingMessagesSummary>> xPendingSummary(
|
||||
Publisher<PendingRecordsCommand> commands);
|
||||
|
||||
/**
|
||||
* Obtained detailed information about all pending messages for a given {@link Consumer}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param consumer the consumer to fetch {@link PendingMessages} for. Must not be {@literal null}.
|
||||
* @return {@link Mono} emitting pending messages for the given {@link Consumer}.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
default Mono<PendingMessages> xPending(ByteBuffer key, Consumer consumer) {
|
||||
return xPending(key, consumer.getGroup(), consumer.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtained detailed information about all pending messages for a given {@literal consumer}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param consumerName the consumer to fetch {@link PendingMessages} for. Must not be {@literal null}.
|
||||
* @return {@link Mono} emitting pending messages for the given {@link Consumer}.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
default Mono<PendingMessages> xPending(ByteBuffer key, String groupName, String consumerName) {
|
||||
return xPending(Mono.just(new PendingRecordsCommand(key, groupName, consumerName, Range.unbounded(), null))).next()
|
||||
.map(CommandResponse::getOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link Range} within a
|
||||
* {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return {@link Mono} emitting pending messages for the given {@literal consumer group}. transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
default Mono<PendingMessages> xPending(ByteBuffer key, String groupName, Range<?> range, Long count) {
|
||||
return xPending(Mono.just(new PendingRecordsCommand(key, groupName, null, range, count))).next()
|
||||
.map(CommandResponse::getOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link Range} and
|
||||
* {@link Consumer} within a {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param consumer the name of the {@link Consumer}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return {@link Mono} emitting pending messages for the given {@link Consumer}.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
default Mono<PendingMessages> xPending(ByteBuffer key, Consumer consumer, Range<?> range, Long count) {
|
||||
return xPending(key, consumer.getGroup(), consumer.getName(), range, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link Range} and
|
||||
* {@literal consumer} within a {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param consumerName the name of the {@literal consumer}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return {@link Mono} emitting pending messages for the given {@literal consumer} in given
|
||||
* {@literal consumer group}.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
default Mono<PendingMessages> xPending(ByteBuffer key, String groupName, String consumerName, Range<?> range,
|
||||
Long count) {
|
||||
return xPending(Mono.just(new PendingRecordsCommand(key, groupName, consumerName, range, count))).next()
|
||||
.map(CommandResponse::getOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} applying given {@link XPendingOptions
|
||||
* options}.
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return {@link Flux} emitting pending messages matching given criteria.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
Flux<CommandResponse<PendingRecordsCommand, PendingMessages>> xPending(Publisher<PendingRecordsCommand> commands);
|
||||
|
||||
/**
|
||||
* Value Object holding parameters for obtaining pending messages.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
*/
|
||||
class PendingRecordsCommand extends KeyCommand {
|
||||
|
||||
private final String groupName;
|
||||
private final @Nullable String consumerName;
|
||||
private final Range<?> range;
|
||||
private final @Nullable Long count;
|
||||
|
||||
private PendingRecordsCommand(ByteBuffer key, String groupName, @Nullable String consumerName, Range<?> range,
|
||||
@Nullable Long count) {
|
||||
|
||||
super(key);
|
||||
|
||||
this.groupName = groupName;
|
||||
this.consumerName = consumerName;
|
||||
this.range = range;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new unbounded {@link PendingRecordsCommand}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @return new instance of {@link PendingRecordsCommand}.
|
||||
*/
|
||||
static PendingRecordsCommand pending(ByteBuffer key, String groupName) {
|
||||
return new PendingRecordsCommand(key, groupName, null, Range.unbounded(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link PendingRecordsCommand} with given {@link Range} and limit.
|
||||
*
|
||||
* @return new instance of {@link XPendingOptions}.
|
||||
*/
|
||||
public PendingRecordsCommand range(Range<String> range, Long count) {
|
||||
return new PendingRecordsCommand(getKey(), groupName, consumerName, range, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append given consumer.
|
||||
*
|
||||
* @param consumerName must not be {@literal null}.
|
||||
* @return new instance of {@link PendingRecordsCommand}.
|
||||
*/
|
||||
public PendingRecordsCommand consumer(String consumerName) {
|
||||
return new PendingRecordsCommand(getKey(), groupName, consumerName, range, count);
|
||||
}
|
||||
|
||||
public String getGroupName() {
|
||||
return groupName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public String getConsumerName() {
|
||||
return consumerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Range<?> getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public Long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if a consumer name is present.
|
||||
*/
|
||||
public boolean hasConsumer() {
|
||||
return StringUtils.hasText(consumerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} count is set.
|
||||
*/
|
||||
public boolean isLimited() {
|
||||
return count != null && count > -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code XRANGE}/{@code XREVRANGE} command parameters.
|
||||
*
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
|
||||
import org.springframework.data.redis.connection.stream.*;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Stream-specific Redis commands.
|
||||
@@ -162,6 +163,212 @@ public interface RedisStreamCommands {
|
||||
@Nullable
|
||||
Long xLen(byte[] key);
|
||||
|
||||
/**
|
||||
* Obtain the {@link PendingMessagesSummary} for a given {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @return a summary of pending messages within the given {@literal consumer group} or {@literal null} when used in
|
||||
* pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
PendingMessagesSummary xPending(byte[] key, String groupName);
|
||||
|
||||
/**
|
||||
* Obtained detailed information about all pending messages for a given {@link Consumer}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param consumer the consumer to fetch {@link PendingMessages} for. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@link Consumer} or {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
default PendingMessages xPending(byte[] key, Consumer consumer) {
|
||||
return xPending(key, consumer.getGroup(), consumer.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtained detailed information about all pending messages for a given {@literal consumer}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param consumerName the consumer to fetch {@link PendingMessages} for. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@link Consumer} or {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
default PendingMessages xPending(byte[] key, String groupName, String consumerName) {
|
||||
return xPending(key, groupName, XPendingOptions.unbounded().consumer(consumerName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link Range} within a
|
||||
* {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@literal consumer group} or {@literal null} when used in pipeline /
|
||||
* transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
default PendingMessages xPending(byte[] key, String groupName, Range<?> range, Long count) {
|
||||
return xPending(key, groupName, XPendingOptions.range(range, count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link Range} and
|
||||
* {@link Consumer} within a {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param consumer the name of the {@link Consumer}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@link Consumer} or {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
default PendingMessages xPending(byte[] key, Consumer consumer, Range<?> range, Long count) {
|
||||
return xPending(key, consumer.getGroup(), consumer.getName(), range, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link Range} and
|
||||
* {@literal consumer} within a {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param consumerName the name of the {@literal consumer}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@literal consumer} in given {@literal consumer group} or {@literal null}
|
||||
* when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
default PendingMessages xPending(byte[] key, String groupName, String consumerName, Range<?> range, Long count) {
|
||||
return xPending(key, groupName, XPendingOptions.range(range, count).consumer(consumerName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} applying given {@link XPendingOptions
|
||||
* options}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param options the options containing {@literal range}, {@literal consumer} and {@literal count}. Must not be
|
||||
* {@literal null}.
|
||||
* @return pending messages matching given criteria or {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
PendingMessages xPending(byte[] key, String groupName, XPendingOptions options);
|
||||
|
||||
/**
|
||||
* Value Object holding parameters for obtaining pending messages.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
*/
|
||||
class XPendingOptions {
|
||||
|
||||
private final @Nullable String consumerName;
|
||||
private final Range<?> range;
|
||||
private final @Nullable Long count;
|
||||
|
||||
private XPendingOptions(@Nullable String consumerName, Range<?> range, @Nullable Long count) {
|
||||
|
||||
this.range = range;
|
||||
this.count = count;
|
||||
this.consumerName = consumerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link XPendingOptions} with an unbounded {@link Range} ({@literal - +}).
|
||||
*
|
||||
* @return new instance of {@link XPendingOptions}.
|
||||
*/
|
||||
public static XPendingOptions unbounded() {
|
||||
return new XPendingOptions(null, Range.unbounded(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link XPendingOptions} with an unbounded {@link Range} ({@literal - +}).
|
||||
*
|
||||
* @param count the max number of messages to return. Must not be {@literal null}.
|
||||
* @return new instance of {@link XPendingOptions}.
|
||||
*/
|
||||
public static XPendingOptions unbounded(Long count) {
|
||||
return new XPendingOptions(null, Range.unbounded(), count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link XPendingOptions} with given {@link Range} and limit.
|
||||
*
|
||||
* @return new instance of {@link XPendingOptions}.
|
||||
*/
|
||||
public static XPendingOptions range(Range<?> range, Long count) {
|
||||
return new XPendingOptions(null, range, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append given consumer.
|
||||
*
|
||||
* @param consumerName must not be {@literal null}.
|
||||
* @return new instance of {@link XPendingOptions}.
|
||||
*/
|
||||
public XPendingOptions consumer(String consumerName) {
|
||||
return new XPendingOptions(consumerName, range, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Range<?> getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public Long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
public String getConsumerName() {
|
||||
return consumerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if a consumer name is present.
|
||||
*/
|
||||
public boolean hasConsumer() {
|
||||
return StringUtils.hasText(consumerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} count is set.
|
||||
*/
|
||||
public boolean isLimited() {
|
||||
return count != null && count > -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all {@link ByteRecord records} within a specific {@link Range} from the stream stored at {@literal key}.
|
||||
* <br />
|
||||
|
||||
@@ -23,12 +23,16 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
import org.springframework.data.geo.Metric;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.redis.connection.stream.Consumer;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessage;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessages;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
|
||||
import org.springframework.data.redis.connection.stream.ReadOffset;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.data.redis.connection.stream.StreamOffset;
|
||||
@@ -2071,6 +2075,68 @@ public interface StringRedisConnection extends RedisConnection {
|
||||
@Nullable
|
||||
Long xLen(String key);
|
||||
|
||||
/**
|
||||
* Obtain the {@link PendingMessagesSummary} for a given {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @return a summary of pending messages within the given {@literal consumer group} or {@literal null} when used in
|
||||
* pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
PendingMessagesSummary xPending(String key, String groupName);
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link org.springframework.data.domain.Range} within a
|
||||
* {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param consumerName the name of the {@literal consumer}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@literal consumer group} or {@literal null} when used in pipeline /
|
||||
* transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
PendingMessages xPending(String key, String groupName, String consumerName, org.springframework.data.domain.Range<String> range, Long count);
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link org.springframework.data.domain.Range} within a
|
||||
* {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@literal consumer group} or {@literal null} when used in pipeline /
|
||||
* transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
PendingMessages xPending(String key, String groupName, org.springframework.data.domain.Range<String> range, Long count);
|
||||
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} applying given {@link XPendingOptions
|
||||
* options}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param groupName the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param options the options containing {@literal range}, {@literal consumer} and {@literal count}. Must not be
|
||||
* {@literal null}.
|
||||
* @return pending messages matching given criteria or {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
PendingMessages xPending(String key, String groupName, XPendingOptions options);
|
||||
|
||||
/**
|
||||
* Read records from a stream within a specific {@link Range}.
|
||||
*
|
||||
|
||||
@@ -18,11 +18,16 @@ package org.springframework.data.redis.connection.lettuce;
|
||||
import io.lettuce.core.*;
|
||||
import io.lettuce.core.cluster.models.partitions.Partitions;
|
||||
import io.lettuce.core.cluster.models.partitions.RedisClusterNode.NodeFlag;
|
||||
import io.lettuce.core.models.stream.PendingMessage;
|
||||
import io.lettuce.core.models.stream.PendingMessages;
|
||||
import io.lettuce.core.models.stream.PendingParser;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -64,6 +69,9 @@ import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.connection.convert.ListConverter;
|
||||
import org.springframework.data.redis.connection.convert.LongToBooleanConverter;
|
||||
import org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter;
|
||||
import org.springframework.data.redis.connection.stream.Consumer;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.data.redis.core.types.RedisClientInfo;
|
||||
@@ -71,6 +79,7 @@ import org.springframework.data.redis.util.ByteUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.NumberUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -108,6 +117,8 @@ abstract public class LettuceConverters extends Converters {
|
||||
private static final Converter<KeyValue<Object, Object>, Object> KEY_VALUE_UNWRAPPER;
|
||||
private static final ListConverter<KeyValue<Object, Object>, Object> KEY_VALUE_LIST_UNWRAPPER;
|
||||
private static final Converter<TransactionResult, List<Object>> TRANSACTION_RESULT_UNWRAPPER;
|
||||
private static final BiFunction<List<Object>, String, org.springframework.data.redis.connection.stream.PendingMessages> PENDING_MESSAGES_CONVERTER;
|
||||
private static final BiFunction<List<Object>, String, PendingMessagesSummary> PENDING_MESSAGES_SUMMARY_CONVERTER;
|
||||
|
||||
public static final byte[] PLUS_BYTES;
|
||||
public static final byte[] MINUS_BYTES;
|
||||
@@ -308,6 +319,70 @@ abstract public class LettuceConverters extends Converters {
|
||||
KEY_VALUE_LIST_UNWRAPPER = new ListConverter<>(KEY_VALUE_UNWRAPPER);
|
||||
|
||||
TRANSACTION_RESULT_UNWRAPPER = transactionResult -> transactionResult.stream().collect(Collectors.toList());
|
||||
|
||||
PENDING_MESSAGES_CONVERTER = (source, groupName) -> {
|
||||
|
||||
List<Object> target = source.stream().map(LettuceConverters::preConvertNativeValues).collect(Collectors.toList());
|
||||
List<PendingMessage> pendingMessages = PendingParser.parseRange(target);
|
||||
|
||||
List<org.springframework.data.redis.connection.stream.PendingMessage> messages = pendingMessages.stream()
|
||||
.map(it -> {
|
||||
|
||||
RecordId id = RecordId.of(it.getId());
|
||||
Consumer consumer = Consumer.from(groupName, it.getConsumer());
|
||||
|
||||
return new org.springframework.data.redis.connection.stream.PendingMessage(id, consumer,
|
||||
Duration.ofMillis(it.getMsSinceLastDelivery()), it.getRedeliveryCount());
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return new org.springframework.data.redis.connection.stream.PendingMessages(groupName, messages);
|
||||
|
||||
};
|
||||
|
||||
PENDING_MESSAGES_SUMMARY_CONVERTER = (source, groupName) -> {
|
||||
|
||||
List<Object> target = source.stream().map(LettuceConverters::preConvertNativeValues).collect(Collectors.toList());
|
||||
|
||||
PendingMessages pendingMessages = PendingParser.parse(target);
|
||||
org.springframework.data.domain.Range<String> range = org.springframework.data.domain.Range.open(
|
||||
pendingMessages.getMessageIds().getLower().getValue(), pendingMessages.getMessageIds().getUpper().getValue());
|
||||
|
||||
return new PendingMessagesSummary(groupName, pendingMessages.getCount(), range,
|
||||
pendingMessages.getConsumerMessageCount());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to convert values into the correct target type since lettuce will give us {@link ByteBuffer} or arrays but
|
||||
* the parser requires us to have them as {@link String} or numeric values. Oh and {@literal null} values aren't real
|
||||
* good citizens as well, so we make them empty strings instead - see it works - somehow ;P
|
||||
*
|
||||
* @param value dont't get me started om this.
|
||||
* @return preconverted values that Lettuce parsers are able to understand \ö/.
|
||||
*/
|
||||
private static Object preConvertNativeValues(Object value) {
|
||||
|
||||
if (value instanceof ByteBuffer || value instanceof byte[]) {
|
||||
|
||||
byte[] targetArray = value instanceof ByteBuffer ? ByteUtils.getBytes((ByteBuffer) value) : (byte[]) value;
|
||||
String tmp = toString(targetArray);
|
||||
|
||||
try {
|
||||
return NumberUtils.parseNumber(tmp, Long.class);
|
||||
} catch (NumberFormatException e) {
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
if (value instanceof List) {
|
||||
List<Object> targetList = new ArrayList<>();
|
||||
for (Object it : (List) value) {
|
||||
targetList.add(preConvertNativeValues(it));
|
||||
}
|
||||
return targetList;
|
||||
}
|
||||
|
||||
return value != null ? value : "";
|
||||
}
|
||||
|
||||
public static List<Tuple> toTuple(List<byte[]> list) {
|
||||
@@ -1042,6 +1117,32 @@ abstract public class LettuceConverters extends Converters {
|
||||
return getUpperBound(range).orElse(INDEXED_RANGE_END);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the raw lettuce xpending result to {@link PendingMessages}.
|
||||
*
|
||||
* @param groupName the group name
|
||||
* @param range the range of messages requested
|
||||
* @param source the raw lettuce response.
|
||||
* @return
|
||||
* @since 2.3
|
||||
*/
|
||||
static org.springframework.data.redis.connection.stream.PendingMessages toPendingMessages(String groupName,
|
||||
org.springframework.data.domain.Range<?> range, List<Object> source) {
|
||||
return PENDING_MESSAGES_CONVERTER.apply(source, groupName).withinRange(range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the raw lettuce xpending result to {@link PendingMessagesSummary}.
|
||||
*
|
||||
* @param groupName
|
||||
* @param source the raw lettuce response.
|
||||
* @return
|
||||
* @since 2.3
|
||||
*/
|
||||
static PendingMessagesSummary toPendingMessagesInfo(String groupName, List<Object> source) {
|
||||
return PENDING_MESSAGES_SUMMARY_CONVERTER.apply(source, groupName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 1.8
|
||||
|
||||
@@ -22,6 +22,7 @@ import io.lettuce.core.cluster.api.reactive.RedisClusterReactiveCommands;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
@@ -34,6 +35,8 @@ import org.springframework.data.redis.connection.ReactiveStreamCommands;
|
||||
import org.springframework.data.redis.connection.ReactiveStreamCommands.GroupCommand.GroupCommandAction;
|
||||
import org.springframework.data.redis.connection.stream.ByteBufferRecord;
|
||||
import org.springframework.data.redis.connection.stream.Consumer;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessages;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.data.redis.connection.stream.StreamReadOptions;
|
||||
import org.springframework.data.redis.connection.stream.StreamRecords;
|
||||
@@ -172,6 +175,67 @@ class LettuceReactiveStreamCommands implements ReactiveStreamCommands {
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveStreamCommands#xPendingSummary(org.reactivestreams.Publisher)
|
||||
*/
|
||||
@Override
|
||||
public Flux<CommandResponse<PendingRecordsCommand, PendingMessagesSummary>> xPendingSummary(
|
||||
Publisher<PendingRecordsCommand> commands) {
|
||||
|
||||
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
|
||||
|
||||
Assert.notNull(command.getKey(), "Key must not be null!");
|
||||
return cmd.xpending(command.getKey(), ByteUtils.getByteBuffer(command.getGroupName())).collectList().map(it -> {
|
||||
|
||||
// begin
|
||||
// {* hacking *}
|
||||
// while (https://github.com/lettuce-io/lettuce-core/issues/1229 != resolved) begin
|
||||
|
||||
ArrayList<Object> target = new ArrayList<>(it);
|
||||
if (target.size() == 2 && target.get(1) instanceof List) {
|
||||
target.add(1, null);
|
||||
target.add(1, null);
|
||||
}
|
||||
while (target.size() < 4) {
|
||||
target.add(null);
|
||||
}
|
||||
|
||||
// end.
|
||||
// end.
|
||||
|
||||
return LettuceConverters.toPendingMessagesInfo(command.getGroupName(), target);
|
||||
}).map(value -> new CommandResponse<PendingRecordsCommand, PendingMessagesSummary>(command, value));
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveStreamCommands#xPending(org.reactivestreams.Publisher)
|
||||
*/
|
||||
@Override
|
||||
public Flux<CommandResponse<PendingRecordsCommand, PendingMessages>> xPending(
|
||||
Publisher<PendingRecordsCommand> commands) {
|
||||
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
|
||||
|
||||
Assert.notNull(command.getKey(), "Key must not be null!");
|
||||
|
||||
ByteBuffer groupName = ByteUtils.getByteBuffer(command.getGroupName());
|
||||
io.lettuce.core.Range<String> range = RangeConverter.toRangeWithDefault(command.getRange(), "-", "+");
|
||||
io.lettuce.core.Limit limit = command.isLimited() ? io.lettuce.core.Limit.from(command.getCount())
|
||||
: io.lettuce.core.Limit.from(Long.MAX_VALUE);
|
||||
|
||||
Flux<Object> publisher = command.hasConsumer() ? cmd.xpending(command.getKey(),
|
||||
io.lettuce.core.Consumer.from(groupName, ByteUtils.getByteBuffer(command.getConsumerName())), range, limit)
|
||||
: cmd.xpending(command.getKey(), groupName, range, limit);
|
||||
|
||||
return publisher.collectList().map(it -> {
|
||||
|
||||
return LettuceConverters.toPendingMessages(command.getGroupName(), command.getRange(), it);
|
||||
}).map(value -> new CommandResponse<PendingRecordsCommand, PendingMessages>(command, value));
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveStreamCommands#xRange(org.reactivestreams.Publisher)
|
||||
|
||||
@@ -28,11 +28,13 @@ import java.util.function.Function;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.redis.connection.stream.ByteRecord;
|
||||
import org.springframework.data.redis.connection.stream.MapRecord;
|
||||
import org.springframework.data.redis.connection.RedisStreamCommands;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
|
||||
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.PendingMessages;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
|
||||
import org.springframework.data.redis.connection.stream.ReadOffset;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.data.redis.connection.stream.StreamOffset;
|
||||
@@ -242,6 +244,78 @@ class LettuceStreamCommands implements RedisStreamCommands {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStreamCommands#xPending(byte[], java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessagesSummary xPending(byte[] key, String groupName) {
|
||||
|
||||
byte[] group = LettuceConverters.toBytes(groupName);
|
||||
try {
|
||||
if (isPipelined()) {
|
||||
pipeline(connection.newLettuceResult(getAsyncConnection().xpending(key, group),
|
||||
it -> LettuceConverters.toPendingMessagesInfo(groupName, it)));
|
||||
return null;
|
||||
}
|
||||
if (isQueueing()) {
|
||||
transaction(connection.newLettuceResult(getAsyncConnection().xpending(key, group),
|
||||
it -> LettuceConverters.toPendingMessagesInfo(groupName, it)));
|
||||
return null;
|
||||
}
|
||||
return LettuceConverters.toPendingMessagesInfo(groupName, getConnection().xpending(key, group));
|
||||
} catch (Exception ex) {
|
||||
throw convertLettuceAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStreamCommands#xPending(byte[], java.lang.String, org.springframework.data.redis.connection.RedisStreamCommands.XPendingOptions)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessages xPending(byte[] key, String groupName, XPendingOptions options) {
|
||||
|
||||
byte[] group = LettuceConverters.toBytes(groupName);
|
||||
io.lettuce.core.Range<String> range = RangeConverter.toRangeWithDefault(options.getRange(), "-", "+");
|
||||
io.lettuce.core.Limit limit = options.isLimited() ? io.lettuce.core.Limit.from(options.getCount())
|
||||
: io.lettuce.core.Limit.from(Long.MAX_VALUE);
|
||||
|
||||
try {
|
||||
if (!options.hasConsumer()) {
|
||||
if (isPipelined()) {
|
||||
pipeline(connection.newLettuceResult(getAsyncConnection().xpending(key, group, range, limit),
|
||||
it -> LettuceConverters.toPendingMessages(groupName, options.getRange(), (List<Object>) it)));
|
||||
return null;
|
||||
}
|
||||
if (isQueueing()) {
|
||||
transaction(connection.newLettuceResult(getAsyncConnection().xpending(key, group, range, limit),
|
||||
it -> LettuceConverters.toPendingMessages(groupName, options.getRange(), (List<Object>) it)));
|
||||
return null;
|
||||
}
|
||||
return LettuceConverters.toPendingMessages(groupName, options.getRange(),
|
||||
getConnection().xpending(key, group, range, limit));
|
||||
} else {
|
||||
if (isPipelined()) {
|
||||
pipeline(connection.newLettuceResult(getAsyncConnection().xpending(key,
|
||||
io.lettuce.core.Consumer.from(group, LettuceConverters.toBytes(options.getConsumerName())), range, limit),
|
||||
it -> LettuceConverters.toPendingMessages(groupName, options.getRange(), (List<Object>) it)));
|
||||
return null;
|
||||
}
|
||||
if (isQueueing()) {
|
||||
transaction(connection.newLettuceResult(getAsyncConnection().xpending(key,
|
||||
io.lettuce.core.Consumer.from(group, LettuceConverters.toBytes(options.getConsumerName())), range, limit),
|
||||
it -> LettuceConverters.toPendingMessages(groupName, options.getRange(), (List<Object>) it)));
|
||||
return null;
|
||||
}
|
||||
return LettuceConverters.toPendingMessages(groupName, options.getRange(), getConnection().xpending(key,
|
||||
io.lettuce.core.Consumer.from(group, LettuceConverters.toBytes(options.getConsumerName())), range, limit));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw convertLettuceAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStreamCommands#xRange(byte[], org.springframework.data.domain.Range, org.springframework.data.redis.connection.RedisZSetCommands.Limit)
|
||||
|
||||
@@ -20,9 +20,11 @@ import io.lettuce.core.Range.Boundary;
|
||||
import io.lettuce.core.codec.StringCodec;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.domain.Range.Bound;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -36,31 +38,64 @@ class RangeConverter {
|
||||
return toRange(range, StringCodec.UTF8::encodeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param range the source {@link org.springframework.data.domain.Range} to convert.
|
||||
* @param lowerDefault the lower default to use if {@link org.springframework.data.domain.Range#getLowerBound()} is
|
||||
* not {@link Bound#isBounded() bounded}.
|
||||
* @param upperDefault the upper default to use if {@link org.springframework.data.domain.Range#getUpperBound()} is
|
||||
* not {@link Bound#isBounded() bounded}.
|
||||
* @param <T>
|
||||
* @return new instance of {@link Range}.
|
||||
* @since 2.3
|
||||
*/
|
||||
static <T> Range<T> toRangeWithDefault(org.springframework.data.domain.Range<?> range, @Nullable T lowerDefault,
|
||||
@Nullable T upperDefault) {
|
||||
return toRangeWithDefault(range, lowerDefault, upperDefault, StringCodec.UTF8::encodeValue);
|
||||
}
|
||||
|
||||
static <T> Range<T> toRange(org.springframework.data.domain.Range<?> range,
|
||||
Function<String, ? extends Object> stringEncoder) {
|
||||
return Range.from(lowerBoundArgOf(range, stringEncoder), upperBoundArgOf(range, stringEncoder));
|
||||
return toRangeWithDefault(range, null, null, stringEncoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param range the source {@link org.springframework.data.domain.Range} to convert.
|
||||
* @param lowerDefault the lower default to use if {@link org.springframework.data.domain.Range#getLowerBound()} is
|
||||
* not {@link Bound#isBounded() bounded}.
|
||||
* @param upperDefault the upper default to use if {@link org.springframework.data.domain.Range#getUpperBound()} is
|
||||
* not {@link Bound#isBounded() bounded}.
|
||||
* @param stringEncoder the encoder to use.
|
||||
* @param <T>
|
||||
* @return new instance of {@link Range}.
|
||||
* @since 2.3
|
||||
*/
|
||||
static <T> Range<T> toRangeWithDefault(org.springframework.data.domain.Range<?> range, @Nullable T lowerDefault,
|
||||
@Nullable T upperDefault, Function<String, ? extends Object> stringEncoder) {
|
||||
|
||||
return Range.from(lowerBoundArgOf(range, lowerDefault, stringEncoder),
|
||||
upperBoundArgOf(range, upperDefault, stringEncoder));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> Boundary<T> lowerBoundArgOf(org.springframework.data.domain.Range<?> range,
|
||||
Function<String, ? extends Object> stringEncoder) {
|
||||
return (Boundary<T>) rangeToBoundArgumentConverter(false, stringEncoder).convert(range);
|
||||
@Nullable T lowerDefault, Function<String, ? extends Object> stringEncoder) {
|
||||
return (Boundary<T>) rangeToBoundArgumentConverter(false, stringEncoder).apply(range, lowerDefault);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> Boundary<T> upperBoundArgOf(org.springframework.data.domain.Range<?> range,
|
||||
Function<String, ? extends Object> stringEncoder) {
|
||||
return (Boundary<T>) rangeToBoundArgumentConverter(true, stringEncoder).convert(range);
|
||||
@Nullable T upperDefault, Function<String, ? extends Object> stringEncoder) {
|
||||
return (Boundary<T>) rangeToBoundArgumentConverter(true, stringEncoder).apply(range, upperDefault);
|
||||
}
|
||||
|
||||
private static Converter<org.springframework.data.domain.Range<?>, Boundary<?>> rangeToBoundArgumentConverter(
|
||||
private static BiFunction<org.springframework.data.domain.Range, Object, Boundary<?>> rangeToBoundArgumentConverter(
|
||||
boolean upper, Function<String, ? extends Object> stringEncoder) {
|
||||
|
||||
return (source) -> {
|
||||
return (source, defaultValue) -> {
|
||||
|
||||
Boolean inclusive = upper ? source.getUpperBound().isInclusive() : source.getLowerBound().isInclusive();
|
||||
Object value = upper ? source.getUpperBound().getValue().orElse(null)
|
||||
: source.getLowerBound().getValue().orElse(null);
|
||||
Object value = upper ? source.getUpperBound().getValue().orElse(defaultValue)
|
||||
: source.getLowerBound().getValue().orElse(defaultValue);
|
||||
|
||||
if (value instanceof Number) {
|
||||
return inclusive ? Boundary.including((Number) value) : Boundary.excluding((Number) value);
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.connection.stream;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Value object representing a single pending message containing its {@literal ID}, the {@literal consumer} that fetched
|
||||
* the message and has still to acknowledge it, the time elapsed since the messages last delivery and the the total
|
||||
* number of times delivered.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
*/
|
||||
public class PendingMessage {
|
||||
|
||||
private final RecordId id;
|
||||
private final Consumer consumer;
|
||||
private final Duration elapsedTimeSinceLastDelivery;
|
||||
private final Long totalDeliveryCount;
|
||||
|
||||
public PendingMessage(RecordId id, Consumer consumer, Duration elapsedTimeSinceLastDelivery,
|
||||
Long totalDeliveryCount) {
|
||||
|
||||
this.id = id;
|
||||
this.consumer = consumer;
|
||||
this.elapsedTimeSinceLastDelivery = elapsedTimeSinceLastDelivery;
|
||||
this.totalDeliveryCount = totalDeliveryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the message id.
|
||||
*/
|
||||
public RecordId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the message id as {@link String}.
|
||||
*/
|
||||
public String getStringId() {
|
||||
return id.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Consumer} to acknowledge the message.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Consumer getConsumer() {
|
||||
return consumer;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@literal consumer name} to acknowledge the message.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public String getConsumerName() {
|
||||
return consumer.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@literal consumer group}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public String getGroupName() {
|
||||
return consumer.getGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time elapsed since the messages last delivery to the {@link #getConsumer() consumer}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Duration getElapsedTimeSinceLastDelivery() {
|
||||
return elapsedTimeSinceLastDelivery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the milliseconds elapsed since the messages last delivery to the {@link #getConsumer() consumer}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Long getElapsedTimeSinceLastDeliveryMS() {
|
||||
return elapsedTimeSinceLastDelivery.toMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of times the messages has been delivered to the {@link #getConsumer() consumer}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Long getTotalDeliveryCount() {
|
||||
return totalDeliveryCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PendingMessage{" + "id=" + id + ", consumer=" + consumer + ", elapsedTimeSinceLastDeliveryMS="
|
||||
+ elapsedTimeSinceLastDelivery.toMillis() + ", totalDeliveryCount=" + totalDeliveryCount + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.connection.stream;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.util.Streamable;
|
||||
|
||||
/**
|
||||
* Value object holding detailed information about pending messages in {@literal consumer group} for a given
|
||||
* {@link org.springframework.data.redis.connection.SortParameters.Range} and offset.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
*/
|
||||
public class PendingMessages implements Streamable<PendingMessage> {
|
||||
|
||||
private final String groupName;
|
||||
private final Range<?> range;
|
||||
private final List<PendingMessage> pendingMessages;
|
||||
|
||||
public PendingMessages(String groupName, List<PendingMessage> pendingMessages) {
|
||||
this(groupName, Range.unbounded(), pendingMessages);
|
||||
}
|
||||
|
||||
public PendingMessages(String groupName, Range<?> range, List<PendingMessage> pendingMessages) {
|
||||
|
||||
this.groupName = groupName;
|
||||
this.range = range;
|
||||
this.pendingMessages = pendingMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the range to the current {@link PendingMessages}.
|
||||
*
|
||||
* @param range must not be {@literal null}.
|
||||
* @return new instance of {@link PendingMessages}.
|
||||
*/
|
||||
public PendingMessages withinRange(Range<?> range) {
|
||||
return new PendingMessages(groupName, range, pendingMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@literal consumer group} name.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public String getGroupName() {
|
||||
return groupName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Range} pending messages have been loaded.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Range<?> getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if no messages pending within range.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return pendingMessages.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of pending messages in range.
|
||||
*/
|
||||
public int size() {
|
||||
return pendingMessages.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link PendingMessage} at the given position.
|
||||
*
|
||||
* @param index
|
||||
* @return the {@link PendingMessage} a the given index.
|
||||
* @throws IndexOutOfBoundsException if the index is out of range.
|
||||
*/
|
||||
public PendingMessage get(int index) {
|
||||
return pendingMessages.get(index);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<PendingMessage> iterator() {
|
||||
return pendingMessages.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PendingMessages{" + "groupName='" + groupName + '\'' + ", range=" + range + ", pendingMessages="
|
||||
+ pendingMessages + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.connection.stream;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
|
||||
/**
|
||||
* Value Object summarizing pending messages in a {@literal consumer group}. It contains the total number and ID range
|
||||
* of pending messages for this consumer group, as well as a collection of total pending messages per consumer.
|
||||
*
|
||||
* @since 2.3
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class PendingMessagesSummary {
|
||||
|
||||
private final String groupName;
|
||||
private final Long totalPendingMessages;
|
||||
private final Range<String> idRange;
|
||||
private final Map<String, Long> pendingMessagesPerConsumer;
|
||||
|
||||
public PendingMessagesSummary(String groupName, Long totalPendingMessages, Range<String> idRange,
|
||||
Map<String, Long> pendingMessagesPerConsumer) {
|
||||
|
||||
this.groupName = groupName;
|
||||
this.totalPendingMessages = totalPendingMessages;
|
||||
this.idRange = idRange;
|
||||
this.pendingMessagesPerConsumer = pendingMessagesPerConsumer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range between the smallest and greatest ID among the pending messages.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Range<String> getIdRange() {
|
||||
return idRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the smallest ID among the pending messages.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public RecordId minRecordId() {
|
||||
return RecordId.of(minMessageId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the greatest ID among the pending messages.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public RecordId maxRecordId() {
|
||||
return RecordId.of(maxMessageId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the smallest ID as {@link String} among the pending messages.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public String minMessageId() {
|
||||
return idRange.getLowerBound().getValue().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the greatest ID as {@link String} among the pending messages.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public String maxMessageId() {
|
||||
return idRange.getUpperBound().getValue().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of total pending messages within the {@literal consumer group}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Long getTotalPendingMessages() {
|
||||
return totalPendingMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@literal consumer group} name.
|
||||
*/
|
||||
public String getGroupName() {
|
||||
return groupName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a map of every {@literal consumer} in the {@literal consumer group} with at least one pending message, and
|
||||
* the number of pending messages.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Map<String, Long> getPendingMessagesPerConsumer() {
|
||||
return Collections.unmodifiableMap(pendingMessagesPerConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return "PendingMessagesSummary{" + "groupName='" + groupName + '\'' + ", totalPendingMessages='"
|
||||
+ getTotalPendingMessages() + '\'' + ", minMessageId='" + minMessageId() + '\'' + ", maxMessageId='"
|
||||
+ maxMessageId() + '\'' + ", pendingMessagesPerConsumer=" + pendingMessagesPerConsumer + '}';
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import org.springframework.data.redis.connection.stream.PendingMessages;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -183,6 +185,40 @@ class DefaultReactiveStreamOperations<K, HK, HV> implements ReactiveStreamOperat
|
||||
return createMono(connection -> connection.xGroupDestroy(rawKey(key), group));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.StreamOperations#pending(java.lang.Object, java.lang.String, org.springframework.data.domain.Range, java.lang.Long)
|
||||
*/
|
||||
@Override
|
||||
public Mono<PendingMessages> pending(K key, String group, Range<?> range, Long count) {
|
||||
|
||||
ByteBuffer rawKey = rawKey(key);
|
||||
return createMono(connection -> connection.xPending(rawKey, group, range, count));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.StreamOperations#pending(java.lang.Object, org.springframework.data.redis.connection.stream.Consumer, org.springframework.data.domain.Range, java.lang.Long)
|
||||
*/
|
||||
@Override
|
||||
public Mono<PendingMessages> pending(K key, Consumer consumer, Range<?> range, Long count) {
|
||||
|
||||
ByteBuffer rawKey = rawKey(key);
|
||||
return createMono(connection -> connection.xPending(rawKey, consumer, range, count));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.StreamOperations#pending(java.lang.Object, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Mono<PendingMessagesSummary> pending(K key, String group) {
|
||||
|
||||
ByteBuffer rawKey = rawKey(key);
|
||||
return createMono(connection -> connection.xPending(rawKey, group));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveStreamOperations#size(java.lang.Object)
|
||||
|
||||
@@ -29,6 +29,8 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
|
||||
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.PendingMessages;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
|
||||
import org.springframework.data.redis.connection.stream.ReadOffset;
|
||||
import org.springframework.data.redis.connection.stream.Record;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
@@ -179,6 +181,39 @@ class DefaultStreamOperations<K, HK, HV> extends AbstractOperations<K, Object> i
|
||||
return execute(connection -> connection.xGroupDestroy(rawKey, group), true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.StreamOperations#pending(java.lang.Object, java.lang.String, org.springframework.data.domain.Range, java.lang.Long)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessages pending(K key, String group, Range<?> range, Long count) {
|
||||
|
||||
byte[] rawKey = rawKey(key);
|
||||
return execute(connection -> connection.xPending(rawKey, group, range, count), true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.StreamOperations#pending(java.lang.Object, org.springframework.data.redis.connection.stream.Consumer, org.springframework.data.domain.Range, java.lang.Long)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessages pending(K key, Consumer consumer, Range<?> range, Long count) {
|
||||
|
||||
byte[] rawKey = rawKey(key);
|
||||
return execute(connection -> connection.xPending(rawKey, consumer, range, count), true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.StreamOperations#pending(java.lang.Object, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public PendingMessagesSummary pending(K key, String group) {
|
||||
|
||||
byte[] rawKey = rawKey(key);
|
||||
return execute(connection -> connection.xPending(rawKey, group), true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.StreamOperations#size(java.lang.Object)
|
||||
@@ -279,7 +314,7 @@ class DefaultStreamOperations<K, HK, HV> extends AbstractOperations<K, Object> i
|
||||
return hashValueSerializer() != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private byte[] serialize(Object value, RedisSerializer serializer) {
|
||||
|
||||
Object _value = value;
|
||||
@@ -303,7 +338,7 @@ class DefaultStreamOperations<K, HK, HV> extends AbstractOperations<K, Object> i
|
||||
public final List<MapRecord<K, HK, HV>> doInRedis(RedisConnection connection) {
|
||||
|
||||
List<ByteRecord> raw = inRedis(connection);
|
||||
if(raw == null) {
|
||||
if (raw == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,16 +24,9 @@ import java.util.Map;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
|
||||
import org.springframework.data.redis.connection.stream.Consumer;
|
||||
import org.springframework.data.redis.connection.stream.MapRecord;
|
||||
import org.springframework.data.redis.connection.stream.ObjectRecord;
|
||||
import org.springframework.data.redis.connection.stream.ReadOffset;
|
||||
import org.springframework.data.redis.connection.stream.Record;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.data.redis.connection.stream.StreamOffset;
|
||||
import org.springframework.data.redis.connection.stream.StreamReadOptions;
|
||||
import org.springframework.data.redis.connection.stream.StreamRecords;
|
||||
import org.springframework.data.redis.connection.stream.*;
|
||||
import org.springframework.data.redis.hash.HashMapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -201,6 +194,61 @@ public interface ReactiveStreamOperations<K, HK, HV> extends HashMapperProvider<
|
||||
*/
|
||||
Mono<String> destroyGroup(K key, String group);
|
||||
|
||||
/**
|
||||
* Obtain the {@link PendingMessagesSummary} for a given {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param group the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @return a summary of pending messages within the given {@literal consumer group} or {@literal null} when used in
|
||||
* pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
Mono<PendingMessagesSummary> pending(K key, String group);
|
||||
|
||||
/**
|
||||
* Obtained detailed information about all pending messages for a given {@link Consumer}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param consumer the consumer to fetch {@link PendingMessages} for. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@link Consumer} or {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
default Mono<PendingMessages> pending(K key, Consumer consumer) {
|
||||
return pending(key, consumer, Range.unbounded(), -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link Range} within a
|
||||
* {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param group the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@literal consumer group} or {@literal null} when used in pipeline /
|
||||
* transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<PendingMessages> pending(K key, String group, Range<?> range, Long count);
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link Range} and
|
||||
* {@link Consumer} within a {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param consumer the name of the {@link Consumer}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@link Consumer} or {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<PendingMessages> pending(K key, Consumer consumer, Range<?> range, Long count);
|
||||
|
||||
/**
|
||||
* Get the length of a stream.
|
||||
*
|
||||
|
||||
@@ -23,15 +23,7 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
|
||||
import org.springframework.data.redis.connection.stream.Consumer;
|
||||
import org.springframework.data.redis.connection.stream.MapRecord;
|
||||
import org.springframework.data.redis.connection.stream.ObjectRecord;
|
||||
import org.springframework.data.redis.connection.stream.ReadOffset;
|
||||
import org.springframework.data.redis.connection.stream.Record;
|
||||
import org.springframework.data.redis.connection.stream.RecordId;
|
||||
import org.springframework.data.redis.connection.stream.StreamOffset;
|
||||
import org.springframework.data.redis.connection.stream.StreamReadOptions;
|
||||
import org.springframework.data.redis.connection.stream.StreamRecords;
|
||||
import org.springframework.data.redis.connection.stream.*;
|
||||
import org.springframework.data.redis.hash.HashMapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -201,6 +193,61 @@ public interface StreamOperations<K, HK, HV> extends HashMapperProvider<HK, HV>
|
||||
@Nullable
|
||||
Boolean destroyGroup(K key, String group);
|
||||
|
||||
/**
|
||||
* Obtain the {@link PendingMessagesSummary} for a given {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param group the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @return a summary of pending messages within the given {@literal consumer group} or {@literal null} when used in
|
||||
* pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
PendingMessagesSummary pending(K key, String group);
|
||||
|
||||
/**
|
||||
* Obtained detailed information about all pending messages for a given {@link Consumer}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param consumer the consumer to fetch {@link PendingMessages} for. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@link Consumer} or {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
default PendingMessages pending(K key, Consumer consumer) {
|
||||
return pending(key, consumer, Range.unbounded(), -1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link Range} within a
|
||||
* {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param group the name of the {@literal consumer group}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@literal consumer group} or {@literal null} when used in pipeline /
|
||||
* transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
PendingMessages pending(K key, String group, Range<?> range, Long count);
|
||||
|
||||
/**
|
||||
* Obtain detailed information about pending {@link PendingMessage messages} for a given {@link Range} and
|
||||
* {@link Consumer} within a {@literal consumer group}.
|
||||
*
|
||||
* @param key the {@literal key} the stream is stored at. Must not be {@literal null}.
|
||||
* @param consumer the name of the {@link Consumer}. Must not be {@literal null}.
|
||||
* @param range the range of messages ids to search within. Must not be {@literal null}.
|
||||
* @param count limit the number of results. Must not be {@literal null}.
|
||||
* @return pending messages for the given {@link Consumer} or {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/xpending">Redis Documentation: xpending</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
PendingMessages pending(K key, Consumer consumer, Range<?> range, Long count);
|
||||
|
||||
/**
|
||||
* Get the length of a stream.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user