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
@@ -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