DATAREDIS-697 - Add support for BITPOS.

We now support the BITPOS command throughout the sync and reactive api.

Original pull request: #335.
This commit is contained in:
Christoph Strobl
2018-04-27 11:00:49 +02:00
committed by Mark Paluch
parent 8215f68d9f
commit e9a3a5b6d9
16 changed files with 472 additions and 9 deletions

View File

@@ -44,6 +44,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Range.Bound;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
@@ -70,6 +71,8 @@ import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.data.redis.test.util.HexStringUtils;
import org.springframework.data.redis.test.util.RedisClientRule;
import org.springframework.data.redis.test.util.RedisDriver;
import org.springframework.data.redis.test.util.WithRedisDriver;
@@ -2822,6 +2825,27 @@ public abstract class AbstractConnectionIntegrationTests {
verifyResults(Arrays.asList(new Object[] { 0L }));
}
@Test // DATAREDIS-697
@IfProfileValue(name = "redisVersion", value = "2.8.7+")
public void bitPosShouldReturnPositionCorrectly() {
actual.add(connection.set("bitpos-1".getBytes(), HexStringUtils.hexToBytes("fff000")));
actual.add(connection.bitPos("bitpos-1", false));
verifyResults(Arrays.asList(new Object[] { true, 12L }));
}
@Test // DATAREDIS-697
@IfProfileValue(name = "redisVersion", value = "2.8.7+")
public void bitPosShouldReturnPositionInRangeCorrectly() {
actual.add(connection.set("bitpos-1".getBytes(), HexStringUtils.hexToBytes("fff0f0")));
actual.add(connection.bitPos("bitpos-1", true,
org.springframework.data.domain.Range.of(Bound.inclusive(2L), Bound.unbounded())));
verifyResults(Arrays.asList(new Object[] { true, 16L }));
}
protected void verifyResults(List<Object> expected) {
assertEquals(expected, getResults());
}
@@ -2845,4 +2869,5 @@ public abstract class AbstractConnectionIntegrationTests {
return (!connection.exists(key));
}
}
}

View File

@@ -38,6 +38,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Range.Bound;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
@@ -59,6 +60,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.test.util.HexStringUtils;
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
import org.springframework.data.redis.test.util.RedisClusterRule;
import org.springframework.test.annotation.IfProfileValue;
@@ -2271,4 +2273,23 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
assertThat(clusterConnection.keyCommands().unlink(KEY_1_BYTES), is(0L));
}
@Test // DATAREDIS-697
@IfProfileValue(name = "redisVersion", value = "2.8.7+")
public void bitPosShouldReturnPositionCorrectly() {
nativeConnection.set(KEY_1_BYTES, HexStringUtils.hexToBytes("fff000"));
assertThat(clusterConnection.stringCommands().bitPos(KEY_1_BYTES, false), is(12L));
}
@Test // DATAREDIS-697
@IfProfileValue(name = "redisVersion", value = "2.8.7+")
public void bitPosShouldReturnPositionInRangeCorrectly() {
nativeConnection.set(KEY_1_BYTES, HexStringUtils.hexToBytes("fff0f0"));
assertThat(clusterConnection.stringCommands().bitPos(KEY_1_BYTES, true,
org.springframework.data.domain.Range.of(Bound.inclusive(2L), Bound.unbounded())), is(16L));
}
}

View File

