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:
Christoph Strobl
2018-04-27 11:00:49 +02:00
committed by Mark Paluch
parent 8215f68d9f
commit e9a3a5b6d9
16 changed files with 472 additions and 9 deletions

View File

@@ -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[])

View File

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

View File

@@ -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}.
*

View File

@@ -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}.
*

View File

@@ -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}.
*

View File

@@ -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[])

View File

@@ -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[])

View File

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

View File

@@ -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[])

View File

@@ -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), //