DATAREDIS-697 - Add support for BITPOS.
We now support the BITPOS command throughout the sync and reactive api. Original pull request: #335.
This commit is contained in:
committed by
Mark Paluch
parent
8215f68d9f
commit
e9a3a5b6d9
@@ -1160,6 +1160,16 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
|
||||
return convertAndReturn(delegate.bitOp(op, destination, keys), identityConverter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection#bitPos(byte[], boolean, org.springframework.data.domain.Range)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Long bitPos(byte[] key, boolean bit, org.springframework.data.domain.Range<Long> range) {
|
||||
return convertAndReturn(delegate.bitPos(key, bit, range), identityConverter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisPubSubCommands#subscribe(org.springframework.data.redis.connection.MessageListener, byte[][])
|
||||
@@ -2470,6 +2480,16 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
|
||||
return bitOp(op, serialize(destination), serializeMulti(keys));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection#bitPos(java.lang.String, boolean, org.springframework.data.domain.Range)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Long bitPos(String key, boolean bit, org.springframework.data.domain.Range<Long> range) {
|
||||
return bitPos(serialize(key), bit, range);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection#subscribe(org.springframework.data.redis.connection.MessageListener, java.lang.String[])
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
@@ -385,6 +386,13 @@ public interface DefaultedRedisConnection extends RedisConnection {
|
||||
return stringCommands().bitOp(op, destination, keys);
|
||||
}
|
||||
|
||||
/** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */
|
||||
@Override
|
||||
@Deprecated
|
||||
default Long bitPos(byte[] key, boolean bit, org.springframework.data.domain.Range<Long> range) {
|
||||
return stringCommands().bitPos(key, bit, range);
|
||||
}
|
||||
|
||||
/** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */
|
||||
@Override
|
||||
@Deprecated
|
||||
|
||||
@@ -1014,6 +1014,80 @@ public interface ReactiveStringCommands {
|
||||
*/
|
||||
Flux<NumericResponse<BitOpCommand, Long>> bitOp(Publisher<BitOpCommand> commands);
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.1
|
||||
*/
|
||||
class BitPosCommand extends KeyCommand {
|
||||
|
||||
private boolean bit;
|
||||
private Range<Long> range;
|
||||
|
||||
private BitPosCommand(@Nullable ByteBuffer key, boolean bit, Range<Long> range) {
|
||||
super(key);
|
||||
this.bit = bit;
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
static BitPosCommand positionOf(boolean bit) {
|
||||
return new BitPosCommand(null, bit, Range.unbounded());
|
||||
}
|
||||
|
||||
public BitPosCommand in(ByteBuffer key) {
|
||||
return new BitPosCommand(key, bit, range);
|
||||
}
|
||||
|
||||
public BitPosCommand within(Range<Long> range) {
|
||||
return new BitPosCommand(getKey(), bit, range);
|
||||
}
|
||||
|
||||
public boolean getBit() {
|
||||
return bit;
|
||||
}
|
||||
|
||||
public Range<Long> getRange() {
|
||||
return range;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the position of the first bit set to given {@code bit} in a string.
|
||||
*
|
||||
* @param key the key holding the actual String.
|
||||
* @param bit the bit value to look for.
|
||||
* @return {@link Mono} emitting result when ready.
|
||||
* @since 2.1
|
||||
*/
|
||||
default Mono<Long> bitPos(ByteBuffer key, boolean bit) {
|
||||
return bitPos(key, bit, Range.unbounded());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the position of the first bit set to given {@code bit} in a string. {@link Range} start and end can contain
|
||||
* negative values in order to index <strong>bytes</strong> starting from the end of the string, where {@literal -1}
|
||||
* is the last byte, {@literal -2} is the penultimate.
|
||||
*
|
||||
* @param key the key holding the actual String.
|
||||
* @param bit the bit value to look for.
|
||||
* @param range must not be {@literal null}. Use {@link Range#unbounded()} to not limit search.
|
||||
* @return {@link Mono} emitting result when ready.
|
||||
* @since 2.1
|
||||
*/
|
||||
default Mono<Long> bitPos(ByteBuffer key, boolean bit, Range<Long> range) {
|
||||
return bitPos(Mono.just(BitPosCommand.positionOf(bit).in(key).within(range))).next()
|
||||
.map(NumericResponse::getOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emmit the the position of the first bit set to given {@code bit} in a string. Get the length of the value stored at
|
||||
* {@literal key}.
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return {@link Flux} emitting results when ready.
|
||||
* @since 2.1
|
||||
*/
|
||||
Flux<NumericResponse<BitPosCommand, Long>> bitPos(Publisher<BitPosCommand> commands);
|
||||
|
||||
/**
|
||||
* Get the length of the value stored at {@literal key}.
|
||||
*
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.redis.connection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -292,6 +293,37 @@ public interface RedisStringCommands {
|
||||
@Nullable
|
||||
Long bitOp(BitOperation op, byte[] destination, byte[]... keys);
|
||||
|
||||
/**
|
||||
* Return the position of the first bit set to given {@code bit} in a string.
|
||||
*
|
||||
* @param key the key holding the actual String.
|
||||
* @param bit the bit value to look for.
|
||||
* @return {@literal null} when used in pipeline / transaction. The position of the first bit set to 1 or 0 according
|
||||
* to the request.
|
||||
* @see <a href="http://redis.io/commands/bitpos">Redis Documentation: BITPOS</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default Long bitPos(byte[] key, boolean bit) {
|
||||
return bitPos(key, bit, Range.unbounded());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the position of the first bit set to given {@code bit} in a string. {@link Range} start and end can contain
|
||||
* negative values in order to index <strong>bytes</strong> starting from the end of the string, where {@literal -1}
|
||||
* is the last byte, {@literal -2} is the penultimate.
|
||||
*
|
||||
* @param key the key holding the actual String.
|
||||
* @param bit the bit value to look for.
|
||||
* @param range must not be {@literal null}. Use {@link Range#unbounded()} to not limit search.
|
||||
* @return {@literal null} when used in pipeline / transaction. The position of the first bit set to 1 or 0 according
|
||||
* to the request.
|
||||
* @see <a href="http://redis.io/commands/bitpos">Redis Documentation: BITPOS</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
Long bitPos(byte[] key, boolean bit, Range<Long> range);
|
||||
|
||||
/**
|
||||
* Get the length of the value stored at {@code key}.
|
||||
*
|
||||
|
||||
@@ -580,6 +580,37 @@ public interface StringRedisConnection extends RedisConnection {
|
||||
*/
|
||||
Long bitOp(BitOperation op, String destination, String... keys);
|
||||
|
||||
/**
|
||||
* Return the position of the first bit set to given {@code bit} in a string.
|
||||
*
|
||||
* @param key the key holding the actual String.
|
||||
* @param bit the bit value to look for.
|
||||
* @return {@literal null} when used in pipeline / transaction. The position of the first bit set to 1 or 0 according
|
||||
* to the request.
|
||||
* @see <a href="http://redis.io/commands/bitpos">Redis Documentation: BITPOS</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
default Long bitPos(String key, boolean bit) {
|
||||
return bitPos(key, bit, org.springframework.data.domain.Range.unbounded());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the position of the first bit set to given {@code bit} in a string.
|
||||
* {@link org.springframework.data.domain.Range} start and end can contain negative values in order to index
|
||||
* <strong>bytes</strong> starting from the end of the string, where {@literal -1} is the last byte, {@literal -2} is
|
||||
* the penultimate.
|
||||
*
|
||||
* @param key the key holding the actual String.
|
||||
* @param bit the bit value to look for.
|
||||
* @param range must not be {@literal null}. Use {@link Range#unbounded()} to not limit search.
|
||||
* @return {@literal null} when used in pipeline / transaction. The position of the first bit set to 1 or 0 according
|
||||
* to the request.
|
||||
* @see <a href="http://redis.io/commands/bitpos">Redis Documentation: BITPOS</a>
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
Long bitPos(String key, boolean bit, org.springframework.data.domain.Range<Long> range);
|
||||
|
||||
/**
|
||||
* Get the length of the value stored at {@code key}.
|
||||
*
|
||||
|
||||
@@ -19,6 +19,7 @@ import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import redis.clients.jedis.BinaryJedis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -26,11 +27,13 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.connection.jedis.JedisClusterConnection.JedisClusterCommandCallback;
|
||||
import org.springframework.data.redis.connection.jedis.JedisClusterConnection.JedisMultiKeyClusterCommandCallback;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConverters;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.data.redis.util.ByteUtils;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -489,6 +492,29 @@ class JedisClusterStringCommands implements RedisStringCommands {
|
||||
throw new InvalidDataAccessApiUsageException("BITOP is only supported for same slot keys in cluster mode.");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#bitPos(byte[], boolean, org.springframework.data.Range)
|
||||
*/
|
||||
@Override
|
||||
public Long bitPos(byte[] key, boolean bit, Range<Long> range) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(range, "Range must not be null! Use Range.unbounded() instead.");
|
||||
|
||||
List<byte[]> args = new ArrayList<>(3);
|
||||
args.add(LettuceConverters.toBit(bit));
|
||||
|
||||
if (range.getLowerBound().isBounded()) {
|
||||
args.add(range.getLowerBound().getValue().map(LettuceConverters::toBytes).get());
|
||||
}
|
||||
if (range.getUpperBound().isBounded()) {
|
||||
args.add(range.getUpperBound().getValue().map(LettuceConverters::toBytes).get());
|
||||
}
|
||||
|
||||
return Long.class.cast(connection.execute("BITPOS", key, args));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#strLen(byte[])
|
||||
|
||||
@@ -17,14 +17,17 @@ package org.springframework.data.redis.connection.jedis;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import redis.clients.jedis.BitPosParams;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@@ -713,6 +716,43 @@ class JedisStringCommands implements RedisStringCommands {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#bitOp(byte[], boolean, org.springframework.data.domain.Range)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Long bitPos(byte[] key, boolean bit, Range<Long> range) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(range, "Range must not be null! Use Range.unbounded() instead.");
|
||||
|
||||
BitPosParams params = null;
|
||||
if (range.getLowerBound().isBounded()) {
|
||||
params = range.getUpperBound().isBounded()
|
||||
? new BitPosParams(range.getLowerBound().getValue().get(), range.getUpperBound().getValue().get())
|
||||
: new BitPosParams(range.getLowerBound().getValue().get());
|
||||
}
|
||||
|
||||
try {
|
||||
if (isPipelined()) {
|
||||
|
||||
pipeline(connection.newJedisResult(params != null ? connection.getRequiredPipeline().bitpos(key, bit, params)
|
||||
: connection.getRequiredPipeline().bitpos(key, bit)));
|
||||
return null;
|
||||
}
|
||||
if (isQueueing()) {
|
||||
transaction(
|
||||
connection.newJedisResult(params != null ? connection.getRequiredTransaction().bitpos(key, bit, params)
|
||||
: connection.getRequiredTransaction().bitpos(key, bit)));
|
||||
return null;
|
||||
}
|
||||
return params != null ? connection.getJedis().bitpos(key, bit, params) : connection.getJedis().bitpos(key, bit);
|
||||
} catch (Exception ex) {
|
||||
throw convertJedisAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#strLen(byte[])
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
package org.springframework.data.redis.connection.lettuce;
|
||||
|
||||
import io.lettuce.core.SetArgs;
|
||||
import io.lettuce.core.codec.ByteArrayCodec;
|
||||
import io.lettuce.core.output.IntegerOutput;
|
||||
import io.lettuce.core.protocol.CommandArgs;
|
||||
import io.lettuce.core.protocol.CommandType;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -32,6 +36,8 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiVa
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.RangeCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveStringCommands;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceReactiveRedisConnection.ByteBufferCodec;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -320,9 +326,9 @@ class LettuceReactiveStringCommands implements ReactiveStringCommands {
|
||||
|
||||
Range<Long> range = command.getRange();
|
||||
|
||||
return (!Range.unbounded().equals(range) ? cmd.bitcount(command.getKey(), range.getLowerBound().getValue().orElse(null),
|
||||
range.getUpperBound().getValue().orElse(null)) : cmd.bitcount(command.getKey()))
|
||||
.map(responseValue -> new NumericResponse<>(command, responseValue));
|
||||
return (!Range.unbounded().equals(range) ? cmd.bitcount(command.getKey(),
|
||||
range.getLowerBound().getValue().orElse(null), range.getUpperBound().getValue().orElse(null))
|
||||
: cmd.bitcount(command.getKey())).map(responseValue -> new NumericResponse<>(command, responseValue));
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -366,6 +372,33 @@ class LettuceReactiveStringCommands implements ReactiveStringCommands {
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#bitPos(org.reactivestreams.Publisher)
|
||||
*/
|
||||
@Override
|
||||
public Flux<NumericResponse<BitPosCommand, Long>> bitPos(Publisher<BitPosCommand> commands) {
|
||||
|
||||
return connection.execute(cmd -> {
|
||||
|
||||
return Flux.from(commands).flatMap(command -> {
|
||||
|
||||
Mono<Long> result = cmd.bitpos(command.getKey(), command.getBit());
|
||||
|
||||
if (command.getRange().getLowerBound().isBounded()) {
|
||||
|
||||
result = cmd.bitpos(command.getKey(), command.getBit(), command.getRange().getLowerBound().getValue().get());
|
||||
|
||||
if (command.getRange().getUpperBound().isBounded()) {
|
||||
result = cmd.bitpos(command.getKey(), command.getBit(), command.getRange().getLowerBound().getValue().get(),
|
||||
command.getRange().getUpperBound().getValue().get());
|
||||
}
|
||||
}
|
||||
return result.map(respValue -> new NumericResponse<>(command, respValue));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveRedisConnection.ReactiveStringCommands#strLen(org.reactivestreams.Publisher)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.redis.connection.lettuce;
|
||||
|
||||
import io.lettuce.core.RedisFuture;
|
||||
import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
|
||||
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
|
||||
import lombok.NonNull;
|
||||
@@ -25,9 +26,11 @@ import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.domain.Range;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -666,6 +669,57 @@ class LettuceStringCommands implements RedisStringCommands {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#bitPos(byte[], boolean, org.springframework.data.domain.Range)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Long bitPos(byte[] key, boolean bit, Range<Long> range) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(range, "Range must not be null! Use Range.unbounded() instead.");
|
||||
|
||||
try {
|
||||
if (isPipelined() || isQueueing()) {
|
||||
|
||||
RedisFuture<Long> futureResult;
|
||||
if (range.getLowerBound().isBounded()) {
|
||||
if (range.getUpperBound().isBounded()) {
|
||||
futureResult = getAsyncConnection().bitpos(key, bit, range.getLowerBound().getValue().get(),
|
||||
range.getUpperBound().getValue().get());
|
||||
} else {
|
||||
|
||||
futureResult = getAsyncConnection().bitpos(key, bit, range.getLowerBound().getValue().get());
|
||||
}
|
||||
|
||||
} else {
|
||||
futureResult = getAsyncConnection().bitpos(key, bit);
|
||||
}
|
||||
|
||||
if (isPipelined()) {
|
||||
pipeline(connection.newLettuceResult(futureResult));
|
||||
}
|
||||
else if (isQueueing()) {
|
||||
transaction(connection.newLettuceResult(futureResult));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (range.getLowerBound().isBounded()) {
|
||||
if (range.getUpperBound().isBounded()) {
|
||||
return getConnection().bitpos(key, bit, range.getLowerBound().getValue().get(),
|
||||
range.getUpperBound().getValue().get());
|
||||
}
|
||||
return getConnection().bitpos(key, bit, range.getLowerBound().getValue().get());
|
||||
}
|
||||
|
||||
return getConnection().bitpos(key, bit);
|
||||
} catch (Exception ex) {
|
||||
throw convertLettuceAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#strLen(byte[])
|
||||
|
||||
@@ -41,7 +41,7 @@ public enum RedisCommand {
|
||||
BGSAVE("r", 0, 0), //
|
||||
BITCOUNT("r", 1, 3), //
|
||||
BITOP("rw", 3), //
|
||||
BITPOS("r", 2, 4), //
|
||||
BITPOS("r", 2), //
|
||||
BLPOP("rw", 2), //
|
||||
BRPOP("rw", 2), //
|
||||
BRPOPLPUSH("rw", 3), //
|
||||
|
||||
@@ -44,6 +44,7 @@ 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;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
@@ -70,6 +71,8 @@ import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.data.redis.core.types.RedisClientInfo;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.data.redis.test.util.HexStringUtils;
|
||||
import org.springframework.data.redis.test.util.RedisClientRule;
|
||||
import org.springframework.data.redis.test.util.RedisDriver;
|
||||
import org.springframework.data.redis.test.util.WithRedisDriver;
|
||||
@@ -2822,6 +2825,27 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
verifyResults(Arrays.asList(new Object[] { 0L }));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-697
|
||||
@IfProfileValue(name = "redisVersion", value = "2.8.7+")
|
||||
public void bitPosShouldReturnPositionCorrectly() {
|
||||
|
||||
actual.add(connection.set("bitpos-1".getBytes(), HexStringUtils.hexToBytes("fff000")));
|
||||
actual.add(connection.bitPos("bitpos-1", false));
|
||||
|
||||
verifyResults(Arrays.asList(new Object[] { true, 12L }));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-697
|
||||
@IfProfileValue(name = "redisVersion", value = "2.8.7+")
|
||||
public void bitPosShouldReturnPositionInRangeCorrectly() {
|
||||
|
||||
actual.add(connection.set("bitpos-1".getBytes(), HexStringUtils.hexToBytes("fff0f0")));
|
||||
actual.add(connection.bitPos("bitpos-1", true,
|
||||
org.springframework.data.domain.Range.of(Bound.inclusive(2L), Bound.unbounded())));
|
||||
|
||||
verifyResults(Arrays.asList(new Object[] { true, 16L }));
|
||||
}
|
||||
|
||||
protected void verifyResults(List<Object> expected) {
|
||||
assertEquals(expected, getResults());
|
||||
}
|
||||
@@ -2845,4 +2869,5 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
return (!connection.exists(key));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Range.Bound;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
@@ -59,6 +60,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.data.redis.test.util.HexStringUtils;
|
||||
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
|
||||
import org.springframework.data.redis.test.util.RedisClusterRule;
|
||||
import org.springframework.test.annotation.IfProfileValue;
|
||||
@@ -2271,4 +2273,23 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
assertThat(clusterConnection.keyCommands().unlink(KEY_1_BYTES), is(0L));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-697
|
||||
@IfProfileValue(name = "redisVersion", value = "2.8.7+")
|
||||
public void bitPosShouldReturnPositionCorrectly() {
|
||||
|
||||
nativeConnection.set(KEY_1_BYTES, HexStringUtils.hexToBytes("fff000"));
|
||||
|
||||
assertThat(clusterConnection.stringCommands().bitPos(KEY_1_BYTES, false), is(12L));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-697
|
||||
@IfProfileValue(name = "redisVersion", value = "2.8.7+")
|
||||
public void bitPosShouldReturnPositionInRangeCorrectly() {
|
||||
|
||||
nativeConnection.set(KEY_1_BYTES, HexStringUtils.hexToBytes("fff0f0"));
|
||||
|
||||
assertThat(clusterConnection.stringCommands().bitPos(KEY_1_BYTES, true,
|
||||
org.springframework.data.domain.Range.of(Bound.inclusive(2L), Bound.unbounded())), is(16L));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import io.lettuce.core.RedisURI.Builder;
|
||||
import io.lettuce.core.api.sync.RedisHLLCommands;
|
||||
import io.lettuce.core.cluster.RedisClusterClient;
|
||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||
import io.lettuce.core.codec.ByteArrayCodec;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.Duration;
|
||||
@@ -38,6 +39,7 @@ import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.domain.Range.Bound;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
@@ -54,6 +56,7 @@ import org.springframework.data.redis.connection.jedis.JedisConverters;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.data.redis.test.util.HexStringUtils;
|
||||
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
|
||||
import org.springframework.data.redis.test.util.RedisClusterRule;
|
||||
import org.springframework.test.annotation.IfProfileValue;
|
||||
@@ -89,6 +92,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
|
||||
RedisClusterClient client;
|
||||
RedisAdvancedClusterCommands<String, String> nativeConnection;
|
||||
RedisAdvancedClusterCommands<byte[], byte[]> binaryConnection;
|
||||
LettuceClusterConnection clusterConnection;
|
||||
|
||||
public static @ClassRule RedisClusterRule clusterAvailable = new RedisClusterRule();
|
||||
@@ -104,6 +108,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
client = RedisClusterClient.create(LettuceTestClientResources.getSharedClientResources(),
|
||||
Builder.redis(CLUSTER_HOST, MASTER_NODE_1_PORT).withTimeout(500, TimeUnit.MILLISECONDS).build());
|
||||
nativeConnection = client.connect().sync();
|
||||
binaryConnection = client.connect(ByteArrayCodec.INSTANCE).sync();
|
||||
clusterConnection = new LettuceClusterConnection(client);
|
||||
}
|
||||
|
||||
@@ -112,6 +117,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
|
||||
clusterConnection.serverCommands().flushDb();
|
||||
nativeConnection.getStatefulConnection().close();
|
||||
binaryConnection.getStatefulConnection().close();
|
||||
clusterConnection.close();
|
||||
client.shutdown(0, 0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
@@ -2294,4 +2300,21 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
public void unlinkReturnsZeroIfNoKeysTouched() {
|
||||
assertThat(clusterConnection.keyCommands().unlink(KEY_1_BYTES), is(0L));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-697
|
||||
public void bitPosShouldReturnPositionCorrectly() {
|
||||
|
||||
binaryConnection.set(KEY_1_BYTES, HexStringUtils.hexToBytes("fff000"));
|
||||
|
||||
assertThat(clusterConnection.stringCommands().bitPos(KEY_1_BYTES, false), is(12L));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-697
|
||||
public void bitPosShouldReturnPositionInRangeCorrectly() {
|
||||
|
||||
binaryConnection.set(KEY_1_BYTES, HexStringUtils.hexToBytes("fff0f0"));
|
||||
|
||||
assertThat(clusterConnection.stringCommands().bitPos(KEY_1_BYTES, true,
|
||||
org.springframework.data.domain.Range.of(Bound.inclusive(2L), Bound.unbounded())), is(16L));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceReactiveRedisConnection.ByteBufferCodec;
|
||||
import org.springframework.data.redis.test.util.LettuceRedisClientProvider;
|
||||
import org.springframework.data.redis.test.util.LettuceRedisClusterClientProvider;
|
||||
|
||||
@@ -75,10 +76,12 @@ public abstract class LettuceReactiveCommandsTestsBase {
|
||||
|
||||
@Parameterized.Parameter(value = 0) public LettuceConnectionProvider connectionProvider;
|
||||
@Parameterized.Parameter(value = 1) public LettuceConnectionProvider nativeConnectionProvider;
|
||||
@Parameterized.Parameter(value = 2) public Object displayName;
|
||||
@Parameterized.Parameter(value = 2) public LettuceConnectionProvider nativeBinaryConnectionProvider;
|
||||
@Parameterized.Parameter(value = 3) public Object displayName;
|
||||
|
||||
LettuceReactiveRedisConnection connection;
|
||||
RedisClusterCommands<String, String> nativeCommands;
|
||||
RedisClusterCommands<ByteBuffer, ByteBuffer> nativeBinaryCommands;
|
||||
|
||||
@Parameterized.Parameters(name = "{2}")
|
||||
public static List<Object[]> parameters() {
|
||||
@@ -92,11 +95,14 @@ public abstract class LettuceReactiveCommandsTestsBase {
|
||||
LettuceReactiveRedisConnection.CODEC);
|
||||
StandaloneConnectionProvider nativeConnectionProvider = new StandaloneConnectionProvider(standalone.getClient(),
|
||||
StringCodec.UTF8);
|
||||
StandaloneConnectionProvider nativeBinaryConnectionProvider = new StandaloneConnectionProvider(
|
||||
standalone.getClient(), ByteBufferCodec.INSTANCE);
|
||||
|
||||
parameters.add(new Object[] { standaloneProvider, nativeConnectionProvider, "Standalone" });
|
||||
parameters.add(
|
||||
new Object[] { standaloneProvider, nativeConnectionProvider, nativeBinaryConnectionProvider, "Standalone" });
|
||||
parameters.add(new Object[] {
|
||||
new LettucePoolingConnectionProvider(standaloneProvider, LettucePoolingClientConfiguration.builder().build()),
|
||||
nativeConnectionProvider, "Standalone/Pooled" });
|
||||
nativeConnectionProvider, nativeBinaryConnectionProvider, "Standalone/Pooled" });
|
||||
|
||||
if (cluster.test()) {
|
||||
|
||||
@@ -104,8 +110,11 @@ public abstract class LettuceReactiveCommandsTestsBase {
|
||||
LettuceReactiveRedisConnection.CODEC);
|
||||
ClusterConnectionProvider nativeClusterConnectionProvider = new ClusterConnectionProvider(cluster.getClient(),
|
||||
StringCodec.UTF8);
|
||||
ClusterConnectionProvider nativeBinaryClusterConnectionProvider = new ClusterConnectionProvider(
|
||||
cluster.getClient(), ByteBufferCodec.INSTANCE);
|
||||
|
||||
parameters.add(new Object[] { clusterProvider, nativeClusterConnectionProvider, "Cluster" });
|
||||
parameters.add(new Object[] { clusterProvider, nativeClusterConnectionProvider,
|
||||
nativeBinaryClusterConnectionProvider, "Cluster" });
|
||||
}
|
||||
|
||||
return parameters;
|
||||
@@ -116,11 +125,13 @@ public abstract class LettuceReactiveCommandsTestsBase {
|
||||
|
||||
if (nativeConnectionProvider instanceof StandaloneConnectionProvider) {
|
||||
nativeCommands = nativeConnectionProvider.getConnection(StatefulRedisConnection.class).sync();
|
||||
nativeBinaryCommands = nativeBinaryConnectionProvider.getConnection(StatefulRedisConnection.class).sync();
|
||||
this.connection = new LettuceReactiveRedisConnection(connectionProvider);
|
||||
|
||||
} else {
|
||||
ClusterConnectionProvider clusterConnectionProvider = (ClusterConnectionProvider) nativeConnectionProvider;
|
||||
nativeCommands = nativeConnectionProvider.getConnection(StatefulRedisClusterConnection.class).sync();
|
||||
nativeBinaryCommands = nativeBinaryConnectionProvider.getConnection(StatefulRedisClusterConnection.class).sync();
|
||||
this.connection = new LettuceReactiveRedisClusterConnection(connectionProvider,
|
||||
clusterConnectionProvider.getRedisClient());
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import static org.junit.Assume.*;
|
||||
import org.springframework.data.redis.util.ByteUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.MonoOperator;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -36,6 +37,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Range.Bound;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
@@ -43,7 +45,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiVa
|
||||
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.test.util.LettuceRedisClientProvider;
|
||||
import org.springframework.data.redis.test.util.HexStringUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
@@ -399,4 +401,22 @@ public class LettuceReactiveStringCommandsTests extends LettuceReactiveCommandsT
|
||||
.expectNext(7L) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-697
|
||||
public void bitPosShouldReturnPositionCorrectly() {
|
||||
|
||||
nativeBinaryCommands.set(KEY_1_BBUFFER, ByteBuffer.wrap(HexStringUtils.hexToBytes("fff000")));
|
||||
|
||||
StepVerifier.create(connection.stringCommands().bitPos(KEY_1_BBUFFER, false)).expectNext(12L).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-697
|
||||
public void bitPosShouldReturnPositionInRangeCorrectly() {
|
||||
|
||||
nativeBinaryCommands.set(KEY_1_BBUFFER, ByteBuffer.wrap(HexStringUtils.hexToBytes("fff0f0")));
|
||||
|
||||
StepVerifier.create(connection.stringCommands().bitPos(KEY_1_BBUFFER, true,
|
||||
org.springframework.data.domain.Range.of(Bound.inclusive(2L), Bound.unbounded()))).expectNext(16L).verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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.test.util;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utils for working with Hex Stings.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @currentRead Beyong the Shadows - Brent Weeks
|
||||
*/
|
||||
public class HexStringUtils {
|
||||
|
||||
/**
|
||||
* Convert a given HEX {@link String} to its byte representation.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static byte[] hexToBytes(String source) {
|
||||
|
||||
Assert.notNull(source, "Source must not be null!");
|
||||
int len = source.length();
|
||||
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(source.charAt(i), 16) << 4) + Character.digit(source.charAt(i + 1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user