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

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