DATAREDIS-1084 - Add support for XPENDING.

Original pull request: #512.
This commit is contained in:
Christoph Strobl
2020-02-17 10:53:14 +01:00
committed by Mark Paluch
parent 18d4ea1db4
commit ca01670c36
21 changed files with 1771 additions and 47 deletions

View File

@@ -129,4 +129,18 @@ public class RedisTestProfileValueSource implements ProfileValueSource {
}
return INSTANCE.get(key) != null ? INSTANCE.get(key).equals(value) : value == null;
}
public static boolean atLeast(String key, String value) {
if (INSTANCE == null) {
INSTANCE = new RedisTestProfileValueSource();
}
String current = INSTANCE.get(key);
if(current == null) {
return value == null;
}
return org.springframework.data.util.Version.parse(current).isGreaterThanOrEqualTo(org.springframework.data.util.Version.parse(value));
}
}

View File

@@ -42,7 +42,6 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Range.Bound;
@@ -68,6 +67,8 @@ import org.springframework.data.redis.connection.StringRedisConnection.StringTup
import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.PendingMessages;
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.connection.stream.StreamOffset;
@@ -3095,6 +3096,131 @@ public abstract class AbstractConnectionIntegrationTests {
assertThat(messages.get(1).getValue()).isEqualTo(Collections.singletonMap(KEY_2, VALUE_2));
}
@Test // DATAREDIS-1084
@IfProfileValue(name = "redisVersion", value = "5.0")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void xPendingShouldLoadOverviewCorrectly() {
actual.add(connection.xAdd(KEY_1, Collections.singletonMap(KEY_2, VALUE_2)));
actual.add(connection.xGroupCreate(KEY_1, ReadOffset.from("0"), "my-group"));
actual.add(connection.xReadGroupAsString(Consumer.from("my-group", "my-consumer"),
StreamOffset.create(KEY_1, ReadOffset.lastConsumed())));
actual.add(connection.xPending(KEY_1, "my-group"));
List<Object> results = getResults();
assertThat(results).hasSize(4);
PendingMessagesSummary info = (PendingMessagesSummary) results.get(3);
assertThat(info.getGroupName()).isEqualTo("my-group");
assertThat(info.getTotalPendingMessages()).isEqualTo(1L);
assertThat(info.getIdRange()).isNotNull();
assertThat(info.getPendingMessagesPerConsumer()).hasSize(1).containsEntry("my-consumer", 1L);
}
@Test // DATAREDIS-1084
@IfProfileValue(name = "redisVersion", value = "5.0")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void xPendingShouldLoadEmptyOverviewCorrectly() {
actual.add(connection.xAdd(KEY_1, Collections.singletonMap(KEY_2, VALUE_2)));
actual.add(connection.xGroupCreate(KEY_1, ReadOffset.from("0"), "my-group"));
actual.add(connection.xPending(KEY_1, "my-group"));
List<Object> results = getResults();
assertThat(results).hasSize(3);
PendingMessagesSummary info = (PendingMessagesSummary) results.get(2);
assertThat(info.getGroupName()).isEqualTo("my-group");
assertThat(info.getTotalPendingMessages()).isEqualTo(0L);
assertThat(info.getIdRange()).isNotNull();
assertThat(info.getPendingMessagesPerConsumer()).isEmpty();
}
@Test // DATAREDIS-1084
@IfProfileValue(name = "redisVersion", value = "5.0")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void xPendingShouldLoadPendingMessages() {
actual.add(connection.xAdd(KEY_1, Collections.singletonMap(KEY_2, VALUE_2)));
actual.add(connection.xGroupCreate(KEY_1, ReadOffset.from("0"), "my-group"));
actual.add(connection.xReadGroupAsString(Consumer.from("my-group", "my-consumer"),
StreamOffset.create(KEY_1, ReadOffset.lastConsumed())));
actual.add(connection.xPending(KEY_1, "my-group", org.springframework.data.domain.Range.open("-", "+"), 10L));
List<Object> results = getResults();
assertThat(results).hasSize(4);
PendingMessages pending = (PendingMessages) results.get(3);
assertThat(pending.size()).isOne();
assertThat(pending.get(0).getConsumerName()).isEqualTo("my-consumer");
assertThat(pending.get(0).getGroupName()).isEqualTo("my-group");
assertThat(pending.get(0).getTotalDeliveryCount()).isOne();
assertThat(pending.get(0).getStringId()).isNotNull();
}
@Test // DATAREDIS-1084
@IfProfileValue(name = "redisVersion", value = "5.0")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void xPendingShouldLoadPendingMessagesForConsumer() {
actual.add(connection.xAdd(KEY_1, Collections.singletonMap(KEY_2, VALUE_2)));
actual.add(connection.xGroupCreate(KEY_1, ReadOffset.from("0"), "my-group"));
actual.add(connection.xReadGroupAsString(Consumer.from("my-group", "my-consumer"),
StreamOffset.create(KEY_1, ReadOffset.lastConsumed())));
actual.add(connection.xPending(KEY_1, "my-group", "my-consumer", org.springframework.data.domain.Range.open("-", "+"), 10L));
List<Object> results = getResults();
assertThat(results).hasSize(4);
PendingMessages pending = (PendingMessages) results.get(3);
assertThat(pending.size()).isOne();
assertThat(pending.get(0).getConsumerName()).isEqualTo("my-consumer");
assertThat(pending.get(0).getGroupName()).isEqualTo("my-group");
assertThat(pending.get(0).getTotalDeliveryCount()).isOne();
assertThat(pending.get(0).getStringId()).isNotNull();
}
@Test // DATAREDIS-1084
@IfProfileValue(name = "redisVersion", value = "5.0")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void xPendingShouldLoadPendingMessagesForNonExistingConsumer() {
actual.add(connection.xAdd(KEY_1, Collections.singletonMap(KEY_2, VALUE_2)));
actual.add(connection.xGroupCreate(KEY_1, ReadOffset.from("0"), "my-group"));
actual.add(connection.xReadGroupAsString(Consumer.from("my-group", "my-consumer"),
StreamOffset.create(KEY_1, ReadOffset.lastConsumed())));
actual.add(connection.xPending(KEY_1, "my-group", "my-consumer-2", org.springframework.data.domain.Range.open("-", "+"), 10L));
List<Object> results = getResults();
assertThat(results).hasSize(4);
PendingMessages pending = (PendingMessages) results.get(3);
assertThat(pending.size()).isZero();
}
@Test // DATAREDIS-1084
@IfProfileValue(name = "redisVersion", value = "5.0")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void xPendingShouldLoadEmptyPendingMessages() {
actual.add(connection.xAdd(KEY_1, Collections.singletonMap(KEY_2, VALUE_2)));
actual.add(connection.xGroupCreate(KEY_1, ReadOffset.from("0"), "my-group"));
actual.add(connection.xPending(KEY_1, "my-group", org.springframework.data.domain.Range.open("-", "+"), 10L));
List<Object> results = getResults();
assertThat(results).hasSize(3);
PendingMessages pending = (PendingMessages) results.get(2);
assertThat(pending.size()).isZero();
}
protected void verifyResults(List<Object> expected) {
assertThat(getResults()).isEqualTo(expected);
}

