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 630603de1..0354d0762 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 @@ -113,6 +113,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; @@ -962,6 +965,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 973232c30..063661639 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 @@ -45,7 +45,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; /** @@ -115,7 +116,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)) @@ -252,8 +253,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)); })); } @@ -324,7 +328,8 @@ 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)) + 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 af2bc8d45..374b7f510 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 @@ -186,33 +186,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)); } } @@ -245,21 +243,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 { @@ -270,23 +268,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)); } } } @@ -377,9 +375,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)); })); } @@ -546,8 +546,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/LettuceReactiveZSetCommandsTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveZSetCommandsTests.java index 24ab322a6..58963bba6 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 @@ -150,8 +150,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(); } @@ -163,8 +164,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(); }