@@ -26,6 +26,7 @@ import io.lettuce.core.RedisURI.Builder;
import io.lettuce.core.api.sync.RedisHLLCommands;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
import io.lettuce.core.codec.ByteArrayCodec;
import java.nio.charset.Charset;
import java.time.Duration;
@@ -38,6 +39,7 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Range.Bound;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
@@ -54,6 +56,7 @@ import org.springframework.data.redis.connection.jedis.JedisConverters;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.test.util.HexStringUtils;
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
import org.springframework.data.redis.test.util.RedisClusterRule;
import org.springframework.test.annotation.IfProfileValue;
@@ -89,6 +92,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
RedisClusterClient client;
RedisAdvancedClusterCommands<String, String> nativeConnection;
RedisAdvancedClusterCommands<byte[], byte[]> binaryConnection;
LettuceClusterConnection clusterConnection;
public static @ClassRule RedisClusterRule clusterAvailable = new RedisClusterRule();
@@ -104,6 +108,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
client = RedisClusterClient.create(LettuceTestClientResources.getSharedClientResources(),
Builder.redis(CLUSTER_HOST, MASTER_NODE_1_PORT).withTimeout(500, TimeUnit.MILLISECONDS).build());
nativeConnection = client.connect().sync();
binaryConnection = client.connect(ByteArrayCodec.INSTANCE).sync();
clusterConnection = new LettuceClusterConnection(client);
}
@@ -112,6 +117,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
clusterConnection.serverCommands().flushDb();
nativeConnection.getStatefulConnection().close();
binaryConnection.getStatefulConnection().close();
clusterConnection.close();
client.shutdown(0, 0, TimeUnit.MILLISECONDS);
}
@@ -2294,4 +2300,21 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
public void unlinkReturnsZeroIfNoKeysTouched() {
assertThat(clusterConnection.keyCommands().unlink(KEY_1_BYTES), is(0L));
}
@Test // DATAREDIS-697
public void bitPosShouldReturnPositionCorrectly() {
binaryConnection.set(KEY_1_BYTES, HexStringUtils.hexToBytes("fff000"));
assertThat(clusterConnection.stringCommands().bitPos(KEY_1_BYTES, false), is(12L));
}
@Test // DATAREDIS-697
public void bitPosShouldReturnPositionInRangeCorrectly() {
binaryConnection.set(KEY_1_BYTES, HexStringUtils.hexToBytes("fff0f0"));
assertThat(clusterConnection.stringCommands().bitPos(KEY_1_BYTES, true,
org.springframework.data.domain.Range.of(Bound.inclusive(2L), Bound.unbounded())), is(16L));
}
}

View File