View File

@@ -19,20 +19,20 @@ import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import io.lettuce.core.XReadArgs;
import org.junit.Ignore;
import org.springframework.data.redis.connection.stream.RecordId;
import reactor.test.StepVerifier;
import java.util.Collections;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.RedisTestProfileValueSource;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.connection.stream.StreamOffset;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
/**
* Integration tests for {@link LettuceReactiveStreamCommands}.
@@ -45,7 +45,7 @@ public class LettuceReactiveStreamCommandsTests extends LettuceReactiveCommandsT
public void before() {
// TODO: Upgrade to 5.0
assumeTrue(RedisTestProfileValueSource.matches("redisVersion", "4.9"));
assumeTrue(RedisTestProfileValueSource.atLeast("redisVersion", "4.9"));
}
@Test // DATAREDIS-864
@@ -179,9 +179,9 @@ public class LettuceReactiveStreamCommandsTests extends LettuceReactiveCommandsT
nativeCommands.xadd(KEY_1, Collections.singletonMap(KEY_2, VALUE_2));
connection.streamCommands().xGroupCreate(KEY_1_BBUFFER, "group-1", ReadOffset.latest()) //
.as(StepVerifier::create) //
.expectNext("OK") //
.verifyComplete();
.as(StepVerifier::create) //
.expectNext("OK") //
.verifyComplete();
}
@Test // DATAREDIS-864
@@ -190,7 +190,8 @@ public class LettuceReactiveStreamCommandsTests extends LettuceReactiveCommandsT
String id = nativeCommands.xadd(KEY_1, Collections.singletonMap(KEY_2, VALUE_2));
nativeCommands.xgroupCreate(XReadArgs.StreamOffset.from(KEY_1, id), "group-1");
nativeCommands.xreadgroup(io.lettuce.core.Consumer.from("group-1", "consumer-1"), XReadArgs.StreamOffset.from(KEY_1, id));
nativeCommands.xreadgroup(io.lettuce.core.Consumer.from("group-1", "consumer-1"),
XReadArgs.StreamOffset.from(KEY_1, id));
connection.streamCommands().xGroupDelConsumer(KEY_1_BBUFFER, Consumer.from("group-1", "consumer-1"))
.as(StepVerifier::create) //
@@ -204,12 +205,136 @@ public class LettuceReactiveStreamCommandsTests extends LettuceReactiveCommandsT
String id = nativeCommands.xadd(KEY_1, Collections.singletonMap(KEY_2, VALUE_2));
nativeCommands.xgroupCreate(XReadArgs.StreamOffset.from(KEY_1, id), "group-1");
connection.streamCommands().xGroupDestroy(KEY_1_BBUFFER, "group-1")
.as(StepVerifier::create) //
connection.streamCommands().xGroupDestroy(KEY_1_BBUFFER, "group-1").as(StepVerifier::create) //
.expectNext("OK") //
.verifyComplete();
}
@Test // DATAREDIS-1084
public void xPendingShouldLoadOverviewCorrectly() {
String initialMessage = nativeCommands.xadd(KEY_1, KEY_1, VALUE_1);
nativeCommands.xgroupCreate(XReadArgs.StreamOffset.from(KEY_1, initialMessage), "my-group");
nativeCommands.xadd(KEY_1, KEY_2, VALUE_2);
connection.streamCommands()
.xReadGroup(Consumer.from("my-group", "my-consumer"),
StreamOffset.create(KEY_1_BBUFFER, ReadOffset.lastConsumed())) //
.then().as(StepVerifier::create) //
.verifyComplete();
nativeCommands.xadd(KEY_1, KEY_2, VALUE_2);
connection.streamCommands().xPending(KEY_1_BBUFFER, "my-group").as(StepVerifier::create).assertNext(it -> {
assertThat(it.getGroupName()).isEqualTo("my-group");
assertThat(it.getTotalPendingMessages()).isEqualTo(1L);
assertThat(it.getIdRange()).isNotNull();
assertThat(it.getPendingMessagesPerConsumer()).hasSize(1).containsEntry("my-consumer", 1L);
}).verifyComplete();
}
@Test // DATAREDIS-1084
public void xPendingShouldLoadEmptyOverviewCorrectly() {
String initialMessage = nativeCommands.xadd(KEY_1, KEY_1, VALUE_1);
nativeCommands.xgroupCreate(XReadArgs.StreamOffset.from(KEY_1, initialMessage), "my-group");
nativeCommands.xadd(KEY_1, KEY_2, VALUE_2);
connection.streamCommands().xPending(KEY_1_BBUFFER, "my-group").as(StepVerifier::create).assertNext(it -> {
assertThat(it.getGroupName()).isEqualTo("my-group");
assertThat(it.getTotalPendingMessages()).isEqualTo(0L);
assertThat(it.getIdRange()).isNotNull();
assertThat(it.getPendingMessagesPerConsumer()).isEmpty();
}).verifyComplete();
}
@Test // DATAREDIS-1084
public void xPendingShouldLoadPendingMessages() {
String initialMessage = nativeCommands.xadd(KEY_1, KEY_1, VALUE_1);
nativeCommands.xgroupCreate(XReadArgs.StreamOffset.from(KEY_1, initialMessage), "my-group");
nativeCommands.xadd(KEY_1, KEY_2, VALUE_2);
connection.streamCommands()
.xReadGroup(Consumer.from("my-group", "my-consumer"),
StreamOffset.create(KEY_1_BBUFFER, ReadOffset.lastConsumed())) //
.then().as(StepVerifier::create) //
.verifyComplete();
connection.streamCommands().xPending(KEY_1_BBUFFER, "my-group", Range.open("-", "+"), 10L).as(StepVerifier::create)
.assertNext(it -> {
assertThat(it.size()).isOne();
assertThat(it.get(0).getConsumerName()).isEqualTo("my-consumer");
assertThat(it.get(0).getGroupName()).isEqualTo("my-group");
assertThat(it.get(0).getTotalDeliveryCount()).isOne();
assertThat(it.get(0).getStringId()).isNotNull();
}).verifyComplete();
}
@Test // DATAREDIS-1084
public void xPendingShouldLoadPendingMessagesForConsumer() {
String initialMessage = nativeCommands.xadd(KEY_1, KEY_1, VALUE_1);
nativeCommands.xgroupCreate(XReadArgs.StreamOffset.from(KEY_1, initialMessage), "my-group");
nativeCommands.xadd(KEY_1, KEY_2, VALUE_2);
connection.streamCommands()
.xReadGroup(Consumer.from("my-group", "my-consumer"),
StreamOffset.create(KEY_1_BBUFFER, ReadOffset.lastConsumed())) //
.then().as(StepVerifier::create) //
.verifyComplete();
connection.streamCommands().xPending(KEY_1_BBUFFER, "my-group", "my-consumer", Range.open("-", "+"), 10L).as(StepVerifier::create)
.assertNext(it -> {
assertThat(it.size()).isOne();
assertThat(it.get(0).getConsumerName()).isEqualTo("my-consumer");
assertThat(it.get(0).getGroupName()).isEqualTo("my-group");
assertThat(it.get(0).getTotalDeliveryCount()).isOne();
assertThat(it.get(0).getStringId()).isNotNull();
}).verifyComplete();
}
@Test // DATAREDIS-1084
public void xPendingShouldLoadPendingMessagesForNonExistingConsumer() {
String initialMessage = nativeCommands.xadd(KEY_1, KEY_1, VALUE_1);
nativeCommands.xgroupCreate(XReadArgs.StreamOffset.from(KEY_1, initialMessage), "my-group");
nativeCommands.xadd(KEY_1, KEY_2, VALUE_2);
connection.streamCommands()
.xReadGroup(Consumer.from("my-group", "my-consumer"),
StreamOffset.create(KEY_1_BBUFFER, ReadOffset.lastConsumed())) //
.then().as(StepVerifier::create) //
.verifyComplete();
connection.streamCommands().xPending(KEY_1_BBUFFER, "my-group", "my-consumer-2", Range.open("-", "+"), 10L).as(StepVerifier::create)
.assertNext(it -> {
assertThat(it.size()).isZero();
}).verifyComplete();
}
@Test // DATAREDIS-1084
public void xPendingShouldLoadEmptyPendingMessages() {
String initialMessage = nativeCommands.xadd(KEY_1, KEY_1, VALUE_1);
nativeCommands.xgroupCreate(XReadArgs.StreamOffset.from(KEY_1, initialMessage), "my-group");
nativeCommands.xadd(KEY_1, KEY_2, VALUE_2);
connection.streamCommands().xPending(KEY_1_BBUFFER, "my-group", Range.open("-", "+"), 10L).as(StepVerifier::create)
.assertNext(it -> {
assertThat(it.isEmpty()).isTrue();
}).verifyComplete();
}
}

View File

@@ -40,6 +40,7 @@ import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.RecordId;
@@ -322,4 +323,53 @@ public class DefaultReactiveStreamOperationsTests<K, HK, HV> {
.expectNext(2L) //
.verifyComplete();
}
@Test // DATAREDIS-1084
public void pendingShouldReadMessageSummary() {
K key = keyFactory.instance();
HK hashKey = hashKeyFactory.instance();
HV value = valueFactory.instance();
streamOperations.add(key, Collections.singletonMap(hashKey, value)).then().as(StepVerifier::create)
.verifyComplete();
streamOperations.createGroup(key, ReadOffset.from("0-0"), "my-group").then().as(StepVerifier::create)
.verifyComplete();
streamOperations.read(Consumer.from("my-group", "my-consumer"), StreamOffset.create(key, ReadOffset.lastConsumed()))
.then().as(StepVerifier::create).verifyComplete();
streamOperations.pending(key, "my-group").as(StepVerifier::create).assertNext(pending -> {
assertThat(pending.getTotalPendingMessages()).isOne();
assertThat(pending.getGroupName()).isEqualTo("my-group");
}).verifyComplete();
}
@Test // DATAREDIS-1084
public void pendingShouldReadMessageDetails() {
K key = keyFactory.instance();
HK hashKey = hashKeyFactory.instance();
HV value = valueFactory.instance();
streamOperations.add(key, Collections.singletonMap(hashKey, value)).then().as(StepVerifier::create)
.verifyComplete();
streamOperations.createGroup(key, ReadOffset.from("0-0"), "my-group").then().as(StepVerifier::create)
.verifyComplete();
streamOperations.read(Consumer.from("my-group", "my-consumer"), StreamOffset.create(key, ReadOffset.lastConsumed()))
.then().as(StepVerifier::create).verifyComplete();
streamOperations.pending(key, "my-group", Range.unbounded(), 10L).as(StepVerifier::create).assertNext(pending -> {
assertThat(pending).hasSize(1);
assertThat(pending.get(0).getGroupName()).isEqualTo("my-group");
assertThat(pending.get(0).getConsumerName()).isEqualTo("my-consumer");
assertThat(pending.get(0).getTotalDeliveryCount()).isOne();
}).verifyComplete();
}
}

View File

@@ -36,6 +36,8 @@ import org.springframework.data.redis.ObjectFactory;
import org.springframework.data.redis.Person;
import org.springframework.data.redis.RedisTestProfileValueSource;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.PendingMessages;
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.ObjectRecord;
@@ -72,7 +74,7 @@ public class DefaultStreamOperationsTests<K, HK, HV> {
// See https://github.com/xetorthio/jedis/issues/1820
assumeTrue(redisTemplate.getConnectionFactory() instanceof LettuceConnectionFactory);
assumeTrue(RedisTestProfileValueSource.matches("redisVersion", "5.0"));
assumeTrue(RedisTestProfileValueSource.atLeast("redisVersion", "5.0"));
this.redisTemplate = redisTemplate;
this.keyFactory = keyFactory;
@@ -322,4 +324,44 @@ public class DefaultStreamOperationsTests<K, HK, HV> {
streamOps.add(key, Collections.singletonMap(hashKey, value));
assertThat(streamOps.size(key)).isEqualTo(2);
}
@Test // DATAREDIS-1084
public void pendingShouldReadMessageSummary() {
K key = keyFactory.instance();
HK hashKey = hashKeyFactory.instance();
HV value = hashValueFactory.instance();
RecordId messageId = streamOps.add(key, Collections.singletonMap(hashKey, value));
streamOps.createGroup(key, ReadOffset.from("0-0"), "my-group");
streamOps.read(Consumer.from("my-group", "my-consumer"),
StreamOffset.create(key, ReadOffset.lastConsumed()));
PendingMessagesSummary pending = streamOps.pending(key, "my-group");
assertThat(pending.getTotalPendingMessages()).isOne();
assertThat(pending.getGroupName()).isEqualTo("my-group");
}
@Test // DATAREDIS-1084
public void pendingShouldReadMessageDetails() {
K key = keyFactory.instance();
HK hashKey = hashKeyFactory.instance();
HV value = hashValueFactory.instance();
RecordId messageId = streamOps.add(key, Collections.singletonMap(hashKey, value));
streamOps.createGroup(key, ReadOffset.from("0-0"), "my-group");
streamOps.read(Consumer.from("my-group", "my-consumer"),
StreamOffset.create(key, ReadOffset.lastConsumed()));
PendingMessages pending = streamOps.pending(key, "my-group", Range.unbounded(), 10L);
assertThat(pending).hasSize(1);
assertThat(pending.get(0).getGroupName()).isEqualTo("my-group");
assertThat(pending.get(0).getConsumerName()).isEqualTo("my-consumer");
assertThat(pending.get(0).getTotalDeliveryCount()).isOne();
}
}