Skip to content

Commit

Permalink
Support HSCAN with NOVALUES argument
Browse files Browse the repository at this point in the history
Issue #2763

HSCAN has a new argument called NOVALUES. The effect is that only the
keys in the hash are returned, without associated values.
  • Loading branch information
gerzse committed Apr 5, 2024
1 parent e1866f3 commit d98c07a
Show file tree
Hide file tree
Showing 20 changed files with 1,133 additions and 1 deletion.
41 changes: 41 additions & 0 deletions src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -1169,42 +1169,83 @@ public RedisFuture<MapScanCursor<K, V>> hscan(K key) {
return dispatch(commandBuilder.hscan(key));
}

@Override
public RedisFuture<KeyScanCursor<K>> hscanNovalues(K key) {
return dispatch(commandBuilder.hscanNovalues(key));
}

@Override
public RedisFuture<MapScanCursor<K, V>> hscan(K key, ScanArgs scanArgs) {
return dispatch(commandBuilder.hscan(key, scanArgs));
}

@Override
public RedisFuture<KeyScanCursor<K>> hscanNovalues(K key, ScanArgs scanArgs) {
return dispatch(commandBuilder.hscanNovalues(key, scanArgs));
}

@Override
public RedisFuture<MapScanCursor<K, V>> hscan(K key, ScanCursor scanCursor, ScanArgs scanArgs) {
return dispatch(commandBuilder.hscan(key, scanCursor, scanArgs));
}

@Override
public RedisFuture<KeyScanCursor<K>> hscanNovalues(K key, ScanCursor scanCursor, ScanArgs scanArgs) {
return dispatch(commandBuilder.hscanNovalues(key, scanCursor, scanArgs));
}

@Override
public RedisFuture<MapScanCursor<K, V>> hscan(K key, ScanCursor scanCursor) {
return dispatch(commandBuilder.hscan(key, scanCursor));
}

@Override
public RedisFuture<KeyScanCursor<K>> hscanNovalues(K key, ScanCursor scanCursor) {
return dispatch(commandBuilder.hscanNovalues(key, scanCursor));
}

@Override
public RedisFuture<StreamScanCursor> hscan(KeyValueStreamingChannel<K, V> channel, K key) {
return dispatch(commandBuilder.hscanStreaming(channel, key));
}

@Override
public RedisFuture<StreamScanCursor> hscanNovalues(KeyStreamingChannel<K> channel, K key) {
return dispatch(commandBuilder.hscanNoValuesStreaming(channel, key));
}

@Override
public RedisFuture<StreamScanCursor> hscan(KeyValueStreamingChannel<K, V> channel, K key, ScanArgs scanArgs) {
return dispatch(commandBuilder.hscanStreaming(channel, key, scanArgs));
}

@Override
public RedisFuture<StreamScanCursor> hscanNovalues(KeyStreamingChannel<K> channel, K key, ScanArgs scanArgs) {
return dispatch(commandBuilder.hscanNoValuesStreaming(channel, key, scanArgs));
}

@Override
public RedisFuture<StreamScanCursor> hscan(KeyValueStreamingChannel<K, V> channel, K key, ScanCursor scanCursor,
ScanArgs scanArgs) {
return dispatch(commandBuilder.hscanStreaming(channel, key, scanCursor, scanArgs));
}

@Override
public RedisFuture<StreamScanCursor> hscanNovalues(KeyStreamingChannel<K> channel, K key, ScanCursor scanCursor,
ScanArgs scanArgs) {
return dispatch(commandBuilder.hscanNoValuesStreaming(channel, key, scanCursor, scanArgs));
}

@Override
public RedisFuture<StreamScanCursor> hscan(KeyValueStreamingChannel<K, V> channel, K key, ScanCursor scanCursor) {
return dispatch(commandBuilder.hscanStreaming(channel, key, scanCursor));
}

@Override
public RedisFuture<StreamScanCursor> hscanNovalues(KeyStreamingChannel<K> channel, K key, ScanCursor scanCursor) {
return dispatch(commandBuilder.hscanNoValuesStreaming(channel, key, scanCursor));
}

