diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java index 229612774..f2c7e2bca 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java @@ -106,6 +106,9 @@ abstract public class LettuceConverters extends Converters { public static final byte[] POSITIVE_INFINITY_BYTES; public static final byte[] NEGATIVE_INFINITY_BYTES; + private static final long INDEXED_RANGE_START = 0; + private static final long INDEXED_RANGE_END = -1; + static { DATE_TO_LONG = source -> source != null ? source.getTime() : null; @@ -868,6 +871,54 @@ abstract public class LettuceConverters extends Converters { return TRANSACTION_RESULT_UNWRAPPER; } + /** + * Return {@link Optional} lower bound from {@link Range}. + * + * @param range + * @param + * @return + * @since 2.0.9 + */ + static > Optional getLowerBound(org.springframework.data.domain.Range range) { + return range.getLowerBound().getValue(); + } + + /** + * Return {@link Optional} upper bound from {@link Range}. + * + * @param range + * @param + * @return + * @since 2.0.9 + */ + static > Optional getUpperBound(org.springframework.data.domain.Range range) { + return range.getUpperBound().getValue(); + } + + /** + * Return the lower bound index from {@link Range} or {@literal 0} (zero) if the lower range is not bounded to point + * to the first element. To be used with index-based commands such as {@code LRANGE}, {@code GETRANGE}. + * + * @param range + * @return the lower index bound value or {@literal 0} for the first element if not bounded. + * @since 2.0.9 + */ + static long getLowerBoundIndex(org.springframework.data.domain.Range range) { + return getLowerBound(range).orElse(INDEXED_RANGE_START); + } + + /** + * Return the upper bound index from {@link Range} or {@literal -1} (minus one) if the upper range is not bounded to + * point to the last element. To be used with index-based commands such as {@code LRANGE}, {@code GETRANGE}. + * + * @param range + * @return the upper index bound value or {@literal -1} for the last element if not bounded. + * @since 2.0.9 + */ + static long getUpperBoundIndex(org.springframework.data.domain.Range range) { + return getUpperBound(range).orElse(INDEXED_RANGE_END); + } + /** * @author Christoph Strobl * @since 1.8 diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveListCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveListCommands.java index c90b2b9a9..fa39f0e50 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveListCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveListCommands.java @@ -24,6 +24,7 @@ import java.util.Arrays; import org.reactivestreams.Publisher; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Range; import org.springframework.data.redis.connection.ReactiveListCommands; import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse; import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse; @@ -74,7 +75,7 @@ class LettuceReactiveListCommands implements ReactiveListCommands { String.format("%s PUSHX only allows one value!", command.getDirection())); } - Mono pushResult = null; + Mono pushResult; if (ObjectUtils.nullSafeEquals(Direction.RIGHT, command.getDirection())) { pushResult = command.getUpsert() @@ -117,8 +118,12 @@ class LettuceReactiveListCommands implements ReactiveListCommands { Assert.notNull(command.getKey(), "Key must not be null!"); Assert.notNull(command.getRange(), "Range must not be null!"); - Flux result = cmd.lrange(command.getKey(), command.getRange().getLowerBound().getValue().orElse(0L), - command.getRange().getUpperBound().getValue().orElse(Long.MAX_VALUE)); + Range range = command.getRange(); + + Flux result = cmd.lrange(command.getKey(), // + LettuceConverters.getLowerBoundIndex(range), // + LettuceConverters.getUpperBoundIndex(range)); + return Mono.just(new CommandResponse<>(command, result)); })); } @@ -135,9 +140,13 @@ class LettuceReactiveListCommands implements ReactiveListCommands { Assert.notNull(command.getKey(), "Key must not be null!"); Assert.notNull(command.getRange(), "Range must not be null!"); - return cmd - .ltrim(command.getKey(), command.getRange().getLowerBound().getValue().orElse(0L), - command.getRange().getUpperBound().getValue().orElse(Long.MAX_VALUE)) + Range range = command.getRange(); + + Mono result = cmd.ltrim(command.getKey(), // + LettuceConverters.getLowerBoundIndex(range), // + LettuceConverters.getUpperBoundIndex(range)); + + return result .map(LettuceConverters::stringToBoolean).map(value -> new BooleanResponse<>(command, value)); })); } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java index c0ec618b1..5215c2999 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java @@ -42,7 +42,8 @@ import org.springframework.util.Assert; */ class LettuceReactiveStringCommands implements ReactiveStringCommands { - private final static ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.wrap(new byte[0]); + private static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.wrap(new byte[0]); + private final LettuceReactiveRedisConnection connection; /** @@ -111,7 +112,7 @@ class LettuceReactiveStringCommands implements ReactiveStringCommands { Assert.notNull(command.getValue(), "Value must not be null!"); if (command.getExpiration().isPresent() || command.getOption().isPresent()) { - throw new IllegalArgumentException("Command must not define exipiration nor option for GETSET."); + throw new IllegalArgumentException("Command must not define expiration nor option for GETSET."); } return cmd.getset(command.getKey(), command.getValue()).map((value) -> new ByteBufferResponse<>(command, value)) @@ -248,8 +249,11 @@ class LettuceReactiveStringCommands implements ReactiveStringCommands { Range range = command.getRange(); - return cmd.getrange(command.getKey(), range.getLowerBound().getValue().orElse(0L), - range.getUpperBound().getValue().orElse(Long.MAX_VALUE)).map((value) -> new ByteBufferResponse<>(command, value)); + Mono result = cmd.getrange(command.getKey(), // + LettuceConverters.getLowerBoundIndex(range), // + LettuceConverters.getUpperBoundIndex(range)); + + return result.map((value) -> new ByteBufferResponse<>(command, value)); })); } @@ -319,8 +323,10 @@ class LettuceReactiveStringCommands implements ReactiveStringCommands { Range range = command.getRange(); - return (!Range.unbounded().equals(range) ? cmd.bitcount(command.getKey(), range.getLowerBound().getValue().orElse(0L), - range.getUpperBound().getValue().orElse(Long.MAX_VALUE)) : cmd.bitcount(command.getKey())) + return (!Range.unbounded().equals(range) + ? cmd.bitcount(command.getKey(), LettuceConverters.getLowerBoundIndex(range), // + LettuceConverters.getUpperBoundIndex(range)) + : cmd.bitcount(command.getKey())) .map(responseValue -> new NumericResponse<>(command, responseValue)); })); } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java index eb8ec00e4..bad7cd96c 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommands.java @@ -183,33 +183,31 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands { Flux result; + long start = LettuceConverters.getLowerBoundIndex(command.getRange()); + long stop = LettuceConverters.getUpperBoundIndex(command.getRange()); + if (ObjectUtils.nullSafeEquals(command.getDirection(), Direction.ASC)) { if (command.isWithScores()) { result = cmd - .zrangeWithScores(command.getKey(), command.getRange().getLowerBound().getValue().orElse(0L), - command.getRange().getUpperBound().getValue().orElse(Long.MAX_VALUE)) - .map(sc -> (Tuple) new DefaultTuple(getBytes(sc), sc.getScore())); + .zrangeWithScores(command.getKey(), start, stop).map(sc -> new DefaultTuple(getBytes(sc), sc.getScore())); } else { result = cmd - .zrange(command.getKey(), command.getRange().getLowerBound().getValue().orElse(0L), - command.getRange().getUpperBound().getValue().orElse(Long.MAX_VALUE)) - .map(value -> (Tuple) new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + .zrange(command.getKey(), start, stop) + .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); } } else { if (command.isWithScores()) { result = cmd - .zrevrangeWithScores(command.getKey(), command.getRange().getLowerBound().getValue().orElse(0L), - command.getRange().getUpperBound().getValue().orElse(Long.MAX_VALUE)) - .map(sc -> (Tuple) new DefaultTuple(getBytes(sc), sc.getScore())); + .zrevrangeWithScores(command.getKey(), start, stop) + .map(sc -> new DefaultTuple(getBytes(sc), sc.getScore())); } else { result = cmd - .zrevrange(command.getKey(), command.getRange().getLowerBound().getValue().orElse(0L), - command.getRange().getUpperBound().getValue().orElse(Long.MAX_VALUE)) - .map(value -> (Tuple) new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + .zrevrange(command.getKey(), start, stop) + .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); } } @@ -242,21 +240,21 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands { if (!isLimited) { result = cmd.zrangebyscoreWithScores(command.getKey(), range) - .map(sc -> (Tuple) new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); + .map(sc -> new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); } else { result = cmd .zrangebyscoreWithScores(command.getKey(), range, LettuceConverters.toLimit(command.getLimit().get())) - .map(sc -> (Tuple) new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); + .map(sc -> new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); } } else { if (!isLimited) { result = cmd.zrangebyscore(command.getKey(), range) - .map(value -> (Tuple) new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); } else { result = cmd.zrangebyscore(command.getKey(), range, LettuceConverters.toLimit(command.getLimit().get())) - .map(value -> (Tuple) new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); } } } else { @@ -267,23 +265,23 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands { if (!isLimited) { result = cmd.zrevrangebyscoreWithScores(command.getKey(), range) - .map(sc -> (Tuple) new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); + .map(sc -> new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); } else { result = cmd .zrevrangebyscoreWithScores(command.getKey(), range, LettuceConverters.toLimit(command.getLimit().get())) - .map(sc -> (Tuple) new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); + .map(sc -> new DefaultTuple(ByteUtils.getBytes(sc.getValue()), sc.getScore())); } } else { if (!isLimited) { result = cmd.zrevrangebyscore(command.getKey(), range) - .map(value -> (Tuple) new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); } else { result = cmd.zrevrangebyscore(command.getKey(), range, LettuceConverters.toLimit(command.getLimit().get())) - .map(value -> (Tuple) new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); + .map(value -> new DefaultTuple(ByteUtils.getBytes(value), Double.NaN)); } } } @@ -355,9 +353,11 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands { Assert.notNull(command.getKey(), "Key must not be null!"); Assert.notNull(command.getRange(), "Range must not be null!"); - return cmd - .zremrangebyrank(command.getKey(), command.getRange().getLowerBound().getValue().orElse(0L), - command.getRange().getUpperBound().getValue().orElse(Long.MAX_VALUE)) + Mono result = cmd.zremrangebyrank(command.getKey(), // + LettuceConverters.getLowerBoundIndex(command.getRange()), // + LettuceConverters.getUpperBoundIndex(command.getRange())); + + return result .map(value -> new NumericResponse<>(command, value)); })); } @@ -527,8 +527,8 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands { return (source) -> { Boolean inclusive = upper ? source.getUpperBound().isInclusive() : source.getLowerBound().isInclusive(); - Object value = upper ? source.getUpperBound().getValue().orElse(null) - : source.getLowerBound().getValue().orElse(null); + Object value = upper ? LettuceConverters.getUpperBound(source).orElse(null) + : LettuceConverters.getLowerBound(source).orElse(null); if (value == null) { return Boundary.unbounded(); diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveListCommandTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveListCommandTests.java index ccea4c841..719ac07f3 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveListCommandTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveListCommandTests.java @@ -20,9 +20,12 @@ import static org.hamcrest.core.Is.*; import static org.hamcrest.core.IsEqual.*; import static org.hamcrest.core.IsNot.*; import static org.junit.Assert.*; -import static org.junit.Assume.assumeThat; +import static org.junit.Assume.*; import static org.springframework.data.domain.Range.Bound.*; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + import java.nio.ByteBuffer; import java.time.Duration; import java.util.Arrays; @@ -36,8 +39,6 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection; import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse; import org.springframework.data.redis.connection.ReactiveRedisConnection.RangeCommand; import org.springframework.data.redis.connection.RedisListCommands.Position; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; /** * @author Christoph Strobl @@ -118,9 +119,7 @@ public class LettuceReactiveListCommandTests extends LettuceReactiveCommandsTest RangeCommand rangeCommand = RangeCommand.key(KEY_1_BBUFFER).within(Range.of(unbounded(), inclusive(1L))); StepVerifier.create(connection.listCommands().lRange(Mono.just(rangeCommand)).flatMap(CommandResponse::getOutput)) // - .expectNext(VALUE_1_BBUFFER) - .expectNext(VALUE_2_BBUFFER) - .verifyComplete(); + .expectNext(VALUE_1_BBUFFER).expectNext(VALUE_2_BBUFFER).verifyComplete(); } @Test // DATAREDIS-852 @@ -131,9 +130,7 @@ public class LettuceReactiveListCommandTests extends LettuceReactiveCommandsTest RangeCommand rangeCommand = RangeCommand.key(KEY_1_BBUFFER).within(Range.of(inclusive(1L), unbounded())); StepVerifier.create(connection.listCommands().lRange(Mono.just(rangeCommand)).flatMap(CommandResponse::getOutput)) // - .expectNext(VALUE_2_BBUFFER) - .expectNext(VALUE_3_BBUFFER) - .verifyComplete(); + .expectNext(VALUE_2_BBUFFER).expectNext(VALUE_3_BBUFFER).verifyComplete(); } @Test // DATAREDIS-525 diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsTests.java index f6a70833a..80b4b1d61 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsTests.java @@ -19,7 +19,6 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.junit.Assume.*; -import org.springframework.data.redis.util.ByteUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -47,6 +46,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.RangeCo import org.springframework.data.redis.connection.ReactiveStringCommands.SetCommand; import org.springframework.data.redis.connection.RedisStringCommands.BitOperation; import org.springframework.data.redis.core.types.Expiration; +import org.springframework.data.redis.util.ByteUtils; /** * @author Christoph Strobl @@ -297,7 +297,8 @@ public class LettuceReactiveStringCommandsTests extends LettuceReactiveCommandsT nativeCommands.set(KEY_1, VALUE_1); - RangeCommand rangeCommand = RangeCommand.key(KEY_1_BBUFFER).within(Range.of(Bound.unbounded(), Bound.inclusive(2L))); + RangeCommand rangeCommand = RangeCommand.key(KEY_1_BBUFFER) + .within(Range.of(Bound.unbounded(), Bound.inclusive(2L))); StepVerifier.create(connection.stringCommands().getRange(Mono.just(rangeCommand))) // .expectNext(new ReactiveRedisConnection.ByteBufferResponse<>(rangeCommand, ByteBuffer.wrap("val".getBytes()))) @@ -309,7 +310,8 @@ public class LettuceReactiveStringCommandsTests extends LettuceReactiveCommandsT nativeCommands.set(KEY_1, VALUE_1); - RangeCommand rangeCommand = RangeCommand.key(KEY_1_BBUFFER).within(Range.of(Bound.inclusive(0L), Bound.unbounded())); + RangeCommand rangeCommand = RangeCommand.key(KEY_1_BBUFFER) + .within(Range.of(Bound.inclusive(0L), Bound.unbounded())); StepVerifier.create(connection.stringCommands().getRange(Mono.just(rangeCommand))) // .expectNext(new ReactiveRedisConnection.ByteBufferResponse<>(rangeCommand, ByteBuffer.wrap(VALUE_1.getBytes()))) diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsTests.java index 455d165a6..326ba35c3 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsTests.java @@ -148,8 +148,9 @@ public class LettuceReactiveZSetCommandsTests extends LettuceReactiveCommandsTes nativeCommands.zadd(KEY_1, 2D, VALUE_2); nativeCommands.zadd(KEY_1, 3D, VALUE_3); - StepVerifier.create(connection.zSetCommands().zRangeByScore(KEY_1_BBUFFER, Range.of(Range.Bound.unbounded(), - Range.Bound.inclusive(3D)))) // + StepVerifier + .create(connection.zSetCommands().zRangeByScore(KEY_1_BBUFFER, + Range.of(Range.Bound.unbounded(), Range.Bound.inclusive(3D)))) // .expectNext(VALUE_1_BBUFFER, VALUE_2_BBUFFER, VALUE_3_BBUFFER) // .verifyComplete(); } @@ -161,8 +162,9 @@ public class LettuceReactiveZSetCommandsTests extends LettuceReactiveCommandsTes nativeCommands.zadd(KEY_1, 2D, VALUE_2); nativeCommands.zadd(KEY_1, 3D, VALUE_3); - StepVerifier.create(connection.zSetCommands().zRangeByScore(KEY_1_BBUFFER, Range.of(Range.Bound.inclusive(0D), - Range.Bound.unbounded()))) // + StepVerifier + .create(connection.zSetCommands().zRangeByScore(KEY_1_BBUFFER, + Range.of(Range.Bound.inclusive(0D), Range.Bound.unbounded()))) // .expectNext(VALUE_1_BBUFFER, VALUE_2_BBUFFER, VALUE_3_BBUFFER) // .verifyComplete(); }