Add support for type using the SCAN command.

We now support the type argument for Keyspace scanning through KeyScanOptions.type.

Closes: #2089
Original Pull Request: #2109
This commit is contained in:
Mark Paluch
2021-07-01 15:30:09 +02:00
committed by Christoph Strobl
parent 085aca9f60
commit 674401c986
7 changed files with 228 additions and 11 deletions

View File

@@ -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.KeyScanOptions;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -268,6 +269,20 @@ public interface ReactiveKeyCommands {
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 the {@link Flux} emitting {@link ByteBuffer keys} one by one.
* @throws IllegalArgumentException when options is {@literal null}.
* @see <a href="https://redis.io/commands/scan">Redis Documentation: SCAN</a>
* @since 2.6
*/
default Flux<ByteBuffer> scan(KeyScanOptions options) {
return scan((ScanOptions) options);
}
/**
* 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.

View File

@@ -21,6 +21,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.KeyScanOptions;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -128,6 +129,18 @@ public interface RedisKeyCommands {
@Nullable
Set<byte[]> keys(byte[] pattern);
/**
* Use a {@link Cursor} to iterate over keys.
*
* @param options must not be {@literal null}.
* @return never {@literal null}.
* @since 2.4
* @see <a href="https://redis.io/commands/scan">Redis Documentation: SCAN</a>
*/
default Cursor<byte[]> scan(KeyScanOptions options) {
return scan((ScanOptions) options);
}
/**
* Use a {@link Cursor} to iterate over keys.
*

View File

@@ -32,6 +32,7 @@ import org.springframework.data.redis.connection.ValueEncoding;
import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.KeyScanOptions;
import org.springframework.data.redis.core.ScanCursor;
import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
@@ -171,6 +172,10 @@ class JedisKeyCommands implements RedisKeyCommands {
*/
public Cursor<byte[]> scan(long cursorId, ScanOptions options) {
if (options instanceof KeyScanOptions && ((KeyScanOptions) options).getType() != null) {
throw new UnsupportedOperationException("'SCAN' with type is not yet supported using the Jedis driver");
}
return new ScanCursor<byte[]>(cursorId, options) {
@Override

View File

@@ -65,6 +65,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.KeyScanOptions;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
@@ -906,7 +907,7 @@ public abstract class LettuceConverters extends Converters {
return null;
}
ScanArgs scanArgs = new ScanArgs();
KeyScanArgs scanArgs = new KeyScanArgs();
byte[] pattern = options.getBytePattern();
if (pattern != null) {
@@ -917,6 +918,10 @@ public abstract class LettuceConverters extends Converters {
scanArgs.limit(options.getCount());
}
if (options instanceof KeyScanOptions) {
scanArgs.type(((KeyScanOptions) options).getType());
}
return scanArgs;
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2014-2021 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
*
* https://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.core;
import java.util.StringJoiner;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Options to be used for with {@literal SCAN} commands.
*
* @author Mark Paluch
* @since 2.6
*/
public class KeyScanOptions extends ScanOptions {
/**
* Constant to apply default {@link KeyScanOptions} without setting a limit or matching a pattern.
*/
public static KeyScanOptions NONE = new KeyScanOptions(null, null, null, null);
private final @Nullable String type;
private KeyScanOptions(@Nullable Long count, @Nullable String pattern, @Nullable byte[] bytePattern,
@Nullable String type) {
super(count, pattern, bytePattern);
this.type = type;
}
/**
* Static factory method that returns a new {@link KeyScanOptionsBuilder}.
*
* @return
*/
public static KeyScanOptionsBuilder scanOptions() {
return new KeyScanOptionsBuilder();
}
@Nullable
public String getType() {
return type;
}
@Override
public String toOptionString() {
if (this.equals(KeyScanOptions.NONE)) {
return "";
}
StringJoiner joiner = new StringJoiner(", ").add(super.toOptionString());
if (StringUtils.hasText(type)) {
joiner.add("'type' '" + type + "'");
}
return joiner.toString();
}
public static class KeyScanOptionsBuilder extends ScanOptionsBuilder {
private @Nullable String type;
private KeyScanOptionsBuilder() {}
/**
* Returns the current {@link KeyScanOptionsBuilder} configured with the given {@code count}.
*
* @param count
* @return
*/
@Override
public KeyScanOptionsBuilder count(long count) {
super.count(count);
return this;
}
/**
* Returns the current {@link KeyScanOptionsBuilder} configured with the given {@code pattern}.
*
* @param pattern
* @return
*/
@Override
public KeyScanOptionsBuilder match(String pattern) {
super.match(pattern);
return this;
}
/**
* Returns the current {@link KeyScanOptionsBuilder} configured with the given {@code pattern}.
*
* @param pattern
* @return
*/
@Override
public KeyScanOptionsBuilder match(byte[] pattern) {
super.match(pattern);
return this;
}
/**
* Returns the current {@link KeyScanOptionsBuilder} configured with the given {@code type}.
*
* @param type
* @return
*/
public KeyScanOptionsBuilder type(String type) {
this.type = type;
return this;
}
/**
* Builds a new {@link KeyScanOptions} objects.
*
* @return a new {@link KeyScanOptions} objects.
*/
@Override
public KeyScanOptions build() {
return new KeyScanOptions(count, pattern, bytePattern, type);
}
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.redis.core;
import java.util.StringJoiner;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
@@ -25,6 +27,7 @@ import org.springframework.util.StringUtils;
* @author Thomas Darimont
* @author Mark Paluch
* @since 1.4
* @see KeyScanOptions
*/
public class ScanOptions {
@@ -37,14 +40,14 @@ public class ScanOptions {
private final @Nullable String pattern;
private final @Nullable byte[] bytePattern;
private ScanOptions(@Nullable Long count, @Nullable String pattern, @Nullable byte[] bytePattern) {
ScanOptions(@Nullable Long count, @Nullable String pattern, @Nullable byte[] bytePattern) {
this.count = count;
this.pattern = pattern;
this.bytePattern = bytePattern;
}
/**
* Static factory method that returns a new {@link ScanOptionsBuilder}.
* Static factory method that returns a new {@link ScanOptionsBuilder}.
*
* @return
*/
@@ -83,17 +86,18 @@ public class ScanOptions {
return "";
}
String params = "";
StringJoiner joiner = new StringJoiner(", ");
if (this.count != null) {
params += (", 'count', " + count);
if (this.getCount() != null) {
joiner.add("'count' " + this.getCount());
}
String pattern = getPattern();
if (StringUtils.hasText(pattern)) {
params += (", 'match' , '" + this.pattern + "'");
joiner.add("'match' '" + pattern + "'");
}
return params;
return joiner.toString();
}
/**
@@ -103,10 +107,11 @@ public class ScanOptions {
*/
public static class ScanOptionsBuilder {
private @Nullable Long count;
private @Nullable String pattern;
private @Nullable byte[] bytePattern;
@Nullable Long count;
@Nullable String pattern;
@Nullable byte[] bytePattern;
ScanOptionsBuilder() {}
/**
* Returns the current {@link ScanOptionsBuilder} configured with the given {@code count}.