
io.lettuce.core.ScanIterator Maven / Gradle / Ivy
Show all versions of lettuce-core Show documentation
package io.lettuce.core;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import io.lettuce.core.api.sync.RedisHashCommands;
import io.lettuce.core.api.sync.RedisKeyCommands;
import io.lettuce.core.api.sync.RedisSetCommands;
import io.lettuce.core.api.sync.RedisSortedSetCommands;
import io.lettuce.core.internal.LettuceAssert;
/**
* Scan command support exposed through {@link Iterator}.
*
* {@link ScanIterator} uses synchronous command interfaces to scan over keys ({@code SCAN}), sets ({@code SSCAN}), sorted sets
* ({@code ZSCAN}), and hashes ({@code HSCAN}). A {@link ScanIterator} is stateful and not thread-safe. Instances can be used
* only once to iterate over results.
*
* Use {@link ScanArgs#limit(long)} to set the batch size.
*
* Data structure scanning is progressive and stateful and demand-aware. It supports full iterations (until all received cursors
* are exhausted) and premature termination. Subsequent scan commands to fetch the cursor data get only issued if the caller
* signals demand by consuming the {@link ScanIterator}.
*
* @param Element type
* @author Mark Paluch
* @since 4.4
*/
public abstract class ScanIterator implements Iterator {
private ScanIterator() {
}
/**
* Sequentially iterate over keys in the keyspace. This method uses {@code SCAN} to perform an iterative scan.
*
* @param commands the commands interface, must not be {@code null}.
* @param Key type.
* @param Value type.
* @return a new {@link ScanIterator}.
*/
public static ScanIterator scan(RedisKeyCommands commands) {
return scan(commands, Optional.empty());
}
/**
* Sequentially iterate over keys in the keyspace. This method uses {@code SCAN} to perform an iterative scan.
*
* @param commands the commands interface, must not be {@code null}.
* @param scanArgs the scan arguments, must not be {@code null}.
* @param Key type.
* @param Value type.
* @return a new {@link ScanIterator}.
*/
public static ScanIterator scan(RedisKeyCommands commands, ScanArgs scanArgs) {
LettuceAssert.notNull(scanArgs, "ScanArgs must not be null");
return scan(commands, Optional.of(scanArgs));
}
private static ScanIterator scan(RedisKeyCommands commands, Optional scanArgs) {
LettuceAssert.notNull(commands, "RedisKeyCommands must not be null");
return new SyncScanIterator() {
@Override
protected ScanCursor nextScanCursor(ScanCursor scanCursor) {
KeyScanCursor cursor = getNextScanCursor(scanCursor);
chunk = cursor.getKeys().iterator();
return cursor;
}
private KeyScanCursor getNextScanCursor(ScanCursor scanCursor) {
if (scanCursor == null) {
return scanArgs.map(commands::scan).orElseGet(commands::scan);
}
return scanArgs.map((scanArgs) -> commands.scan(scanCursor, scanArgs))
.orElseGet(() -> commands.scan(scanCursor));
}
};
}
/**
* Sequentially iterate over entries in a hash identified by {@code key}. This method uses {@code HSCAN} to perform an
* iterative scan.
*
* @param commands the commands interface, must not be {@code null}.
* @param key the hash to scan.
* @param Key type.
* @param Value type.
* @return a new {@link ScanIterator}.
*/
public static ScanIterator> hscan(RedisHashCommands commands, K key) {
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 Key type.
* @param Value type.
* @return a new {@link ScanIterator}.
* @since 6.4
*/
public static ScanIterator hscanNovalues(RedisHashCommands 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.
*
* @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 Key type.
* @param Value type.
* @return a new {@link ScanIterator}.
*/
public static ScanIterator> hscan(RedisHashCommands commands, K key, ScanArgs scanArgs) {
LettuceAssert.notNull(scanArgs, "ScanArgs must not be null");
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 Key type.
* @param Value type.
* @return a new {@link ScanIterator}.
* @since 6.4
*/
public static ScanIterator hscanNovalues(RedisHashCommands commands, K key, ScanArgs scanArgs) {
LettuceAssert.notNull(scanArgs, "ScanArgs must not be null");
return hscanNovalues(commands, key, Optional.of(scanArgs));
}
private static ScanIterator> hscan(RedisHashCommands commands, K key,
Optional scanArgs) {
LettuceAssert.notNull(commands, "RedisKeyCommands must not be null");
LettuceAssert.notNull(key, "Key must not be null");
return new SyncScanIterator>() {
@Override
protected ScanCursor nextScanCursor(ScanCursor scanCursor) {
MapScanCursor cursor = getNextScanCursor(scanCursor);
chunk = cursor.getMap().keySet().stream().map(k -> KeyValue.fromNullable(k, cursor.getMap().get(k))).iterator();
return cursor;
}
private MapScanCursor getNextScanCursor(ScanCursor scanCursor) {
if (scanCursor == null) {
return scanArgs.map(scanArgs -> commands.hscan(key, scanArgs)).orElseGet(() -> commands.hscan(key));
}
return scanArgs.map((scanArgs) -> commands.hscan(key, scanCursor, scanArgs))
.orElseGet(() -> commands.hscan(key, scanCursor));
}
};
}
private static ScanIterator hscanNovalues(RedisHashCommands commands, K key, Optional scanArgs) {
LettuceAssert.notNull(commands, "RedisKeyCommands must not be null");
LettuceAssert.notNull(key, "Key must not be null");
return new SyncScanIterator() {
@Override
protected ScanCursor nextScanCursor(ScanCursor scanCursor) {
KeyScanCursor cursor = getNextScanCursor(scanCursor);
chunk = cursor.getKeys().iterator();
return cursor;
}
private KeyScanCursor 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));
}
};
}
/**
* Sequentially iterate over elements in a set identified by {@code key}. This method uses {@code SSCAN} to perform an
* iterative scan.
*
* @param commands the commands interface, must not be {@code null}.
* @param key the set to scan.
* @param Key type.
* @param Value type.
* @return a new {@link ScanIterator}.
*/
public static ScanIterator sscan(RedisSetCommands commands, K key) {
return sscan(commands, key, Optional.empty());
}
/**
* Sequentially iterate over elements in a set identified by {@code key}. This method uses {@code SSCAN} to perform an
* iterative scan.
*
* @param commands the commands interface, must not be {@code null}.
* @param key the set to scan.
* @param scanArgs the scan arguments, must not be {@code null}.
* @param Key type.
* @param Value type.
* @return a new {@link ScanIterator}.
*/
public static ScanIterator sscan(RedisSetCommands commands, K key, ScanArgs scanArgs) {
LettuceAssert.notNull(scanArgs, "ScanArgs must not be null");
return sscan(commands, key, Optional.of(scanArgs));
}
private static ScanIterator sscan(RedisSetCommands commands, K key, Optional scanArgs) {
LettuceAssert.notNull(commands, "RedisKeyCommands must not be null");
LettuceAssert.notNull(key, "Key must not be null");
return new SyncScanIterator() {
@Override
protected ScanCursor nextScanCursor(ScanCursor scanCursor) {
ValueScanCursor cursor = getNextScanCursor(scanCursor);
chunk = cursor.getValues().iterator();
return cursor;
}
private ValueScanCursor getNextScanCursor(ScanCursor scanCursor) {
if (scanCursor == null) {
return scanArgs.map(scanArgs -> commands.sscan(key, scanArgs)).orElseGet(() -> commands.sscan(key));
}
return scanArgs.map((scanArgs) -> commands.sscan(key, scanCursor, scanArgs))
.orElseGet(() -> commands.sscan(key, scanCursor));
}
};
}
/**
* Sequentially iterate over scored values in a sorted set identified by {@code key}. This method uses {@code ZSCAN} to
* perform an iterative scan.
*
* @param commands the commands interface, must not be {@code null}.
* @param key the sorted set to scan.
* @param Key type.
* @param Value type.
* @return a new {@link ScanIterator}.
*/
public static ScanIterator> zscan(RedisSortedSetCommands commands, K key) {
return zscan(commands, key, Optional.empty());
}
/**
* Sequentially iterate over scored values in a sorted set identified by {@code key}. This method uses {@code ZSCAN} to
* perform an iterative scan.
*
* @param commands the commands interface, must not be {@code null}.
* @param key the sorted set to scan.
* @param scanArgs the scan arguments, must not be {@code null}.
* @param Key type.
* @param Value type.
* @return a new {@link ScanIterator}.
*/
public static ScanIterator> zscan(RedisSortedSetCommands commands, K key, ScanArgs scanArgs) {
LettuceAssert.notNull(scanArgs, "ScanArgs must not be null");
return zscan(commands, key, Optional.of(scanArgs));
}
private static ScanIterator> zscan(RedisSortedSetCommands commands, K key,
Optional scanArgs) {
LettuceAssert.notNull(commands, "RedisKeyCommands must not be null");
LettuceAssert.notNull(key, "Key must not be null");
return new SyncScanIterator>() {
@Override
protected ScanCursor nextScanCursor(ScanCursor scanCursor) {
ScoredValueScanCursor cursor = getNextScanCursor(scanCursor);
chunk = cursor.getValues().iterator();
return cursor;
}
private ScoredValueScanCursor getNextScanCursor(ScanCursor scanCursor) {
if (scanCursor == null) {
return scanArgs.map(scanArgs -> commands.zscan(key, scanArgs)).orElseGet(() -> commands.zscan(key));
}
return scanArgs.map((scanArgs) -> commands.zscan(key, scanCursor, scanArgs))
.orElseGet(() -> commands.zscan(key, scanCursor));
}
};
}
/**
* Returns a sequential {@code Stream} with this {@link ScanIterator} as its source.
*
* @return a {@link Stream} for this {@link ScanIterator}.
*/
public Stream stream() {
return StreamSupport.stream(Spliterators.spliterator(this, -1, 0), false);
}
/**
* Synchronous {@link ScanIterator} implementation.
*
* @param
*/
private static abstract class SyncScanIterator extends ScanIterator {
private ScanCursor scanCursor;
protected Iterator chunk = null;
@Override
public boolean hasNext() {
while (scanCursor == null || !scanCursor.isFinished()) {
if (scanCursor == null || !hasChunkElements()) {
scanCursor = nextScanCursor(scanCursor);
}
if (hasChunkElements()) {
return true;
}
}
return hasChunkElements();
}
private boolean hasChunkElements() {
return chunk.hasNext();
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return chunk.next();
}
protected abstract ScanCursor nextScanCursor(ScanCursor scanCursor);
}
}