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:
Christoph Strobl
2016-10-20 16:35:24 +02:00
committed by Mark Paluch
parent e631352832
commit 41db6a10fe
19 changed files with 1352 additions and 2 deletions

View File

@@ -13,6 +13,7 @@
|BGREWRITEAOF |X
|BGSAVE |X
|BITCOUNT |X
|BITFIELD |X
|BITOP |X
|BLPOP |X
|BRPOP |X

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();
}

View File

@@ -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());
}

View File

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

View File

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

View File

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

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

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