@@ -31,6 +31,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.data.redis.connection.lettuce.LettuceReactiveRedisConnection.ByteBufferCodec;
import org.springframework.data.redis.test.util.LettuceRedisClientProvider;
import org.springframework.data.redis.test.util.LettuceRedisClusterClientProvider;
@@ -75,10 +76,12 @@ public abstract class LettuceReactiveCommandsTestsBase {
@Parameterized.Parameter(value = 0) public LettuceConnectionProvider connectionProvider;
@Parameterized.Parameter(value = 1) public LettuceConnectionProvider nativeConnectionProvider;
@Parameterized.Parameter(value = 2) public Object displayName;
@Parameterized.Parameter(value = 2) public LettuceConnectionProvider nativeBinaryConnectionProvider;
@Parameterized.Parameter(value = 3) public Object displayName;
LettuceReactiveRedisConnection connection;
RedisClusterCommands<String, String> nativeCommands;
RedisClusterCommands<ByteBuffer, ByteBuffer> nativeBinaryCommands;
@Parameterized.Parameters(name = "{2}")
public static List<Object[]> parameters() {
@@ -92,11 +95,14 @@ public abstract class LettuceReactiveCommandsTestsBase {
LettuceReactiveRedisConnection.CODEC);
StandaloneConnectionProvider nativeConnectionProvider = new StandaloneConnectionProvider(standalone.getClient(),
StringCodec.UTF8);
StandaloneConnectionProvider nativeBinaryConnectionProvider = new StandaloneConnectionProvider(
standalone.getClient(), ByteBufferCodec.INSTANCE);
parameters.add(new Object[] { standaloneProvider, nativeConnectionProvider, "Standalone" });
parameters.add(
new Object[] { standaloneProvider, nativeConnectionProvider, nativeBinaryConnectionProvider, "Standalone" });
parameters.add(new Object[] {
new LettucePoolingConnectionProvider(standaloneProvider, LettucePoolingClientConfiguration.builder().build()),
nativeConnectionProvider, "Standalone/Pooled" });
nativeConnectionProvider, nativeBinaryConnectionProvider, "Standalone/Pooled" });
if (cluster.test()) {
@@ -104,8 +110,11 @@ public abstract class LettuceReactiveCommandsTestsBase {
LettuceReactiveRedisConnection.CODEC);
ClusterConnectionProvider nativeClusterConnectionProvider = new ClusterConnectionProvider(cluster.getClient(),
StringCodec.UTF8);
ClusterConnectionProvider nativeBinaryClusterConnectionProvider = new ClusterConnectionProvider(
cluster.getClient(), ByteBufferCodec.INSTANCE);
parameters.add(new Object[] { clusterProvider, nativeClusterConnectionProvider, "Cluster" });
parameters.add(new Object[] { clusterProvider, nativeClusterConnectionProvider,
nativeBinaryClusterConnectionProvider, "Cluster" });
}
return parameters;
@@ -116,11 +125,13 @@ public abstract class LettuceReactiveCommandsTestsBase {
if (nativeConnectionProvider instanceof StandaloneConnectionProvider) {
nativeCommands = nativeConnectionProvider.getConnection(StatefulRedisConnection.class).sync();
nativeBinaryCommands = nativeBinaryConnectionProvider.getConnection(StatefulRedisConnection.class).sync();
this.connection = new LettuceReactiveRedisConnection(connectionProvider);
} else {
ClusterConnectionProvider clusterConnectionProvider = (ClusterConnectionProvider) nativeConnectionProvider;
nativeCommands = nativeConnectionProvider.getConnection(StatefulRedisClusterConnection.class).sync();
nativeBinaryCommands = nativeBinaryConnectionProvider.getConnection(StatefulRedisClusterConnection.class).sync();
this.connection = new LettuceReactiveRedisClusterConnection(connectionProvider,
clusterConnectionProvider.getRedisClient());
}

View File

@@ -22,6 +22,7 @@ import static org.junit.Assume.*;
import org.springframework.data.redis.util.ByteUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoOperator;
import reactor.test.StepVerifier;
import java.nio.ByteBuffer;
@@ -36,6 +37,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
import org.springframework.data.domain.Range.Bound;
import org.springframework.data.redis.connection.ReactiveRedisConnection.ByteBufferResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
@@ -43,7 +45,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiVa
import org.springframework.data.redis.connection.ReactiveStringCommands.SetCommand;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.test.util.LettuceRedisClientProvider;
import org.springframework.data.redis.test.util.HexStringUtils;
/**
* @author Christoph Strobl
@@ -399,4 +401,22 @@ public class LettuceReactiveStringCommandsTests extends LettuceReactiveCommandsT
.expectNext(7L) //
.verifyComplete();
}
@Test // DATAREDIS-697
public void bitPosShouldReturnPositionCorrectly() {
nativeBinaryCommands.set(KEY_1_BBUFFER, ByteBuffer.wrap(HexStringUtils.hexToBytes("fff000")));
StepVerifier.create(connection.stringCommands().bitPos(KEY_1_BBUFFER, false)).expectNext(12L).verifyComplete();
}
@Test // DATAREDIS-697
public void bitPosShouldReturnPositionInRangeCorrectly() {
nativeBinaryCommands.set(KEY_1_BBUFFER, ByteBuffer.wrap(HexStringUtils.hexToBytes("fff0f0")));
StepVerifier.create(connection.stringCommands().bitPos(KEY_1_BBUFFER, true,
org.springframework.data.domain.Range.of(Bound.inclusive(2L), Bound.unbounded()))).expectNext(16L).verifyComplete();
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.test.util;
import org.springframework.util.Assert;
/**
* Utils for working with Hex Stings.
*
* @author Christoph Strobl
* @currentRead Beyong the Shadows - Brent Weeks
*/
public class HexStringUtils {
/**
* Convert a given HEX {@link String} to its byte representation.
*
* @param source must not be {@literal null}.
* @return
*/
public static byte[] hexToBytes(String source) {
Assert.notNull(source, "Source must not be null!");
int len = source.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(source.charAt(i), 16) << 4) + Character.digit(source.charAt(i + 1), 16));
}
return data;
}
}