DATAREDIS-743 - Add Reactive SCAN, HSCAN, SSCAN, and ZSCAN support.
We now provide reactive SCAN support for keys, hashes, sets, and sorted sets. Scanning uses a cursor-based Flux and takes subscriber demand into account to issue the according scan commands once a batch of keys/values/entries was emitted. Specifying a SCAN limit controls the batch (page) size and can be used to optimize the number of roundtrips between the application and Redis. Original Pull Request: #343
This commit is contained in:
committed by
Christoph Strobl
parent
a9062dfe33
commit
d4b06c2984
@@ -14,6 +14,7 @@ This section briefly covers items that are new and noteworthy in the latest rele
|
||||
* <<redis:reactive:pubsub,Reactive Pub/Sub>> to send and receive a message stream.
|
||||
* `BITFIELD`, `BITPOS`, and `OBJECT` command support.
|
||||
* Align return types of `BoundZSetOperations` with `ZSetOperations`.
|
||||
* Reactive `SCAN`, `HSCAN`, `SSCAN`, and `ZSCAN` support.
|
||||
|
||||
[[new-in-2.0.0]]
|
||||
== New in Spring Data Redis 2.0
|
||||
|
||||
@@ -31,8 +31,10 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.Boolean
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.Command;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyScanCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -578,6 +580,45 @@ public interface ReactiveHashCommands {
|
||||
*/
|
||||
Flux<CommandResponse<KeyCommand, Flux<Map.Entry<ByteBuffer, ByteBuffer>>>> hGetAll(Publisher<KeyCommand> commands);
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over entries in the hash at {@code key}. The resulting {@link Flux} acts as a cursor
|
||||
* and issues {@code HSCAN} commands itself as long as the subscriber signals demand .
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="http://redis.io/commands/hscan">Redis Documentation: HSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<Map.Entry<ByteBuffer, ByteBuffer>> hScan(ByteBuffer key) {
|
||||
return hScan(key, ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over entries in the hash at {@code key} given {@link ScanOptions}. The resulting
|
||||
* {@link Flux} acts as a cursor and issues {@code HSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="http://redis.io/commands/hscan">Redis Documentation: HSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<Map.Entry<ByteBuffer, ByteBuffer>> hScan(ByteBuffer key, ScanOptions options) {
|
||||
return hScan(Mono.just(KeyScanCommand.key(key).withOptions(options))).map(CommandResponse::getOutput)
|
||||
.flatMap(it -> it);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over entries in the hash at {@code key}. The resulting {@link Flux} acts as a cursor
|
||||
* and issues {@code HSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="http://redis.io/commands/hscan">Redis Documentation: HSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<CommandResponse<KeyCommand, Flux<Map.Entry<ByteBuffer, ByteBuffer>>>> hScan(Publisher<KeyScanCommand> commands);
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @see <a href="http://redis.io/commands/hstrlen">Redis Documentation: HSTRLEN</a>
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.Command
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -133,6 +134,29 @@ public interface ReactiveKeyCommands {
|
||||
*/
|
||||
Flux<MultiValueResponse<ByteBuffer, ByteBuffer>> keys(Publisher<ByteBuffer> patterns);
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over keys. The resulting {@link Flux} acts as a cursor and issues {@code SCAN}
|
||||
* commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/scan">Redis Documentation: SCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<ByteBuffer> scan() {
|
||||
return scan(ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over keys. The resulting {@link Flux} acts as a cursor and issues {@code SCAN}
|
||||
* commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/scan">Redis Documentation: SCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<ByteBuffer> scan(ScanOptions options);
|
||||
|
||||
/**
|
||||
* Return a random key from the keyspace.
|
||||
*
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.domain.Range.Bound;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -209,6 +210,49 @@ public interface ReactiveRedisConnection extends Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Command} for key-bound scan operations like {@code HSCAN}, {@code SSCAN}, and {@code ZSCAN}, .
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
class KeyScanCommand extends KeyCommand {
|
||||
|
||||
private ScanOptions options;
|
||||
|
||||
private KeyScanCommand(@Nullable ByteBuffer key, ScanOptions options) {
|
||||
|
||||
super(key);
|
||||
|
||||
Assert.notNull(options, "ScanOptions must not be null!");
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link KeyScanCommand} given a {@code key}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return a new {@link KeyScanCommand} for {@code key}.
|
||||
*/
|
||||
public static KeyScanCommand key(ByteBuffer key) {
|
||||
return new KeyScanCommand(key, ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies {@link ScanOptions}. Constructs a new command instance with all previously configured properties.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return a new {@link KeyScanCommand} with {@link ScanOptions} applied.
|
||||
*/
|
||||
public KeyScanCommand withOptions(ScanOptions options) {
|
||||
return new KeyScanCommand(getKey(), options);
|
||||
}
|
||||
|
||||
public ScanOptions getOptions() {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,9 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBuf
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.Command;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyScanCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -1013,6 +1015,45 @@ public interface ReactiveSetCommands {
|
||||
*/
|
||||
Flux<CommandResponse<KeyCommand, Flux<ByteBuffer>>> sMembers(Publisher<KeyCommand> commands);
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over members in the set at {@code key}. The resulting {@link Flux} acts as a cursor
|
||||
* and issues {@code SSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="http://redis.io/commands/sscan">Redis Documentation: SSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<ByteBuffer> sScan(ByteBuffer key) {
|
||||
return sScan(key, ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over members in the set at {@code key} given {@link ScanOptions}. The resulting
|
||||
* {@link Flux} acts as a cursor and issues {@code SSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="http://redis.io/commands/sscan">Redis Documentation: SSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<ByteBuffer> sScan(ByteBuffer key, ScanOptions options) {
|
||||
return sScan(Mono.just(KeyScanCommand.key(key).withOptions(options))).map(CommandResponse::getOutput)
|
||||
.flatMap(it -> it);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over members in the set at {@code key}. The resulting {@link Flux} acts as a cursor
|
||||
* and issues {@code SSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="http://redis.io/commands/sscan">Redis Documentation: SSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<CommandResponse<KeyCommand, Flux<ByteBuffer>>> sScan(Publisher<KeyScanCommand> commands);
|
||||
|
||||
/**
|
||||
* {@code SRANDMEMBER} command parameters.
|
||||
*
|
||||
|
||||
@@ -30,11 +30,13 @@ import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyScanCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Weights;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.util.ByteUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -997,6 +999,45 @@ public interface ReactiveZSetCommands {
|
||||
*/
|
||||
Flux<CommandResponse<ZRangeByScoreCommand, Flux<Tuple>>> zRangeByScore(Publisher<ZRangeByScoreCommand> commands);
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over members in the sorted set at {@code key}. The resulting {@link Flux} acts as a
|
||||
* cursor and issues {@code ZSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="http://redis.io/commands/zscan">Redis Documentation: ZSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<Tuple> zScan(ByteBuffer key) {
|
||||
return zScan(key, ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over members in the sorted set at {@code key} given {@link ScanOptions}. The
|
||||
* resulting {@link Flux} acts as a cursor and issues {@code ZSCAN} commands itself as long as the subscriber signals.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="http://redis.io/commands/zscan">Redis Documentation: ZSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<Tuple> zScan(ByteBuffer key, ScanOptions options) {
|
||||
return zScan(Mono.just(KeyScanCommand.key(key).withOptions(options))).map(CommandResponse::getOutput)
|
||||
.flatMap(it -> it);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over members in the sorted set at {@code key}. The resulting {@link Flux} acts as a
|
||||
* cursor and issues {@code ZSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="http://redis.io/commands/zscan">Redis Documentation: ZSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<CommandResponse<KeyCommand, Flux<Tuple>>> zScan(Publisher<KeyScanCommand> commands);
|
||||
|
||||
/**
|
||||
* {@code ZCOUNT} command parameters.
|
||||
*
|
||||
|
||||
@@ -204,7 +204,7 @@ class LettuceClusterKeyCommands extends LettuceKeyCommands {
|
||||
@Override
|
||||
protected LettuceScanIteration<byte[]> doScan(io.lettuce.core.ScanCursor cursor, ScanOptions options) {
|
||||
|
||||
ScanArgs scanArgs = connection.getScanArgs(options);
|
||||
ScanArgs scanArgs = LettuceConverters.toScanArgs(options);
|
||||
|
||||
KeyScanCursor<byte[]> keyScanCursor = client.scan(cursor, scanArgs);
|
||||
return new LettuceScanIteration<>(keyScanCursor, keyScanCursor.getKeys());
|
||||
|
||||
@@ -23,7 +23,6 @@ import io.lettuce.core.RedisClient;
|
||||
import io.lettuce.core.RedisException;
|
||||
import io.lettuce.core.RedisFuture;
|
||||
import io.lettuce.core.RedisURI;
|
||||
import io.lettuce.core.ScanArgs;
|
||||
import io.lettuce.core.TransactionResult;
|
||||
import io.lettuce.core.api.StatefulConnection;
|
||||
import io.lettuce.core.api.StatefulRedisConnection;
|
||||
@@ -68,7 +67,6 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionProvid
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceResult.LettuceResultBuilder;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceResult.LettuceStatusResult;
|
||||
import org.springframework.data.redis.core.RedisCommand;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -922,26 +920,6 @@ public class LettuceConnection extends AbstractRedisConnection {
|
||||
return io.lettuce.core.ScanCursor.of(Long.toString(cursorId));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
ScanArgs getScanArgs(@Nullable ScanOptions options) {
|
||||
|
||||
if (options == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ScanArgs scanArgs = new ScanArgs();
|
||||
|
||||
if (options.getPattern() != null) {
|
||||
scanArgs.match(options.getPattern());
|
||||
}
|
||||
|
||||
if (options.getCount() != null) {
|
||||
scanArgs.limit(options.getCount());
|
||||
}
|
||||
|
||||
return scanArgs;
|
||||
}
|
||||
|
||||
private void validateCommandIfRunningInTransactionMode(CommandType cmd, byte[]... args) {
|
||||
|
||||
if (this.isQueueing()) {
|
||||
|
||||
@@ -63,6 +63,7 @@ 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.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.data.redis.core.types.RedisClientInfo;
|
||||
import org.springframework.data.redis.util.ByteUtils;
|
||||
@@ -878,6 +879,33 @@ abstract public class LettuceConverters extends Converters {
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert {@link ScanOptions} to {@link ScanArgs}.
|
||||
*
|
||||
* @param options the {@link ScanOptions} to convert, may be {@literal null}.
|
||||
* @return the converted {@link ScanArgs}. Returns {@literal null} if {@link ScanOptions} is {@literal null}.
|
||||
* @see 2.1
|
||||
*/
|
||||
@Nullable
|
||||
static ScanArgs toScanArgs(@Nullable ScanOptions options) {
|
||||
|
||||
if (options == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ScanArgs scanArgs = new ScanArgs();
|
||||
|
||||
if (options.getPattern() != null) {
|
||||
scanArgs.match(options.getPattern());
|
||||
}
|
||||
|
||||
if (options.getCount() != null) {
|
||||
scanArgs.limit(options.getCount());
|
||||
}
|
||||
|
||||
return scanArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link Converter} capable of {@link Set} of {@link Byte} into {@link GeoResults}.
|
||||
*
|
||||
|
||||
@@ -401,7 +401,7 @@ class LettuceHashCommands implements RedisHashCommands {
|
||||
}
|
||||
|
||||
io.lettuce.core.ScanCursor scanCursor = connection.getScanCursor(cursorId);
|
||||
ScanArgs scanArgs = connection.getScanArgs(options);
|
||||
ScanArgs scanArgs = LettuceConverters.toScanArgs(options);
|
||||
|
||||
MapScanCursor<byte[], byte[]> mapScanCursor = getConnection().hscan(key, scanCursor, scanArgs);
|
||||
String nextCursorId = mapScanCursor.getCursor();
|
||||
|
||||
@@ -259,7 +259,7 @@ class LettuceKeyCommands implements RedisKeyCommands {
|
||||
throw new UnsupportedOperationException("'SCAN' cannot be called in pipeline / transaction mode.");
|
||||
}
|
||||
|
||||
ScanArgs scanArgs = connection.getScanArgs(options);
|
||||
ScanArgs scanArgs = LettuceConverters.toScanArgs(options);
|
||||
|
||||
KeyScanCursor<byte[]> keyScanCursor = getConnection().scan(cursor, scanArgs);
|
||||
List<byte[]> keys = keyScanCursor.getKeys();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.redis.connection.lettuce;
|
||||
|
||||
import io.lettuce.core.KeyValue;
|
||||
import io.lettuce.core.ScanStream;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -31,6 +32,7 @@ import org.springframework.data.redis.connection.ReactiveHashCommands;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyScanCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -214,6 +216,44 @@ class LettuceReactiveHashCommands implements ReactiveHashCommands {
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hScan(org.reactivestreams.Publisher)
|
||||
*/
|
||||
@Override
|
||||
public Flux<CommandResponse<KeyCommand, Flux<Map.Entry<ByteBuffer, ByteBuffer>>>> hScan(
|
||||
Publisher<KeyScanCommand> commands) {
|
||||
|
||||
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
|
||||
|
||||
Assert.notNull(command.getKey(), "Key must not be null!");
|
||||
Assert.notNull(command.getOptions(), "ScanOptions must not be null!");
|
||||
|
||||
Flux<KeyValue<ByteBuffer, ByteBuffer>> result = ScanStream.hscan(cmd, command.getKey(),
|
||||
LettuceConverters.toScanArgs(command.getOptions()));
|
||||
|
||||
Flux<Entry<ByteBuffer, ByteBuffer>> entryFlux = result.map(it -> new Entry<ByteBuffer, ByteBuffer>() {
|
||||
|
||||
@Override
|
||||
public ByteBuffer getKey() {
|
||||
return it.getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getValue() {
|
||||
return it.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer setValue(ByteBuffer value) {
|
||||
throw new UnsupportedOperationException("Cannot set value for entry in cursor");
|
||||
}
|
||||
});
|
||||
|
||||
return Mono.just(new CommandResponse<>(command, entryFlux));
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hstrlen(org.reactivestreams.Publisher)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.redis.connection.lettuce;
|
||||
|
||||
import io.lettuce.core.ScanStream;
|
||||
import io.lettuce.core.api.reactive.RedisKeyReactiveCommands;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -34,6 +35,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiVa
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.connection.ValueEncoding;
|
||||
import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -119,6 +121,18 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands {
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#scan(org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Flux<ByteBuffer> scan(ScanOptions options) {
|
||||
|
||||
Assert.notNull(options, "ScanOptions must not be null!");
|
||||
|
||||
return connection.execute(cmd -> ScanStream.scan(cmd, LettuceConverters.toScanArgs(options)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveKeyCommands#randomKey()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.redis.connection.lettuce;
|
||||
|
||||
import io.lettuce.core.ScanStream;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -25,6 +26,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.Boolean
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyScanCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveSetCommands;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -276,6 +278,25 @@ class LettuceReactiveSetCommands implements ReactiveSetCommands {
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sScan(org.reactivestreams.Publisher)
|
||||
*/
|
||||
@Override
|
||||
public Flux<CommandResponse<KeyCommand, Flux<ByteBuffer>>> sScan(Publisher<KeyScanCommand> commands) {
|
||||
|
||||
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
|
||||
|
||||
Assert.notNull(command.getKey(), "Key must not be null!");
|
||||
Assert.notNull(command.getOptions(), "ScanOptions must not be null!");
|
||||
|
||||
Flux<ByteBuffer> result = ScanStream.sscan(cmd, command.getKey(),
|
||||
LettuceConverters.toScanArgs(command.getOptions()));
|
||||
|
||||
return Mono.just(new CommandResponse<>(command, result));
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveSetCommands#sRandMembers(org.reactivestreams.Publisher)
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.redis.connection.lettuce;
|
||||
|
||||
import io.lettuce.core.Range;
|
||||
import io.lettuce.core.Range.Boundary;
|
||||
import io.lettuce.core.ScanStream;
|
||||
import io.lettuce.core.ScoredValue;
|
||||
import io.lettuce.core.ZAddArgs;
|
||||
import io.lettuce.core.ZStoreArgs;
|
||||
@@ -34,6 +35,7 @@ import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.redis.connection.DefaultTuple;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyScanCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveZSetCommands;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
|
||||
@@ -292,6 +294,25 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands {
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zScan(org.reactivestreams.Publisher)
|
||||
*/
|
||||
@Override
|
||||
public Flux<CommandResponse<KeyCommand, Flux<Tuple>>> zScan(Publisher<KeyScanCommand> commands) {
|
||||
|
||||
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
|
||||
|
||||
Assert.notNull(command.getKey(), "Key must not be null!");
|
||||
Assert.notNull(command.getOptions(), "ScanOptions must not be null!");
|
||||
|
||||
Flux<Tuple> result = ScanStream.zscan(cmd, command.getKey(), LettuceConverters.toScanArgs(command.getOptions()))
|
||||
.map(it -> new DefaultTuple(ByteUtils.getBytes(it.getValue()), it.getScore()));
|
||||
|
||||
return Mono.just(new CommandResponse<>(command, result));
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveZSetCommands#zCount(org.reactivestreams.Publisher)
|
||||
|
||||
@@ -476,7 +476,7 @@ class LettuceSetCommands implements RedisSetCommands {
|
||||
}
|
||||
|
||||
io.lettuce.core.ScanCursor scanCursor = connection.getScanCursor(cursorId);
|
||||
ScanArgs scanArgs = connection.getScanArgs(options);
|
||||
ScanArgs scanArgs = LettuceConverters.toScanArgs(options);
|
||||
|
||||
ValueScanCursor<byte[]> valueScanCursor = getConnection().sscan(key, scanCursor, scanArgs);
|
||||
String nextCursorId = valueScanCursor.getCursor();
|
||||
|
||||
@@ -711,7 +711,7 @@ class LettuceZSetCommands implements RedisZSetCommands {
|
||||
}
|
||||
|
||||
io.lettuce.core.ScanCursor scanCursor = connection.getScanCursor(cursorId);
|
||||
ScanArgs scanArgs = connection.getScanArgs(options);
|
||||
ScanArgs scanArgs = LettuceConverters.toScanArgs(options);
|
||||
|
||||
ScoredValueScanCursor<byte[]> scoredValueScanCursor = getConnection().zscan(key, scanCursor, scanArgs);
|
||||
String nextCursorId = scoredValueScanCursor.getCursor();
|
||||
|
||||
@@ -221,6 +221,19 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
|
||||
.map(this::deserializeHashEntry));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveHashOperations#scan(java.lang.Object, org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Flux<Map.Entry<HK, HV>> scan(H key, ScanOptions options) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(key, "ScanOptions must not be null!");
|
||||
|
||||
return createFlux(connection -> connection.hScan(rawKey(key), options) //
|
||||
.map(this::deserializeHashEntry));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveHashOperations#delete(java.lang.Object)
|
||||
*/
|
||||
|
||||
@@ -322,6 +322,18 @@ class DefaultReactiveSetOperations<K, V> implements ReactiveSetOperations<K, V>
|
||||
return createFlux(connection -> connection.sMembers(rawKey(key)).map(this::readValue));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveSetOperations#scan(java.lang.Object, org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Flux<V> scan(K key, ScanOptions options) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(options, "ScanOptions must not be null!");
|
||||
|
||||
return createFlux(connection -> connection.sScan(rawKey(key)).map(this::readValue));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveSetOperations#randomMember(java.lang.Object)
|
||||
*/
|
||||
|
||||
@@ -283,6 +283,18 @@ class DefaultReactiveZSetOperations<K, V> implements ReactiveZSetOperations<K, V
|
||||
connection -> connection.zRevRangeByScoreWithScores(rawKey(key), range, limit).map(this::readTypedTuple));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveZSetOperations#scan(java.lang.Object, org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Flux<TypedTuple<V>> scan(K key, ScanOptions options) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(options, "ScanOptions must not be null!");
|
||||
|
||||
return createFlux(connection -> connection.zScan(rawKey(key), options).map(this::readTypedTuple));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveZSetOperations#count(java.lang.Object, org.springframework.data.domain.Range)
|
||||
*/
|
||||
|
||||
@@ -146,6 +146,31 @@ public interface ReactiveHashOperations<H, HK, HV> {
|
||||
*/
|
||||
Flux<Map.Entry<HK, HV>> entries(H key);
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over entries in the hash at {@code key} given {@link ScanOptions}. The resulting
|
||||
* {@link Flux} acts as a cursor and issues {@code HSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/hscan">Redis Documentation: HSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<Map.Entry<HK, HV>> scan(H key) {
|
||||
return scan(key, ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over entries in the hash at {@code key} given {@link ScanOptions}. The resulting
|
||||
* {@link Flux} acts as a cursor and issues {@code HSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/hscan">Redis Documentation: HSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<Map.Entry<HK, HV>> scan(H key, ScanOptions options);
|
||||
|
||||
/**
|
||||
* Removes the given {@literal key}.
|
||||
*
|
||||
|
||||
@@ -143,9 +143,35 @@ public interface ReactiveRedisOperations<K, V> {
|
||||
* @param pattern must not be {@literal null}.
|
||||
* @return
|
||||
* @see <a href="http://redis.io/commands/keys">Redis Documentation: KEYS</a>
|
||||
* @deprecated Since 2.1. Use {@link #scan()} to iterate over the keyspace as {@link #keys(Object)} is a
|
||||
* non-interruptible and expensive Redis operation.
|
||||
*/
|
||||
@Deprecated
|
||||
Flux<K> keys(K pattern);
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over keys. The resulting {@link Flux} acts as a cursor and issues {@code SCAN}
|
||||
* commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/scan">Redis Documentation: SCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<K> scan() {
|
||||
return scan(ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over keys. The resulting {@link Flux} acts as a cursor and issues {@code SCAN}
|
||||
* commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/scan">Redis Documentation: SCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<K> scan(ScanOptions options);
|
||||
|
||||
/**
|
||||
* Return a random key from the keyspace.
|
||||
*
|
||||
|
||||
@@ -270,6 +270,19 @@ public class ReactiveRedisTemplate<K, V> implements ReactiveRedisOperations<K, V
|
||||
.map(this::readKey);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveRedisOperations#scan(org.springframework.data.redis.core.ScanOptions)
|
||||
*/
|
||||
@Override
|
||||
public Flux<K> scan(ScanOptions options) {
|
||||
|
||||
Assert.notNull(options, "ScanOptions must not be null!");
|
||||
|
||||
return createFlux(connection -> connection.keyCommands().scan(options)) //
|
||||
.map(this::readKey);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveRedisOperations#randomKey()
|
||||
|
||||
@@ -234,6 +234,31 @@ public interface ReactiveSetOperations<K, V> {
|
||||
*/
|
||||
Flux<V> members(K key);
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over entries in the set at {@code key} given {@link ScanOptions}. The resulting
|
||||
* {@link Flux} acts as a cursor and issues {@code SSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/sscan">Redis Documentation: SSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<V> scan(K key) {
|
||||
return scan(key, ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over entries in the set at {@code key} given {@link ScanOptions}. The resulting
|
||||
* {@link Flux} acts as a cursor and issues {@code SSCAN} commands itself as long as the subscriber signals demand.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/sscan">Redis Documentation: SSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<V> scan(K key, ScanOptions options);
|
||||
|
||||
/**
|
||||
* Get random element from set at {@code key}.
|
||||
*
|
||||
|
||||
@@ -228,6 +228,33 @@ public interface ReactiveZSetOperations<K, V> {
|
||||
*/
|
||||
Flux<TypedTuple<V>> reverseRangeByScoreWithScores(K key, Range<Double> range, Limit limit);
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over entries in the sorted set at {@code key} given {@link ScanOptions}. The
|
||||
* resulting {@link Flux} acts as a cursor and issues {@code ZSCAN} commands itself as long as the subscriber signals
|
||||
* demand.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/zscan">Redis Documentation: ZSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Flux<TypedTuple<V>> scan(K key) {
|
||||
return scan(key, ScanOptions.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a {@link Flux} to iterate over entries in the sorted set at {@code key} given {@link ScanOptions}. The
|
||||
* resulting {@link Flux} acts as a cursor and issues {@code ZSCAN} commands itself as long as the subscriber signals
|
||||
* demand.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param options must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/zscan">Redis Documentation: ZSCAN</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<TypedTuple<V>> scan(K key, ScanOptions options);
|
||||
|
||||
/**
|
||||
* Count number of elements within sorted set with scores between {@code min} and {@code max}.
|
||||
*
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
@@ -233,6 +234,18 @@ public class LettuceReactiveHashCommandsTests extends LettuceReactiveCommandsTes
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-743
|
||||
public void hScanShouldIterateOverHash() {
|
||||
|
||||
nativeCommands.hset(KEY_1, FIELD_1, VALUE_1);
|
||||
nativeCommands.hset(KEY_1, FIELD_2, VALUE_2);
|
||||
nativeCommands.hset(KEY_1, FIELD_3, VALUE_3);
|
||||
|
||||
StepVerifier.create(connection.hashCommands().hScan(KEY_1_BBUFFER, ScanOptions.scanOptions().count(1).build())) //
|
||||
.expectNextCount(3) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-698
|
||||
public void hStrLenReturnsFieldLength() {
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.data.redis.connection.DataType;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
|
||||
import org.springframework.test.annotation.IfProfileValue;
|
||||
|
||||
@@ -95,6 +96,26 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest
|
||||
.expectNextCount(3).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-743
|
||||
public void scanShouldShouldIterateOverKeyspace() {
|
||||
|
||||
nativeCommands.set(KEY_1, VALUE_2);
|
||||
nativeCommands.set(KEY_2, VALUE_2);
|
||||
nativeCommands.set(KEY_3, VALUE_3);
|
||||
|
||||
nativeCommands.set(VALUE_1, KEY_1);
|
||||
nativeCommands.set(VALUE_2, KEY_2);
|
||||
nativeCommands.set(VALUE_3, KEY_3);
|
||||
|
||||
StepVerifier.create(connection.keyCommands().scan(ScanOptions.scanOptions().count(2).build())) //
|
||||
.expectNextCount(6) //
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(connection.keyCommands().scan(ScanOptions.scanOptions().count(2).match("key*").build())) //
|
||||
.expectNextCount(3) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-525
|
||||
public void randomKeyShouldReturnAnyKey() {
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import reactor.test.StepVerifier;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
|
||||
public class LettuceReactiveSetCommandsTests extends LettuceReactiveCommandsTestsBase {
|
||||
|
||||
@@ -220,6 +221,21 @@ public class LettuceReactiveSetCommandsTests extends LettuceReactiveCommandsTest
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-743
|
||||
public void sScanShouldIterateOverSet() {
|
||||
|
||||
nativeCommands.sadd(KEY_1, VALUE_1, VALUE_2, VALUE_3);
|
||||
|
||||
StepVerifier.create(connection.setCommands().sScan(KEY_1_BBUFFER)) //
|
||||
.expectNextCount(3) //
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier
|
||||
.create(connection.setCommands().sScan(KEY_1_BBUFFER, ScanOptions.scanOptions().match("value-3").build())) //
|
||||
.expectNext(VALUE_3_BBUFFER) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-525
|
||||
public void sRandMemberReturnsRandomMember() {
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.hamcrest.collection.IsIterableContainingInOrder;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.redis.connection.DefaultTuple;
|
||||
import org.springframework.data.redis.test.util.LettuceRedisClientProvider;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
@@ -276,6 +276,22 @@ public class LettuceReactiveZSetCommandsTests extends LettuceReactiveCommandsTes
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-743
|
||||
public void zScanShouldIterateOverSortedSet() {
|
||||
|
||||
nativeCommands.zadd(KEY_1, 1D, VALUE_1);
|
||||
nativeCommands.zadd(KEY_1, 2D, VALUE_2);
|
||||
nativeCommands.zadd(KEY_1, 3D, VALUE_3);
|
||||
|
||||
StepVerifier.create(connection.zSetCommands().zScan(KEY_1_BBUFFER, ScanOptions.scanOptions().count(1).build())) //
|
||||
.expectNextCount(3).verifyComplete();
|
||||
|
||||
StepVerifier
|
||||
.create(connection.zSetCommands().zScan(KEY_1_BBUFFER, ScanOptions.scanOptions().match(VALUE_2).build())) //
|
||||
.expectNext(new DefaultTuple(VALUE_2_BBUFFER.array(), 2D)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-525
|
||||
public void zCountShouldCountValuesInRange() {
|
||||
|
||||
|
||||
@@ -382,6 +382,31 @@ public class DefaultReactiveHashOperationsIntegrationTests<K, HK, HV> {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-743
|
||||
public void scan() {
|
||||
|
||||
assumeTrue(hashKeyFactory instanceof StringObjectFactory && hashValueFactory instanceof StringObjectFactory);
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK hashkey1 = hashKeyFactory.instance();
|
||||
HV hashvalue1 = hashValueFactory.instance();
|
||||
|
||||
HK hashkey2 = hashKeyFactory.instance();
|
||||
HV hashvalue2 = hashValueFactory.instance();
|
||||
|
||||
putAll(key, hashkey1, hashvalue1, hashkey2, hashvalue2);
|
||||
|
||||
StepVerifier.create(hashOperations.scan(key).buffer(2)) //
|
||||
.consumeNextWith(list -> {
|
||||
|
||||
Entry<HK, HV> entry1 = Collections.singletonMap(hashkey1, hashvalue1).entrySet().iterator().next();
|
||||
Entry<HK, HV> entry2 = Collections.singletonMap(hashkey2, hashvalue2).entrySet().iterator().next();
|
||||
|
||||
assertThat(list).containsExactlyInAnyOrder(entry1, entry2);
|
||||
}) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-602
|
||||
public void delete() {
|
||||
|
||||
|
||||
@@ -310,6 +310,25 @@ public class DefaultReactiveSetOperationsIntegrationTests<K, V> {
|
||||
.consumeNextWith(actual -> assertThat(actual).isIn(value1, value2)).expectNextCount(1).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-743
|
||||
public void scan() {
|
||||
|
||||
assumeFalse(valueFactory instanceof ByteBufferObjectFactory);
|
||||
|
||||
K key = keyFactory.instance();
|
||||
V value1 = valueFactory.instance();
|
||||
V value2 = valueFactory.instance();
|
||||
|
||||
StepVerifier.create(setOperations.add(key, value1, value2)) //
|
||||
.expectNext(2L) //
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(setOperations.scan(key)) //
|
||||
.consumeNextWith(actual -> assertThat(actual).isIn(value1, value2)) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-602
|
||||
public void randomMember() {
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import reactor.test.StepVerifier;
|
||||
@@ -389,6 +390,29 @@ public class DefaultReactiveZSetOperationsIntegrationTests<K, V> {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-743
|
||||
public void scan() {
|
||||
|
||||
assumeFalse(valueFactory instanceof ByteBufferObjectFactory);
|
||||
|
||||
K key = keyFactory.instance();
|
||||
V value1 = valueFactory.instance();
|
||||
V value2 = valueFactory.instance();
|
||||
|
||||
StepVerifier.create(zSetOperations.add(key, value1, 42.1)) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
StepVerifier.create(zSetOperations.add(key, value2, 10)) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(zSetOperations.scan(key)) //
|
||||
.consumeNextWith(actual -> assertThat(actual).isIn(new DefaultTypedTuple<>(value1, 42.1),
|
||||
new DefaultTypedTuple<>(value2, 10d))) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-602
|
||||
public void count() {
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
@@ -116,6 +118,23 @@ public class ReactiveRedisTemplateIntegrationTests<K, V> {
|
||||
StepVerifier.create(redisTemplate.hasKey(key)).expectNext(true).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-743
|
||||
public void scan() {
|
||||
|
||||
assumeFalse(valueFactory.instance() instanceof Person);
|
||||
|
||||
Map<K, V> tuples = new HashMap<>();
|
||||
tuples.put(keyFactory.instance(), valueFactory.instance());
|
||||
tuples.put(keyFactory.instance(), valueFactory.instance());
|
||||
tuples.put(keyFactory.instance(), valueFactory.instance());
|
||||
|
||||
StepVerifier.create(redisTemplate.opsForValue().multiSet(tuples)).expectNext(true).verifyComplete();
|
||||
|
||||
StepVerifier.create(redisTemplate.scan().collectList()) //
|
||||
.consumeNextWith(actual -> assertThat(actual).containsAll(tuples.keySet())) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-602
|
||||
public void type() {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user