@Override
public RedisFuture<Boolean> hset(K key, K field, V value) {
return dispatch(commandBuilder.hset(key, field, value));
Expand Down
41 changes: 41 additions & 0 deletions src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -1232,42 +1232,83 @@ public Mono<MapScanCursor<K, V>> hscan(K key) {
return createMono(() -> commandBuilder.hscan(key));
}

@Override
public Mono<KeyScanCursor<K>> hscanNovalues(K key) {
return createMono(() -> commandBuilder.hscanNovalues(key));
}

@Override
public Mono<MapScanCursor<K, V>> hscan(K key, ScanArgs scanArgs) {
return createMono(() -> commandBuilder.hscan(key, scanArgs));
}

@Override
public Mono<KeyScanCursor<K>> hscanNovalues(K key, ScanArgs scanArgs) {
return createMono(() -> commandBuilder.hscanNovalues(key, scanArgs));
}

@Override
public Mono<MapScanCursor<K, V>> hscan(K key, ScanCursor scanCursor, ScanArgs scanArgs) {
return createMono(() -> commandBuilder.hscan(key, scanCursor, scanArgs));
}

@Override
public Mono<KeyScanCursor<K>> hscanNovalues(K key, ScanCursor scanCursor, ScanArgs scanArgs) {
return createMono(() -> commandBuilder.hscanNovalues(key, scanCursor, scanArgs));
}

@Override
public Mono<MapScanCursor<K, V>> hscan(K key, ScanCursor scanCursor) {
return createMono(() -> commandBuilder.hscan(key, scanCursor));
}

@Override
public Mono<KeyScanCursor<K>> hscanNovalues(K key, ScanCursor scanCursor) {
return createMono(() -> commandBuilder.hscanNovalues(key, scanCursor));
}

@Override
public Mono<StreamScanCursor> hscan(KeyValueStreamingChannel<K, V> channel, K key) {
return createMono(() -> commandBuilder.hscanStreaming(channel, key));
}

@Override
public Mono<StreamScanCursor> hscanNovalues(KeyStreamingChannel<K> channel, K key) {
return createMono(() -> commandBuilder.hscanNoValuesStreaming(channel, key));
}

@Override
public Mono<StreamScanCursor> hscan(KeyValueStreamingChannel<K, V> channel, K key, ScanArgs scanArgs) {
return createMono(() -> commandBuilder.hscanStreaming(channel, key, scanArgs));
}

@Override
public Mono<StreamScanCursor> hscanNovalues(KeyStreamingChannel<K> channel, K key, ScanArgs scanArgs) {
return createMono(() -> commandBuilder.hscanNoValuesStreaming(channel, key, scanArgs));
}

@Override
public Mono<StreamScanCursor> hscan(KeyValueStreamingChannel<K, V> channel, K key, ScanCursor scanCursor,
ScanArgs scanArgs) {
return createMono(() -> commandBuilder.hscanStreaming(channel, key, scanCursor, scanArgs));
}

@Override
public Mono<StreamScanCursor> hscanNovalues(KeyStreamingChannel<K> channel, K key, ScanCursor scanCursor,
ScanArgs scanArgs) {
return createMono(() -> commandBuilder.hscanNoValuesStreaming(channel, key, scanCursor, scanArgs));
}

@Override
public Mono<StreamScanCursor> hscan(KeyValueStreamingChannel<K, V> channel, K key, ScanCursor scanCursor) {
return createMono(() -> commandBuilder.hscanStreaming(channel, key, scanCursor));
}

@Override
public Mono<StreamScanCursor> hscanNovalues(KeyStreamingChannel<K> channel, K key, ScanCursor scanCursor) {
return createMono(() -> commandBuilder.hscanNoValuesStreaming(channel, key, scanCursor));
}

@Override
public Mono<Boolean> hset(K key, K field, V value) {
return createMono(() -> commandBuilder.hset(key, field, value));
Expand Down
69 changes: 69 additions & 0 deletions src/main/java/io/lettuce/core/RedisCommandBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -1535,18 +1535,36 @@ Command<K, V, MapScanCursor<K, V>> hscan(K key) {
return hscan(key, ScanCursor.INITIAL, null);
}

Command<K, V, KeyScanCursor<K>> hscanNovalues(K key) {
notNullKey(key);

return hscanNovalues(key, ScanCursor.INITIAL, null);
}

Command<K, V, MapScanCursor<K, V>> hscan(K key, ScanCursor scanCursor) {
notNullKey(key);

return hscan(key, scanCursor, null);
}

Command<K, V, KeyScanCursor<K>> hscanNovalues(K key, ScanCursor scanCursor) {
notNullKey(key);

return hscanNovalues(key, scanCursor, null);
}

Command<K, V, MapScanCursor<K, V>> hscan(K key, ScanArgs scanArgs) {
notNullKey(key);

return hscan(key, ScanCursor.INITIAL, scanArgs);
}

Command<K, V, KeyScanCursor<K>> hscanNovalues(K key, ScanArgs scanArgs) {
notNullKey(key);

return hscanNovalues(key, ScanCursor.INITIAL, scanArgs);
}

Command<K, V, MapScanCursor<K, V>> hscan(K key, ScanCursor scanCursor, ScanArgs scanArgs) {
notNullKey(key);

Expand All @@ -1559,27 +1577,62 @@ Command<K, V, MapScanCursor<K, V>> hscan(K key, ScanCursor scanCursor, ScanArgs
return createCommand(HSCAN, output, args);
}

Command<K, V, KeyScanCursor<K>> hscanNovalues(K key, ScanCursor scanCursor, ScanArgs scanArgs) {
notNullKey(key);

CommandArgs<K, V> args = new CommandArgs<>(codec);
args.addKey(key);

scanArgs(scanCursor, scanArgs, args);

args.add(NOVALUES);

KeyScanOutput<K, V> output = new KeyScanOutput<>(codec);
return createCommand(HSCAN, output, args);
}

Command<K, V, StreamScanCursor> hscanStreaming(KeyValueStreamingChannel<K, V> channel, K key) {
notNullKey(key);
notNull(channel);

return hscanStreaming(channel, key, ScanCursor.INITIAL, null);
}

Command<K, V, StreamScanCursor> hscanNoValuesStreaming(KeyStreamingChannel<K> channel, K key) {
notNullKey(key);
notNull(channel);

return hscanNoValuesStreaming(channel, key, ScanCursor.INITIAL, null);
}

Command<K, V, StreamScanCursor> hscanStreaming(KeyValueStreamingChannel<K, V> channel, K key, ScanCursor scanCursor) {
notNullKey(key);
notNull(channel);

return hscanStreaming(channel, key, scanCursor, null);
}

Command<K, V, StreamScanCursor> hscanNoValuesStreaming(KeyStreamingChannel<K> channel, K key, ScanCursor scanCursor) {
notNullKey(key);
notNull(channel);

return hscanNoValuesStreaming(channel, key, scanCursor, null);
}

Command<K, V, StreamScanCursor> hscanStreaming(KeyValueStreamingChannel<K, V> channel, K key, ScanArgs scanArgs) {
notNullKey(key);
notNull(channel);

return hscanStreaming(channel, key, ScanCursor.INITIAL, scanArgs);
}

Command<K, V, StreamScanCursor> hscanNoValuesStreaming(KeyStreamingChannel<K> channel, K key, ScanArgs scanArgs) {
notNullKey(key);
notNull(channel);

return hscanNoValuesStreaming(channel, key, ScanCursor.INITIAL, scanArgs);
}

Command<K, V, StreamScanCursor> hscanStreaming(KeyValueStreamingChannel<K, V> channel, K key, ScanCursor scanCursor,
ScanArgs scanArgs) {
notNullKey(key);
Expand All @@ -1594,6 +1647,22 @@ Command<K, V, StreamScanCursor> hscanStreaming(KeyValueStreamingChannel<K, V> ch
return createCommand(HSCAN, output, args);
}

Command<K, V, StreamScanCursor> hscanNoValuesStreaming(KeyStreamingChannel<K> channel, K key, ScanCursor scanCursor,
ScanArgs scanArgs) {
notNullKey(key);
notNull(channel);

CommandArgs<K, V> args = new CommandArgs<>(codec);

args.addKey(key);
scanArgs(scanCursor, scanArgs, args);

args.add(NOVALUES);

KeyScanStreamingOutput<K, V> output = new KeyScanStreamingOutput<>(codec, channel);
return createCommand(HSCAN, output, args);
}

Command<K, V, Boolean> hset(K key, K field, V value) {
notNullKey(key);
LettuceAssert.notNull(field, "Field " + MUST_NOT_BE_NULL);
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/io/lettuce/core/ScanIterator.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ public static <K, V> ScanIterator<KeyValue<K, V>> hscan(RedisHashCommands<K, V>
return hscan(commands, key, Optional.empty());
}

/**
* Sequentially iterate over keys in a hash identified by {@code key}. This method uses {@code HSCAN NOVALUES} to perform an
* iterative scan.
*
* @param commands the commands interface, must not be {@code null}.
* @param key the hash to scan.
* @param <K> Key type.
* @param <V> Value type.
* @return a new {@link ScanIterator}.
* @since 7.0
*/
public static <K, V> ScanIterator<K> hscanNovalues(RedisHashCommands<K, V> commands, K key) {
return hscanNovalues(commands, key, Optional.empty());
}

/**
* Sequentially iterate over entries in a hash identified by {@code key}. This method uses {@code HSCAN} to perform an
* iterative scan.
Expand All @@ -122,6 +137,25 @@ public static <K, V> ScanIterator<KeyValue<K, V>> hscan(RedisHashCommands<K, V>
return hscan(commands, key, Optional.of(scanArgs));
}

/**
* Sequentially iterate over keys in a hash identified by {@code key}. This method uses {@code HSCAN NOVALUES} to perform an
* iterative scan.
*
* @param commands the commands interface, must not be {@code null}.
* @param key the hash to scan.
* @param scanArgs the scan arguments, must not be {@code null}.
* @param <K> Key type.
* @param <V> Value type.
* @return a new {@link ScanIterator}.
* @since 7.0
*/
public static <K, V> ScanIterator<K> hscanNovalues(RedisHashCommands<K, V> commands, K key, ScanArgs scanArgs) {

LettuceAssert.notNull(scanArgs, "ScanArgs must not be null");

return hscanNovalues(commands, key, Optional.of(scanArgs));
}

private static <K, V> ScanIterator<KeyValue<K, V>> hscan(RedisHashCommands<K, V> commands, K key,
Optional<ScanArgs> scanArgs) {

Expand Down Expand Up @@ -151,6 +185,35 @@ private MapScanCursor<K, V> getNextScanCursor(ScanCursor scanCursor) {
};
}

private static <K, V> ScanIterator<K> hscanNovalues(RedisHashCommands<K, V> commands, K key,
Optional<ScanArgs> scanArgs) {

LettuceAssert.notNull(commands, "RedisKeyCommands must not be null");
LettuceAssert.notNull(key, "Key must not be null");

return new SyncScanIterator<K>() {

@Override
protected ScanCursor nextScanCursor(ScanCursor scanCursor) {

KeyScanCursor<K> cursor = getNextScanCursor(scanCursor);
chunk = cursor.getKeys().iterator();
return cursor;
}

private KeyScanCursor<K> getNextScanCursor(ScanCursor scanCursor) {

if (scanCursor == null) {
return scanArgs.map(scanArgs -> commands.hscanNovalues(key, scanArgs)).orElseGet(() -> commands.hscanNovalues(key));
}

return scanArgs.map((scanArgs) -> commands.hscanNovalues(key, scanCursor, scanArgs))
.orElseGet(() -> commands.hscanNovalues(key, scanCursor));

Check warning on line 211 in src/main/java/io/lettuce/core/ScanIterator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/io/lettuce/core/ScanIterator.java#L210-L211

Added lines #L210 - L211 were not covered by tests
}

};
}

/**
* Sequentially iterate over elements in a set identified by {@code key}. This method uses {@code SSCAN} to perform an
* iterative scan.
Expand Down
Loading

0 comments on commit d98c07a

Please sign in to comment.