DATAREDIS-1005 - Polishing.
Implement cluster-wide scriptFlush(), scriptKill(), and scriptLoad(). Add method argument assertions and author tags. Adapt and fix tests. Original pull request: #460.
This commit is contained in:
@@ -9,6 +9,7 @@ This section briefly covers items that are new and noteworthy in the latest rele
|
||||
* <<redis.streams>>
|
||||
* Refined `union`/`diff`/`intersect` set-operation methods accepting a single collection of keys.
|
||||
* Upgrade to Jedis 3.
|
||||
* Add support for scripting commands using Jedis Cluster.
|
||||
|
||||
[[new-in-2.1.0]]
|
||||
== New in Spring Data Redis 2.1
|
||||
|
||||
@@ -587,7 +587,8 @@ public class ClusterCommandExecutor implements DisposableBean {
|
||||
if (CollectionUtils.isEmpty((Map<?, ?>) nodeResult.getValue())) {
|
||||
return nodeResult.getValue();
|
||||
}
|
||||
} else if (CollectionUtils.isEmpty((Collection<?>) nodeResult.getValue())) {
|
||||
} else if (nodeResult.getValue() instanceof Collection
|
||||
&& CollectionUtils.isEmpty((Collection<?>) nodeResult.getValue())) {
|
||||
return nodeResult.getValue();
|
||||
} else {
|
||||
return nodeResult.getValue();
|
||||
|
||||
@@ -69,6 +69,7 @@ import org.springframework.util.Assert;
|
||||
* @author Ninad Divadkar
|
||||
* @author Tao Chen
|
||||
* @author Chen Guanqun
|
||||
* @author Pavel Khokhlov
|
||||
* @since 1.7
|
||||
*/
|
||||
public class JedisClusterConnection implements DefaultedRedisClusterConnection {
|
||||
|
||||
@@ -15,24 +15,28 @@
|
||||
*/
|
||||
package org.springframework.data.redis.connection.jedis;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import redis.clients.jedis.BinaryJedis;
|
||||
import redis.clients.jedis.JedisCluster;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.redis.connection.ClusterCommandExecutor;
|
||||
import org.springframework.data.redis.connection.RedisScriptingCommands;
|
||||
import org.springframework.data.redis.connection.ReturnType;
|
||||
import redis.clients.jedis.JedisCluster;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @author Pavel Khokhlov
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class JedisClusterScriptingCommands implements RedisScriptingCommands {
|
||||
|
||||
private final @NonNull JedisClusterConnection clusterConnection;
|
||||
private final @NonNull JedisClusterConnection connection;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -40,7 +44,13 @@ class JedisClusterScriptingCommands implements RedisScriptingCommands {
|
||||
*/
|
||||
@Override
|
||||
public void scriptFlush() {
|
||||
throw new InvalidDataAccessApiUsageException("ScriptFlush is not supported in cluster environment.");
|
||||
|
||||
try {
|
||||
connection.getClusterCommandExecutor().executeCommandOnAllNodes(
|
||||
(JedisClusterConnection.JedisClusterCommandCallback<String>) BinaryJedis::scriptFlush);
|
||||
} catch (Exception ex) {
|
||||
throw convertJedisAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -49,7 +59,13 @@ class JedisClusterScriptingCommands implements RedisScriptingCommands {
|
||||
*/
|
||||
@Override
|
||||
public void scriptKill() {
|
||||
throw new InvalidDataAccessApiUsageException("ScriptKill is not supported in cluster environment.");
|
||||
|
||||
try {
|
||||
connection.getClusterCommandExecutor().executeCommandOnAllNodes(
|
||||
(JedisClusterConnection.JedisClusterCommandCallback<String>) BinaryJedis::scriptKill);
|
||||
} catch (Exception ex) {
|
||||
throw convertJedisAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -58,7 +74,18 @@ class JedisClusterScriptingCommands implements RedisScriptingCommands {
|
||||
*/
|
||||
@Override
|
||||
public String scriptLoad(byte[] script) {
|
||||
throw new InvalidDataAccessApiUsageException("ScriptLoad is not supported in cluster environment.");
|
||||
|
||||
Assert.notNull(script, "Script must not be null!");
|
||||
|
||||
try {
|
||||
ClusterCommandExecutor.MultiNodeResult<byte[]> multiNodeResult = connection.getClusterCommandExecutor()
|
||||
.executeCommandOnAllNodes(
|
||||
(JedisClusterConnection.JedisClusterCommandCallback<byte[]>) client -> client.scriptLoad(script));
|
||||
|
||||
return JedisConverters.toString(multiNodeResult.getFirstNonNullNotEmptyOrDefault(new byte[0]));
|
||||
} catch (Exception ex) {
|
||||
throw convertJedisAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -77,7 +104,9 @@ class JedisClusterScriptingCommands implements RedisScriptingCommands {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T eval(byte[] script, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
|
||||
checkConnection();
|
||||
|
||||
Assert.notNull(script, "Script must not be null!");
|
||||
|
||||
try {
|
||||
return (T) new JedisScriptReturnConverter(returnType)
|
||||
.convert(getCluster().eval(script, JedisConverters.toBytes(numKeys), keysAndArgs));
|
||||
@@ -102,7 +131,9 @@ class JedisClusterScriptingCommands implements RedisScriptingCommands {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T evalSha(byte[] scriptSha, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
|
||||
checkConnection();
|
||||
|
||||
Assert.notNull(scriptSha, "Script digest must not be null!");
|
||||
|
||||
try {
|
||||
return (T) new JedisScriptReturnConverter(returnType)
|
||||
.convert(getCluster().evalsha(scriptSha, numKeys, keysAndArgs));
|
||||
@@ -111,32 +142,11 @@ class JedisClusterScriptingCommands implements RedisScriptingCommands {
|
||||
}
|
||||
}
|
||||
|
||||
public JedisClusterConnection getClusterConnection() {
|
||||
return clusterConnection;
|
||||
}
|
||||
|
||||
protected RuntimeException convertJedisAccessException(Exception ex) {
|
||||
return clusterConnection.convertJedisAccessException(ex);
|
||||
return connection.convertJedisAccessException(ex);
|
||||
}
|
||||
|
||||
private JedisCluster getCluster() {
|
||||
return clusterConnection.getCluster();
|
||||
}
|
||||
|
||||
protected void checkConnection() {
|
||||
if (isQueueing()) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
if (isPipelined()) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPipelined() {
|
||||
return clusterConnection.isPipelined();
|
||||
}
|
||||
|
||||
private boolean isQueueing() {
|
||||
return clusterConnection.isQueueing();
|
||||
return connection.getCluster();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,9 +147,9 @@ class JedisScriptingCommands implements RedisScriptingCommands {
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T evalSha(byte[] scriptSha1, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
|
||||
public <T> T evalSha(byte[] scriptSha, ReturnType returnType, int numKeys, byte[]... keysAndArgs) {
|
||||
|
||||
Assert.notNull(scriptSha1, "Script digest must not be null!");
|
||||
Assert.notNull(scriptSha, "Script digest must not be null!");
|
||||
|
||||
if (isQueueing() || isPipelined()) {
|
||||
throw new UnsupportedOperationException();
|
||||
@@ -157,7 +157,7 @@ class JedisScriptingCommands implements RedisScriptingCommands {
|
||||
|
||||
try {
|
||||
return (T) new JedisScriptReturnConverter(returnType)
|
||||
.convert(connection.getJedis().evalsha(scriptSha1, numKeys, keysAndArgs));
|
||||
.convert(connection.getJedis().evalsha(scriptSha, numKeys, keysAndArgs));
|
||||
} catch (Exception ex) {
|
||||
throw convertJedisAccessException(ex);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
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;
|
||||
@@ -51,7 +52,6 @@ import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.redis.connection.ClusterConnectionTests;
|
||||
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
|
||||
import org.springframework.data.redis.connection.DataType;
|
||||
import org.springframework.data.redis.connection.ReturnType;
|
||||
import org.springframework.data.redis.connection.DefaultSortParameters;
|
||||
import org.springframework.data.redis.connection.DefaultTuple;
|
||||
import org.springframework.data.redis.connection.RedisClusterNode;
|
||||
@@ -63,19 +63,21 @@ import org.springframework.data.redis.connection.RedisStringCommands.BitOperatio
|
||||
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
|
||||
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
|
||||
import org.springframework.data.redis.connection.ReturnType;
|
||||
import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.script.DigestUtils;
|
||||
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;
|
||||
import org.springframework.data.redis.core.script.DigestUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Pavel Khokhlov
|
||||
*/
|
||||
public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
|
||||
@@ -2368,10 +2370,8 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitFieldGetShouldWorkCorrectly() {
|
||||
|
||||
assertThat(
|
||||
clusterConnection.stringCommands().bitField(JedisConverters.toBytes(KEY_1),
|
||||
create().get(INT_8).valueAt(offset(0L))),
|
||||
contains(0L));
|
||||
assertThat(clusterConnection.stringCommands().bitField(JedisConverters.toBytes(KEY_1),
|
||||
create().get(INT_8).valueAt(offset(0L))), contains(0L));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-562
|
||||
@@ -2392,22 +2392,16 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)), contains(2L));
|
||||
assertThat(clusterConnection.stringCommands().bitField(JedisConverters.toBytes(KEY_1),
|
||||
create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)), contains(3L));
|
||||
assertThat(
|
||||
clusterConnection.stringCommands()
|
||||
.bitField(JedisConverters.toBytes(KEY_1),
|
||||
create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L))
|
||||
.get(0),
|
||||
is(nullValue()));
|
||||
assertThat(clusterConnection.stringCommands().bitField(JedisConverters.toBytes(KEY_1),
|
||||
create().incr(unsigned(2)).valueAt(offset(102L)).overflow(FAIL).by(1L)).get(0), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-562
|
||||
@IfProfileValue(name = "redisVersion", value = "3.2+")
|
||||
public void bitfieldShouldAllowMultipleSubcommands() {
|
||||
|
||||
assertThat(
|
||||
clusterConnection.stringCommands().bitField(JedisConverters.toBytes(KEY_1),
|
||||
create().incr(signed(5)).valueAt(offset(100L)).by(1L).get(unsigned(4)).valueAt(0L)),
|
||||
contains(1L, 0L));
|
||||
assertThat(clusterConnection.stringCommands().bitField(JedisConverters.toBytes(KEY_1),
|
||||
create().incr(signed(5)).valueAt(offset(100L)).by(1L).get(unsigned(4)).valueAt(0L)), contains(1L, 0L));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-562
|
||||
@@ -2415,36 +2409,71 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
public void bitfieldShouldWorkUsingNonZeroBasedOffset() {
|
||||
|
||||
assertThat(clusterConnection.stringCommands().bitField(JedisConverters.toBytes(KEY_1),
|
||||
create().set(INT_8).valueAt(offset(0L).multipliedByTypeLength())
|
||||
.to(100L).set(INT_8).valueAt(offset(1L).multipliedByTypeLength()).to(200L)), contains(0L, 0L));
|
||||
create().set(INT_8).valueAt(offset(0L).multipliedByTypeLength()).to(100L).set(INT_8)
|
||||
.valueAt(offset(1L).multipliedByTypeLength()).to(200L)),
|
||||
contains(0L, 0L));
|
||||
assertThat(
|
||||
clusterConnection.stringCommands()
|
||||
.bitField(JedisConverters.toBytes(KEY_1), create().get(INT_8)
|
||||
.valueAt(offset(0L).multipliedByTypeLength())
|
||||
.get(INT_8).valueAt(offset(1L).multipliedByTypeLength())), contains(100L, -56L));
|
||||
clusterConnection.stringCommands().bitField(JedisConverters.toBytes(KEY_1), create().get(INT_8)
|
||||
.valueAt(offset(0L).multipliedByTypeLength()).get(INT_8).valueAt(offset(1L).multipliedByTypeLength())),
|
||||
contains(100L, -56L));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-1005
|
||||
@IfProfileValue(name = "redisVersion", value = "2.6+")
|
||||
public void evalOK() {
|
||||
byte[] keyAndArgs = JedisConverters.toBytes("FOO");
|
||||
public void evalShouldRunScript() {
|
||||
|
||||
byte[] keyAndArgs = JedisConverters.toBytes("FOO");
|
||||
String luaScript = "return redis.call(\"INCR\", KEYS[1])";
|
||||
byte[] luaScriptBin = JedisConverters.toBytes(luaScript);
|
||||
|
||||
Long result = clusterConnection.scriptingCommands().eval(luaScriptBin, ReturnType.VALUE, 1, keyAndArgs);
|
||||
|
||||
assertThat(result, is(1L));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-1005
|
||||
@IfProfileValue(name = "redisVersion", value = "2.6+")
|
||||
public void evalShaOK() {
|
||||
byte[] keyAndArgs = JedisConverters.toBytes("FOO");
|
||||
public void scriptLoadShouldLoadScript() {
|
||||
|
||||
String luaScript = "return redis.call(\"INCR\", KEYS[1])";
|
||||
String digest = DigestUtils.sha1DigestAsHex(luaScript);
|
||||
byte[] luaScriptBin = JedisConverters.toBytes(digest);
|
||||
Long result = clusterConnection.scriptingCommands().evalSha(luaScriptBin, ReturnType.VALUE, 1, keyAndArgs);
|
||||
assertThat(result, is(1L));
|
||||
byte[] luaScriptBin = JedisConverters.toBytes(luaScript);
|
||||
|
||||
String result = clusterConnection.scriptingCommands().scriptLoad(luaScriptBin);
|
||||
|
||||
assertThat(result, is(digest));
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-1005
|
||||
@IfProfileValue(name = "redisVersion", value = "2.6+")
|
||||
public void scriptFlushShouldRemoveScripts() {
|
||||
|
||||
byte[] keyAndArgs = JedisConverters.toBytes("FOO");
|
||||
String luaScript = "return redis.call(\"GET\", KEYS[1])";
|
||||
byte[] luaScriptBin = JedisConverters.toBytes(luaScript);
|
||||
|
||||
clusterConnection.scriptingCommands().scriptLoad(luaScriptBin);
|
||||
clusterConnection.scriptingCommands().scriptFlush();
|
||||
|
||||
try {
|
||||
clusterConnection.scriptingCommands().evalSha(luaScriptBin, ReturnType.VALUE, 1, keyAndArgs);
|
||||
fail("expected InvalidDataAccessApiUsageException");
|
||||
} catch (InvalidDataAccessApiUsageException e) {
|
||||
assertThat(e.getMessage(), containsString("NOSCRIPT"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-1005
|
||||
@IfProfileValue(name = "redisVersion", value = "2.6+")
|
||||
public void evelShaShouldRunScript() {
|
||||
|
||||
byte[] keyAndArgs = JedisConverters.toBytes("FOO");
|
||||
String luaScript = "return redis.call(\"INCR\", KEYS[1])";
|
||||
byte[] digest = JedisConverters.toBytes(DigestUtils.sha1DigestAsHex(luaScript));
|
||||
|
||||
clusterConnection.scriptingCommands().scriptLoad(JedisConverters.toBytes(luaScript));
|
||||
|
||||
Long result = clusterConnection.scriptingCommands().evalSha(digest, ReturnType.VALUE, 1, keyAndArgs);
|
||||
assertThat(result, is(1L));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user