DATAREDIS-562 - Add support for BITFIELD.
We now offer support for BITFIELD via RedisConnection and RedisClusterConnection using Lettuce and Jedis. connection.bitField(key, create() .set(INT_8).valueAt(offset(0).multipliedByTypeLength()).to(100) .incr(signed(8)).valueAt(offset(102)).overflow(FAIL).by(1)); Original pull request: #227.
This commit is contained in:
committed by
Mark Paluch
parent
e631352832
commit
41db6a10fe
@@ -13,6 +13,7 @@
|
||||
|BGREWRITEAOF |X
|
||||
|BGSAVE |X
|
||||
|BITCOUNT |X
|
||||
|BITFIELD |X
|
||||
|BITOP |X
|
||||
|BLPOP |X
|
||||
|BRPOP |X
|
||||
|
||||
@@ -3657,6 +3657,29 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
|
||||
return convertedResults;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#bitfield(byte[], BitfieldCommand)
|
||||
*/
|
||||
@Override
|
||||
public List<Long> bitfield(byte[] key, BitfieldCommand operation) {
|
||||
return delegate.bitfield(key, operation);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection#bitfield(byte[], BitfieldCommand)
|
||||
*/
|
||||
@Override
|
||||
public List<Long> bitfield(String key, BitfieldCommand operation) {
|
||||
|
||||
List<Long> results = delegate.bitfield(serialize(key), operation);
|
||||
if (isFutureConversion()) {
|
||||
addResultConverter(identityConverter);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.DecoratedRedisConnection#getDelegate()
|
||||
|
||||
@@ -401,6 +401,13 @@ public interface DefaultedRedisConnection extends RedisConnection {
|
||||
return stringCommands().bitCount(key, start, end);
|
||||
}
|
||||
|
||||
/** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */
|
||||
@Override
|
||||
@Deprecated
|
||||
default List<Long> bitfield(byte[] key, BitfieldCommand operation) {
|
||||
return stringCommands().bitfield(key, operation);
|
||||
}
|
||||
|
||||
/** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */
|
||||
@Override
|
||||
@Deprecated
|
||||
|
||||
@@ -15,11 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.redis.connection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
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.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -312,7 +316,7 @@ public interface RedisStringCommands {
|
||||
* 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.
|
||||
@@ -334,6 +338,18 @@ public interface RedisStringCommands {
|
||||
@Nullable
|
||||
Long strLen(byte[] key);
|
||||
|
||||
/**
|
||||
* Get / Manipulate specific integer fields of varying bit widths and arbitrary non (necessary) aligned offset stored
|
||||
* at a given {@code key}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param operation must not be {@literal null}.
|
||||
* @return {@literal null} when used in pipeline / transaction.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> bitfield(byte[] key, BitfieldCommand operation);
|
||||
|
||||
/**
|
||||
* {@code SET} command arguments for {@code NX}, {@code XX}.
|
||||
*
|
||||
@@ -390,4 +406,581 @@ public interface RedisStringCommands {
|
||||
return SET_IF_ABSENT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual {@code BITFIELD} command representation holding several {@link BitfieldSubCommand}s to execute.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.8
|
||||
*/
|
||||
class BitfieldCommand {
|
||||
|
||||
private final List<BitfieldSubCommand> subCommands;
|
||||
|
||||
private BitfieldCommand(List<BitfieldSubCommand> subCommands) {
|
||||
|
||||
this.subCommands = new ArrayList<BitfieldSubCommand>(
|
||||
subCommands != null ? subCommands : Collections.<BitfieldSubCommand> emptyList());
|
||||
}
|
||||
|
||||
private BitfieldCommand(List<BitfieldSubCommand> subCommands, BitfieldSubCommand subCommand) {
|
||||
|
||||
this(subCommands);
|
||||
|
||||
Assert.notNull(subCommand, "SubCommand must not be null!");
|
||||
this.subCommands.add(subCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link BitfieldCommand}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static BitfieldCommand newBitfieldCommand() {
|
||||
return new BitfieldCommand(Collections.<BitfieldSubCommand> emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a new {@link BitfieldGetBuilder} for creating and adding a {@link BitfieldGet} sub command.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public BitfieldGetBuilder get(BitfieldType type) {
|
||||
return new BitfieldGetBuilder(this).forType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link BitfieldCommand} adding given {@link BitfieldGet} to the sub commands.
|
||||
*
|
||||
* @param get must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected BitfieldCommand get(BitfieldGet get) {
|
||||
return new BitfieldCommand(subCommands, get);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a new {@link BitfieldSetBuilder} for creating and adding a {@link BitfieldSet} sub command.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public BitfieldSetBuilder set(BitfieldType type) {
|
||||
return new BitfieldSetBuilder(this).forType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link BitfieldCommand} adding given {@link BitfieldSet} to the sub commands.
|
||||
*
|
||||
* @param get must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected BitfieldCommand set(BitfieldSet set) {
|
||||
return new BitfieldCommand(subCommands, set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a new {@link BitfieldIncrByBuilder} for creating and adding a {@link BitfieldIncrby} sub command.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public BitfieldIncrByBuilder incr(BitfieldType type) {
|
||||
return new BitfieldIncrByBuilder(this).forType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link BitfieldCommand} adding given {@link BitfieldIncrBy} to the sub commands.
|
||||
*
|
||||
* @param get must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected BitfieldCommand incr(BitfieldIncrBy incrBy) {
|
||||
return new BitfieldCommand(subCommands, incrBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link List} of sub commands.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public List<BitfieldSubCommand> getSubCommands() {
|
||||
return subCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class BitfieldSetBuilder {
|
||||
|
||||
private BitfieldCommand ref;
|
||||
|
||||
BitfieldSet set = new BitfieldSet();
|
||||
|
||||
private BitfieldSetBuilder(BitfieldCommand ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
public BitfieldSetBuilder forType(BitfieldType type) {
|
||||
this.set.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the zero based bit {@literal offset}.
|
||||
*
|
||||
* @param offset must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public BitfieldSetBuilder valueAt(Long offset) {
|
||||
return valueAt(Offset.offset(offset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bit offset.
|
||||
*
|
||||
* @param offset must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public BitfieldSetBuilder valueAt(Offset offset) {
|
||||
|
||||
Assert.notNull(offset, "Offset must not be null!");
|
||||
|
||||
this.set.offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public BitfieldCommand to(Long value) {
|
||||
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
|
||||
this.set.value = value;
|
||||
return ref.set(this.set);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class BitfieldGetBuilder {
|
||||
|
||||
private BitfieldCommand ref;
|
||||
|
||||
BitfieldGet get = new BitfieldGet();
|
||||
|
||||
private BitfieldGetBuilder(BitfieldCommand ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
public BitfieldGetBuilder forType(BitfieldType type) {
|
||||
this.get.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the zero based bit {@literal offset}.
|
||||
*
|
||||
* @param offset must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public BitfieldCommand valueAt(Long offset) {
|
||||
return valueAt(Offset.offset(offset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bit offset.
|
||||
*
|
||||
* @param offset must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public BitfieldCommand valueAt(Offset offset) {
|
||||
|
||||
Assert.notNull(offset, "Offset must not be null!");
|
||||
|
||||
this.get.offset = offset;
|
||||
return ref.get(this.get);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class BitfieldIncrByBuilder {
|
||||
|
||||
private BitfieldCommand ref;
|
||||
|
||||
BitfieldIncrBy incrBy = new BitfieldIncrBy();
|
||||
|
||||
private BitfieldIncrByBuilder(BitfieldCommand ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
public BitfieldIncrByBuilder forType(BitfieldType type) {
|
||||
this.incrBy.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the zero based bit {@literal offset}.
|
||||
*
|
||||
* @param offset must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public BitfieldIncrByBuilder valueAt(Long offset) {
|
||||
return valueAt(Offset.offset(offset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bit offset.
|
||||
*
|
||||
* @param offset must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public BitfieldIncrByBuilder valueAt(Offset offset) {
|
||||
|
||||
Assert.notNull(offset, "Offset must not be null!");
|
||||
this.incrBy.offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link BitfieldIncrBy.Overflow} to be used for this and any subsequent {@link BitfieldIncrBy} commands.
|
||||
*
|
||||
* @param overflow
|
||||
* @return
|
||||
*/
|
||||
public BitfieldIncrByBuilder overflow(BitfieldIncrBy.Overflow overflow) {
|
||||
this.incrBy.overflow = overflow;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value used for increasing.
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public BitfieldCommand by(Long value) {
|
||||
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
|
||||
this.incrBy.value = value;
|
||||
return ref.incr(this.incrBy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub command to be used as part of {@link BitfieldCommand}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.8
|
||||
*/
|
||||
interface BitfieldSubCommand {
|
||||
|
||||
/**
|
||||
* The actual sub command
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
String getCommand();
|
||||
|
||||
/**
|
||||
* The {@link BitfieldType} to apply for the command.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
BitfieldType getType();
|
||||
|
||||
/**
|
||||
* The bit offset to apply for the command.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
Offset getOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset used inside a {@link BitfieldSubCommand}. Can be zero or type based. See
|
||||
* <a href="http://redis.io/commands/bitfield#bits-and-positional-offsets">Bits and positional offsets</a> in the
|
||||
* Redis reference.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.8
|
||||
*/
|
||||
class Offset {
|
||||
|
||||
private final Long offset;
|
||||
private final Boolean zeroBased;
|
||||
|
||||
private Offset(Long offset, Boolean zeroBased) {
|
||||
|
||||
Assert.notNull(offset, "Offset must not be null!");
|
||||
Assert.notNull(zeroBased, "ZeroBased must not be null!");
|
||||
|
||||
this.offset = offset;
|
||||
this.zeroBased = zeroBased;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new zero based offset. <br />
|
||||
* <b>NOTE:</b> change to type based offset by calling {@link #multipliedByTypeLength()}.
|
||||
*
|
||||
* @param offset must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Offset offset(Long offset) {
|
||||
return new Offset(offset, Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new type based offset.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Offset multipliedByTypeLength() {
|
||||
return new Offset(offset, Boolean.FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if offset starts at 0 and is not multiplied by the type length.
|
||||
*/
|
||||
public Boolean isZeroBased() {
|
||||
return zeroBased;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the actual offset value
|
||||
*/
|
||||
public Long getValue() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Redis Command representation
|
||||
*/
|
||||
public String asString() {
|
||||
return (isZeroBased() ? "" : "#") + getValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual Redis bitfield type representation for signed and unsigned integers used with
|
||||
* {@link BitfieldSubCommand}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.8
|
||||
*/
|
||||
class BitfieldType {
|
||||
|
||||
/** 8 bit signed Integer */
|
||||
public static final BitfieldType INT_8 = new BitfieldType(Boolean.TRUE, 8);
|
||||
|
||||
/** 16 bit signed Integer */
|
||||
public static final BitfieldType INT_16 = new BitfieldType(Boolean.TRUE, 16);
|
||||
|
||||
/** 32 bit signed Integer */
|
||||
public static final BitfieldType INT_32 = new BitfieldType(Boolean.TRUE, 32);
|
||||
|
||||
/** 64 bit signed Integer */
|
||||
public static final BitfieldType INT_64 = new BitfieldType(Boolean.TRUE, 64);
|
||||
|
||||
/** 8 bit unsigned Integer */
|
||||
public static final BitfieldType UINT_8 = new BitfieldType(Boolean.FALSE, 8);
|
||||
|
||||
/** 16 bit unsigned Integer */
|
||||
public static final BitfieldType UINT_16 = new BitfieldType(Boolean.FALSE, 16);
|
||||
|
||||
/** 32 bit unsigned Integer */
|
||||
public static final BitfieldType UINT_32 = new BitfieldType(Boolean.FALSE, 32);
|
||||
|
||||
/** 64 bit unsigned Integer */
|
||||
public static final BitfieldType UINT_64 = new BitfieldType(Boolean.FALSE, 64);
|
||||
|
||||
private final Boolean signed;
|
||||
private final Integer bits;
|
||||
|
||||
private BitfieldType(Boolean signed, Integer bits) {
|
||||
|
||||
Assert.notNull(signed, "Signed must not be null!");
|
||||
Assert.notNull(bits, "Bits must not be null!");
|
||||
|
||||
this.signed = signed;
|
||||
this.bits = bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new signed {@link BitfieldType}.
|
||||
*
|
||||
* @param bits must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static BitfieldType signed(Integer bits) {
|
||||
return new BitfieldType(Boolean.TRUE, bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new unsigned {@link BitfieldType}.
|
||||
*
|
||||
* @param bits must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static BitfieldType unsigned(Integer bits) {
|
||||
return new BitfieldType(Boolean.FALSE, bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if {@link BitfieldType} is signed.
|
||||
*/
|
||||
public boolean isSigned() {
|
||||
return ObjectUtils.nullSafeEquals(signed, Boolean.TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual bits of the type.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Integer getBits() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Redis Command representation.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String asString() {
|
||||
return (isSigned() ? "i" : "u") + getBits();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
abstract class AbstractBitfieldSubCommand implements BitfieldSubCommand {
|
||||
|
||||
BitfieldType type;
|
||||
Offset offset;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection.BitfieldSubCommand#getType()
|
||||
*/
|
||||
@Override
|
||||
public BitfieldType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection.BitfieldSubCommand#getOffset()
|
||||
*/
|
||||
@Override
|
||||
public Offset getOffset() {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code SET} sub command used with {@link BitfieldCommand}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.8
|
||||
*/
|
||||
class BitfieldSet extends AbstractBitfieldSubCommand {
|
||||
|
||||
private Long value;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection.BitfieldSubCommand#getCommand()
|
||||
*/
|
||||
@Override
|
||||
public String getCommand() {
|
||||
return "SET";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value to set.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Long getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code GET} sub command used with {@link BitfieldCommand}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.8
|
||||
*/
|
||||
class BitfieldGet extends AbstractBitfieldSubCommand {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection.BitfieldSubCommand#getCommand()
|
||||
*/
|
||||
@Override
|
||||
public String getCommand() {
|
||||
return "GET";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code INCRBY} sub command used with {@link BitfieldCommand}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.8
|
||||
*/
|
||||
class BitfieldIncrBy extends AbstractBitfieldSubCommand {
|
||||
|
||||
private Long value;
|
||||
private Overflow overflow;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.StringRedisConnection.BitfieldSubCommand#getCommand()
|
||||
*/
|
||||
@Override
|
||||
public String getCommand() {
|
||||
return "INCRBY";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the increment value.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public Long getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the overflow to apply. Can be {@literal null} to use redis defaults.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
public Overflow getOverflow() {
|
||||
return overflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public enum Overflow {
|
||||
SAT, FAIL, WRAP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1945,4 +1945,14 @@ public interface StringRedisConnection extends RedisConnection {
|
||||
*/
|
||||
List<RedisClientInfo> getClientList();
|
||||
|
||||
|
||||
/**
|
||||
* Get / Manipulate specific integer fields of varying bit widths and arbitrary non (necessary) aligned offset stored
|
||||
* at a given {@code key}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param command must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
List<Long> bitfield(String key, BitfieldCommand command);
|
||||
}
|
||||
|
||||
@@ -469,6 +469,27 @@ class JedisClusterStringCommands implements RedisStringCommands {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#bitfield(byte[], BitfieldCommand)
|
||||
*/
|
||||
@Override
|
||||
public List<Long> bitfield(byte[] key, BitfieldCommand command) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(command, "Command must not be null!");
|
||||
|
||||
try {
|
||||
|
||||
List untypedListToAvoidClassCastErrorsSinceThisOneDeclaresListOfByteArrayButReturnsListOfLong = connection.getCluster()
|
||||
.bitfield(key, JedisConverters.toBitfieldCommandArguments(command));
|
||||
return (List<Long>) untypedListToAvoidClassCastErrorsSinceThisOneDeclaresListOfByteArrayButReturnsListOfLong;
|
||||
} catch (Exception ex) {
|
||||
throw convertJedisAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#bitOp(org.springframework.data.redis.connection.RedisStringCommands.BitOperation, byte[], byte[][])
|
||||
|
||||
@@ -103,6 +103,8 @@ abstract public class JedisConverters extends Converters {
|
||||
private static final ListConverter<redis.clients.jedis.GeoCoordinate, Point> LIST_GEO_COORDINATE_TO_POINT_CONVERTER;
|
||||
private static final Converter<byte[], String> BYTES_TO_STRING_CONVERTER;
|
||||
private static final ListConverter<byte[], String> BYTES_LIST_TO_STRING_LIST_CONVERTER;
|
||||
private static final ListConverter<byte[], Long> BYTES_LIST_TO_LONG_LIST_CONVERTER;
|
||||
private static final Converter<RedisStringCommands.BitfieldCommand, List<byte[]>> BITFIELD_COMMAND_ARGUMENT_CONVERTER;
|
||||
|
||||
public static final byte[] PLUS_BYTES;
|
||||
public static final byte[] MINUS_BYTES;
|
||||
@@ -191,6 +193,50 @@ abstract public class JedisConverters extends Converters {
|
||||
GEO_COORDINATE_TO_POINT_CONVERTER = geoCoordinate -> geoCoordinate != null
|
||||
? new Point(geoCoordinate.getLongitude(), geoCoordinate.getLatitude()) : null;
|
||||
LIST_GEO_COORDINATE_TO_POINT_CONVERTER = new ListConverter<>(GEO_COORDINATE_TO_POINT_CONVERTER);
|
||||
|
||||
BYTES_LIST_TO_LONG_LIST_CONVERTER = new ListConverter<byte[], Long>(new Converter<byte[], Long>() {
|
||||
@Override
|
||||
public Long convert(byte[] source) {
|
||||
return Long.valueOf(JedisConverters.toString(source));
|
||||
}
|
||||
});
|
||||
|
||||
BITFIELD_COMMAND_ARGUMENT_CONVERTER = new Converter<RedisStringCommands.BitfieldCommand, List<byte[]>>() {
|
||||
@Override
|
||||
public List<byte[]> convert(RedisStringCommands.BitfieldCommand source) {
|
||||
|
||||
if (source == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<byte[]> args = new ArrayList<byte[]>(source.getSubCommands().size() * 4);
|
||||
|
||||
for (RedisStringCommands.BitfieldSubCommand command : source.getSubCommands()) {
|
||||
|
||||
if (command instanceof RedisStringCommands.BitfieldIncrBy) {
|
||||
|
||||
RedisStringCommands.BitfieldIncrBy.Overflow overflow = ((RedisStringCommands.BitfieldIncrBy) command)
|
||||
.getOverflow();
|
||||
if (overflow != null) {
|
||||
args.add(JedisConverters.toBytes("OVERFLOW"));
|
||||
args.add(JedisConverters.toBytes(overflow.name()));
|
||||
}
|
||||
}
|
||||
|
||||
args.add(JedisConverters.toBytes(command.getCommand()));
|
||||
args.add(JedisConverters.toBytes(command.getType().asString()));
|
||||
args.add(JedisConverters.toBytes(command.getOffset().asString()));
|
||||
|
||||
if (command instanceof RedisStringCommands.BitfieldSet) {
|
||||
args.add(JedisConverters.toBytes(((RedisStringCommands.BitfieldSet) command).getValue()));
|
||||
} else if (command instanceof RedisStringCommands.BitfieldIncrBy) {
|
||||
args.add(JedisConverters.toBytes(((RedisStringCommands.BitfieldIncrBy) command).getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Converter<String, byte[]> stringToBytes() {
|
||||
@@ -548,6 +594,10 @@ abstract public class JedisConverters extends Converters {
|
||||
return BYTES_LIST_TO_STRING_LIST_CONVERTER;
|
||||
}
|
||||
|
||||
public static ListConverter<byte[], Long> getBytesListToLongListConverter() {
|
||||
return BYTES_LIST_TO_LONG_LIST_CONVERTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @since 1.8
|
||||
@@ -638,6 +688,20 @@ abstract public class JedisConverters extends Converters {
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert given {@link org.springframework.data.redis.connection.RedisStringCommands.BitfieldCommand} into argument
|
||||
* array.
|
||||
*
|
||||
* @param bitfieldOperation
|
||||
* @return never {@literal null}.
|
||||
* @since 1.8
|
||||
*/
|
||||
public static byte[][] toBitfieldCommandArguments(RedisStringCommands.BitfieldCommand bitfieldOperation) {
|
||||
|
||||
List<byte[]> tmp = BITFIELD_COMMAND_ARGUMENT_CONVERTER.convert(bitfieldOperation);
|
||||
return tmp.toArray(new byte[tmp.size()][]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 1.8
|
||||
|
||||
@@ -685,6 +685,40 @@ class JedisStringCommands implements RedisStringCommands {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#bitfield(byte[], BitfieldCommand)
|
||||
*/
|
||||
@Override
|
||||
public List<Long> bitfield(byte[] key, BitfieldCommand command) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(command, "Command must not be null!");
|
||||
|
||||
byte[][] args = JedisConverters.toBitfieldCommandArguments(command);
|
||||
|
||||
try {
|
||||
if (isPipelined()) {
|
||||
pipeline(new JedisResult(connection.getRequiredPipeline().bitfield(key, args)));
|
||||
return null;
|
||||
}
|
||||
if (isQueueing()) {
|
||||
transaction(new JedisResult(connection.getRequiredTransaction().bitfield(key, args)));
|
||||
return null;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw convertJedisAccessException(ex);
|
||||
}
|
||||
|
||||
// TODO: fix type declaration once https://github.com/xetorthio/jedis/issues/1413 is resolved
|
||||
// Jedis return type declaration is wrong. it indicates List<byte[]> but return List<Long> causing Class cast errors
|
||||
// when trying to convert that stuff. So we hacked it here to make it work.
|
||||
|
||||
List untypedListToAvoidClassCastErrorsSinceThisOneDeclaresListOfByteArrayButReturnsListOfLong = connection.getJedis().bitfield(key,
|
||||
args);
|
||||
return (List<Long>) untypedListToAvoidClassCastErrorsSinceThisOneDeclaresListOfByteArrayButReturnsListOfLong;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#bitOp(org.springframework.data.redis.connection.RedisStringCommands.BitOperation, byte[], byte[][])
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.redis.connection.lettuce;
|
||||
|
||||
import io.lettuce.core.BitFieldArgs;
|
||||
import io.lettuce.core.BitFieldArgs.Offset;
|
||||
import io.lettuce.core.RedisFuture;
|
||||
import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
|
||||
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
|
||||
@@ -603,6 +605,78 @@ class LettuceStringCommands implements RedisStringCommands {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#bitfield(byte[], BitfieldCommand)
|
||||
*/
|
||||
@Override
|
||||
public List<Long> bitfield(byte[] key, BitfieldCommand command) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
Assert.notNull(command, "Command must not be null!");
|
||||
|
||||
BitFieldArgs args = new BitFieldArgs();
|
||||
|
||||
for (BitfieldSubCommand subCommand : command.getSubCommands()) {
|
||||
|
||||
BitFieldArgs.BitFieldType bft = subCommand.getType().isSigned() ? BitFieldArgs.signed(subCommand.getType().getBits())
|
||||
: BitFieldArgs.unsigned(subCommand.getType().getBits());
|
||||
|
||||
BitFieldArgs.Offset offset;
|
||||
if (!subCommand.getOffset().isZeroBased()) {
|
||||
offset = BitFieldArgs.offset(subCommand.getOffset().getValue().intValue());
|
||||
}else{
|
||||
offset = BitFieldArgs.typeWidthBasedOffset(subCommand.getOffset().getValue().intValue());
|
||||
}
|
||||
|
||||
if (subCommand instanceof BitfieldGet) {
|
||||
args = args.get(bft, offset);
|
||||
} else if (subCommand instanceof BitfieldSet) {
|
||||
args = args.set(bft, offset, ((BitfieldSet) subCommand).getValue());
|
||||
} else if (subCommand instanceof BitfieldIncrBy) {
|
||||
|
||||
BitfieldIncrBy.Overflow overflow = ((BitfieldIncrBy) subCommand).getOverflow();
|
||||
if (overflow != null) {
|
||||
|
||||
BitFieldArgs.OverflowType type;
|
||||
|
||||
switch (overflow) {
|
||||
case SAT:
|
||||
type = BitFieldArgs.OverflowType.SAT;
|
||||
break;
|
||||
case FAIL:
|
||||
type = BitFieldArgs.OverflowType.FAIL;
|
||||
break;
|
||||
case WRAP:
|
||||
type = BitFieldArgs.OverflowType.WRAP;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
String.format("o_O invalid OVERFLOW. Expected one the following %s but got %s.",
|
||||
BitfieldIncrBy.Overflow.values(), overflow));
|
||||
}
|
||||
args = args.overflow(type);
|
||||
}
|
||||
|
||||
args = args.incrBy(bft, subCommand.getOffset().getValue().intValue(), ((BitfieldIncrBy) subCommand).getValue());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (isPipelined()) {
|
||||
pipeline(connection.newLettuceResult(getAsyncConnection().bitfield(key, args)));
|
||||
return null;
|
||||
}
|
||||
if (isQueueing()) {
|
||||
transaction(connection.newLettuceResult(getAsyncConnection().bitfield(key, args)));
|
||||
return null;
|
||||
}
|
||||
return getConnection().bitfield(key, args);
|
||||
} catch (Exception ex) {
|
||||
throw convertLettuceAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.RedisStringCommands#bitOp(org.springframework.data.redis.connection.RedisStringCommands.BitOperation, byte[], byte[][])
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -386,4 +387,20 @@ class DefaultValueOperations<K, V> extends AbstractOperations<K, V> implements V
|
||||
byte[] rawKey = rawKey(key);
|
||||
return execute(connection -> connection.getBit(rawKey, offset), true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ValueOperations#bitfield(Object, RedisStringCommands.BitfieldCommand)
|
||||
*/
|
||||
@Override
|
||||
public List<Long> bitfield(K key, final RedisStringCommands.BitfieldCommand command) {
|
||||
|
||||
final byte[] rawKey = rawKey(key);
|
||||
return execute(new RedisCallback<List<Long>>() {
|
||||
|
||||
public List<Long> doInRedis(RedisConnection connection) {
|
||||
return connection.bitfield(rawKey, command);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -347,6 +349,16 @@ public interface ValueOperations<K, V> {
|
||||
@Nullable
|
||||
Boolean getBit(K key, long offset);
|
||||
|
||||
RedisOperations<K, V> getOperations();
|
||||
/**
|
||||
* Get / Manipulate specific integer fields of varying bit widths and arbitrary non (necessary) aligned offset stored
|
||||
* at a given {@code key}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param command must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
List<Long> bitfield(K key, RedisStringCommands.BitfieldCommand command);
|
||||
|
||||
RedisOperations<K, V> getOperations();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.redis.connection;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.collection.IsCollectionWithSize.*;
|
||||
import static org.hamcrest.collection.IsEmptyCollection.*;
|
||||
import static org.hamcrest.collection.IsIterableContainingInOrder.*;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.hamcrest.number.IsCloseTo.*;
|
||||
import static org.junit.Assert.*;
|
||||
@@ -25,6 +26,10 @@ import static org.junit.Assume.*;
|
||||
import static org.springframework.data.redis.SpinBarrier.*;
|
||||
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.*;
|
||||
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.BitfieldIncrBy.Overflow.FAIL;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.BitfieldCommand.newBitfieldCommand;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.BitfieldType.*;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.Offset.*;
|
||||
import static org.springframework.data.redis.core.ScanOptions.*;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -2902,6 +2907,116 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
verifyResults(Arrays.asList(new Object[] { null }));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
|
||||
public void bitFieldSetShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
actual.add(connection.bitfield(key, newBitfieldCommand().set(INT_8).valueAt(offset(0L)).to(10L)));
|
||||
actual.add(connection.bitfield(key, newBitfieldCommand().set(INT_8).valueAt(offset(0L)).to(20L)));
|
||||
|
||||
List<Object> results = getResults();
|
||||
assertThat((List<Long>) results.get(0), contains(0L));
|
||||
assertThat((List<Long>) results.get(1), contains(10L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
|
||||
public void bitFieldGetShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
actual.add(connection.bitfield(key, newBitfieldCommand().get(INT_8).valueAt(offset(0L))));
|
||||
|
||||
List<Object> results = getResults();
|
||||
assertThat((List<Long>) results.get(0), contains(0L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
|
||||
public void bitFieldIncrByShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
actual.add(connection.bitfield(key, newBitfieldCommand().incr(INT_8).valueAt(offset(100L)).by(1L)));
|
||||
|
||||
List<Object> results = getResults();
|
||||
assertThat((List<Long>) results.get(0), contains(1L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
|
||||
public void bitFieldIncrByWithOverflowShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
actual.add(
|
||||
connection.bitfield(key, newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)));
|
||||
actual.add(
|
||||
connection.bitfield(key, newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)));
|
||||
actual.add(
|
||||
connection.bitfield(key, newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)));
|
||||
actual.add(
|
||||
connection.bitfield(key, newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)));
|
||||
|
||||
List<Object> results = getResults();
|
||||
assertThat((List<Long>) results.get(0), contains(1L));
|
||||
assertThat((List<Long>) results.get(1), contains(2L));
|
||||
assertThat((List<Long>) results.get(2), contains(3L));
|
||||
assertThat(((List<Long>) results.get(3)).get(0), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
|
||||
public void bitfieldShouldAllowMultipleSubcommands() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
actual.add(connection.bitfield(key,
|
||||
newBitfieldCommand().incr(signed(5)).valueAt(offset(100L)).by(1L).get(unsigned(4)).valueAt(0L)));
|
||||
|
||||
assertThat((List<Long>) getResults().get(0), contains(1L, 0L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
@WithRedisDriver({ RedisDriver.JEDIS, RedisDriver.LETTUCE })
|
||||
public void bitfieldShouldWorkUsingNonZeroBasedOffset() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
actual.add(connection.bitfield(key, newBitfieldCommand().set(INT_8).valueAt(offset(0L).multipliedByTypeLength()).to(100L).set(INT_8).valueAt(offset(1L).multipliedByTypeLength()).to(200L)));
|
||||
actual.add(connection.bitfield(key, newBitfieldCommand().get(INT_8).valueAt(offset(0L).multipliedByTypeLength()).get(INT_8).valueAt(offset(1L).multipliedByTypeLength())));
|
||||
|
||||
List<Object> results = getResults();
|
||||
assertThat((List<Long>) results.get(0), contains(0L, 0L));
|
||||
assertThat((List<Long>) results.get(1), contains(100L, -56L));
|
||||
}
|
||||
|
||||
protected void verifyResults(List<Object> expected) {
|
||||
assertEquals(expected, getResults());
|
||||
}
|
||||
|
||||
@@ -981,5 +981,10 @@ public class RedisConnectionUnitTests {
|
||||
public Boolean set(byte[] key, byte[] value, Expiration expiration, SetOption options) {
|
||||
return delegate.set(key, value, expiration, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> bitfield(byte[] key, BitfieldCommand operation) {
|
||||
return delegate.bitfield(key, operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ import static org.junit.Assert.*;
|
||||
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
|
||||
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.*;
|
||||
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.BitfieldCommand.*;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.BitfieldIncrBy.Overflow.*;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.BitfieldType.*;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.Offset.*;
|
||||
import static org.springframework.data.redis.core.ScanOptions.*;
|
||||
|
||||
import redis.clients.jedis.HostAndPort;
|
||||
@@ -30,6 +34,7 @@ import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -2333,4 +2338,97 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
public void refcountReturnsNullWhenKeyDoesNotExist() {
|
||||
assertThat(clusterConnection.keyCommands().refcount(KEY_3_BYTES), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitFieldSetShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().set(INT_8).valueAt(offset(0L)).to(10L)), contains(0L));
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().set(INT_8).valueAt(offset(0L)).to(20L)), contains(10L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitFieldGetShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(
|
||||
clusterConnection.bitfield(JedisConverters.toBytes(key), newBitfieldCommand().get(INT_8).valueAt(offset(0L))),
|
||||
contains(0L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitFieldIncrByShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(INT_8).valueAt(offset(100L)).by(1L)), contains(1L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitFieldIncrByWithOverflowShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)), contains(1L));
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)), contains(2L));
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)), contains(3L));
|
||||
assertThat(
|
||||
clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)).get(0),
|
||||
is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitfieldShouldAllowMultipleSubcommands() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(
|
||||
clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(signed(5)).valueAt(offset(100L)).by(1L).get(unsigned(4)).valueAt(0L)),
|
||||
contains(1L, 0L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitfieldShouldWorkUsingNonZeroBasedOffset() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key), newBitfieldCommand().set(INT_8).valueAt(offset(0L).multipliedByTypeLength())
|
||||
.to(100L).set(INT_8).valueAt(offset(1L).multipliedByTypeLength()).to(200L)), contains(0L, 0L));
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key), newBitfieldCommand().get(INT_8).valueAt(offset(0L).multipliedByTypeLength())
|
||||
.get(INT_8).valueAt(offset(1L).multipliedByTypeLength())), contains(100L, -56L));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ import static org.junit.Assert.*;
|
||||
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
|
||||
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.*;
|
||||
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.BitfieldCommand.*;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.BitfieldIncrBy.Overflow.*;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.BitfieldType.*;
|
||||
import static org.springframework.data.redis.connection.RedisStringCommands.Offset.*;
|
||||
import static org.springframework.data.redis.core.ScanOptions.*;
|
||||
|
||||
import io.lettuce.core.RedisURI.Builder;
|
||||
@@ -31,11 +35,13 @@ import io.lettuce.core.codec.ByteArrayCodec;
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
@@ -2358,4 +2364,99 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
public void refcountReturnsNullWhenKeyDoesNotExist() {
|
||||
assertThat(clusterConnection.keyCommands().refcount(KEY_3_BYTES), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitFieldSetShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().set(INT_8).valueAt(offset(0L)).to(10L)), contains(0L));
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().set(INT_8).valueAt(offset(0L)).to(20L)), contains(10L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitFieldGetShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(
|
||||
clusterConnection.bitfield(JedisConverters.toBytes(key), newBitfieldCommand().get(INT_8).valueAt(offset(0L))),
|
||||
contains(0L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitFieldIncrByShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(INT_8).valueAt(offset(100L)).by(1L)), contains(1L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitFieldIncrByWithOverflowShouldWorkCorrectly() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)), contains(1L));
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)), contains(2L));
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)), contains(3L));
|
||||
assertThat(
|
||||
clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)).get(0),
|
||||
is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitfieldShouldAllowMultipleSubcommands() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(
|
||||
clusterConnection.bitfield(JedisConverters.toBytes(key),
|
||||
newBitfieldCommand().incr(signed(5)).valueAt(offset(100L)).by(1L).get(unsigned(4)).valueAt(0L)),
|
||||
contains(1L, 0L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitfieldShouldWorkUsingNonZeroBasedOffset() {
|
||||
|
||||
String key = "bitfield-" + UUID.randomUUID();
|
||||
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key), newBitfieldCommand().set(INT_8).valueAt(offset(0L).multipliedByTypeLength())
|
||||
.to(100L).set(INT_8).valueAt(offset(1L).multipliedByTypeLength()).to(200L)), contains(0L, 0L));
|
||||
assertThat(clusterConnection.bitfield(JedisConverters.toBytes(key), newBitfieldCommand().get(INT_8).valueAt(offset(0L).multipliedByTypeLength())
|
||||
.get(INT_8).valueAt(offset(1L).multipliedByTypeLength())), contains(100L, -56L));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.hamcrest.core.AllOf;
|
||||
import org.hamcrest.core.IsCollectionContaining;
|
||||
import org.hamcrest.core.IsInstanceOf;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -337,4 +338,24 @@ public class LettuceConnectionIntegrationTests extends AbstractConnectionIntegra
|
||||
new RedisSentinelConfiguration().master("mymaster").sentinel("127.0.0.1", 26379).sentinel("127.0.0.1", 26380));
|
||||
assertThat(connection.getSentinelConnection(), notNullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore("Lettuce Bug")
|
||||
@Override
|
||||
public void bitFieldIncrByWithOverflowShouldWorkCorrectly() {
|
||||
super.bitFieldIncrByWithOverflowShouldWorkCorrectly();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore("Lettuce Bug")
|
||||
@Override
|
||||
public void bitfieldShouldWorkUsingNonZeroBasedOffset() {
|
||||
super.bitfieldShouldWorkUsingNonZeroBasedOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import static org.springframework.data.redis.SpinBarrier.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
@@ -110,4 +111,23 @@ public class LettuceConnectionPipelineIntegrationTests extends AbstractConnectio
|
||||
super.testListClientsContainsAtLeastOneElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore("Lettuce Bug")
|
||||
@Override
|
||||
public void bitFieldIncrByWithOverflowShouldWorkCorrectly() {
|
||||
super.bitFieldIncrByWithOverflowShouldWorkCorrectly();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore("Lettuce Bug")
|
||||
@Override
|
||||
public void bitfieldShouldWorkUsingNonZeroBasedOffset() {
|
||||
super.bitfieldShouldWorkUsingNonZeroBasedOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.data.redis.connection.AbstractConnectionTransactionIntegrationTests;
|
||||
@@ -68,4 +69,64 @@ public class LettuceConnectionTransactionIntegrationTests extends AbstractConnec
|
||||
public void testSelect() {
|
||||
super.testSelect();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore("Lettuce Bug")
|
||||
@Override
|
||||
public void bitFieldIncrByWithOverflowShouldWorkCorrectly() {
|
||||
super.bitFieldIncrByWithOverflowShouldWorkCorrectly();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore("Lettuce Bug")
|
||||
@Override
|
||||
public void bitFieldGetShouldWorkCorrectly() {
|
||||
super.bitFieldGetShouldWorkCorrectly();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore("Lettuce Bug")
|
||||
@Override
|
||||
public void bitFieldIncrByShouldWorkCorrectly() {
|
||||
super.bitFieldIncrByShouldWorkCorrectly();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore("Lettuce Bug")
|
||||
@Override
|
||||
public void bitFieldSetShouldWorkCorrectly() {
|
||||
super.bitFieldSetShouldWorkCorrectly();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore("Lettuce Bug")
|
||||
@Override
|
||||
public void bitfieldShouldAllowMultipleSubcommands() {
|
||||
super.bitfieldShouldAllowMultipleSubcommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
@Ignore("Lettuce Bug")
|
||||
@Override
|
||||
public void bitfieldShouldWorkUsingNonZeroBasedOffset() {
|
||||
super.bitfieldShouldWorkUsingNonZeroBasedOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2016. 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.core;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DefaultValueOperationsUnitTests<K, V> {
|
||||
|
||||
@Mock RedisConnectionFactory connectionFactoryMock;
|
||||
@Mock RedisConnection connectionMock;
|
||||
RedisSerializer<String> serializer;
|
||||
RedisTemplate<String, V> template;
|
||||
ValueOperations<String, V> valueOps;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
when(connectionFactoryMock.getConnection()).thenReturn(connectionMock);
|
||||
|
||||
serializer = new StringRedisSerializer();
|
||||
|
||||
template = new RedisTemplate<String, V>();
|
||||
template.setKeySerializer(serializer);
|
||||
template.setConnectionFactory(connectionFactoryMock);
|
||||
template.afterPropertiesSet();
|
||||
|
||||
this.valueOps = template.opsForValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-562
|
||||
*/
|
||||
@Test
|
||||
public void bitfieldShouldBeDelegatedCorrectly() {
|
||||
|
||||
RedisStringCommands.BitfieldCommand command = RedisStringCommands.BitfieldCommand.newBitfieldCommand()
|
||||
.get(RedisStringCommands.BitfieldType.INT_8).valueAt(0L);
|
||||
|
||||
valueOps.bitfield("key", command);
|
||||
|
||||
verify(connectionMock, times(1)).bitfield(eq(serializer.serialize("key")), eq(command));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user