DATAREDIS-683 - Polishing.
Rename *Operators -> *Executor and use plain RedisElementReader/Writer instead of SerilaizationPairs. Therefore we’ve introduced new static factory methods creating the required Reader/Writer from a given RedisSerializer. Additionally some minor Javadoc updates, tests and removal of redundant null checks. Original Pull Request: #268
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
Redis versions 2.6 and higher provide support for execution of Lua scripts through the http://redis.io/commands/eval[eval] and http://redis.io/commands/evalsha[evalsha] commands. Spring Data Redis provides a high-level abstraction for script execution that handles serialization and automatically makes use of the Redis script cache.
|
||||
|
||||
Scripts can be run through the `execute` methods of `RedisTemplate`. RedisTemplate uses a configurable `ScriptExecutor` to execute the provided script. By default, the `ScriptExecutor` takes care of serializing the provided keys and arguments and deserializing the script result. This is done with the `RedisTemplate` key and value serializers. There is an additional `execute` method that allows you to pass custom serializers for the script arguments and result.
|
||||
Scripts can be run through the `execute` methods of `RedisTemplate` and `ReactiveRedisTemplate`. Both use a configurable `ScriptExecutor` / `ReactiveScriptExecutor` to run the provided script. By default, the `ScriptExecutor` takes care of serializing the provided keys and arguments and deserializing the script result. This is done via the key and value serializers of the template. There is an additional overload that allows you to pass custom serializers for the script arguments and result.
|
||||
|
||||
The default `ScriptExecutor` optimizes performance by retrieving the SHA1 of the script and attempting first to run `evalsha`, falling back to `eval` if the script is not yet present in the Redis script cache.
|
||||
|
||||
@@ -13,19 +13,21 @@ Here's an example that executes a common "check-and-set" scenario using a Lua sc
|
||||
----
|
||||
@Bean
|
||||
public RedisScript<Boolean> script() {
|
||||
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<Boolean>();
|
||||
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("META-INF/scripts/checkandset.lua")));
|
||||
redisScript.setResultType(Boolean.class);
|
||||
|
||||
ScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource("META-INF/scripts/checkandset.lua");
|
||||
return RedisScript.of(scriptSource, Boolean.class);
|
||||
}
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class Example {
|
||||
public class Example {
|
||||
|
||||
@Autowired
|
||||
RedisScript<Boolean> script;
|
||||
RedisScript<Boolean> script;
|
||||
|
||||
public boolean checkAndSet(String expectedValue, String newValue) {
|
||||
return redisTemplate.execute(script, Collections.singletonList("key"), expectedValue, newValue);
|
||||
return redisTemplate.execute(script, singletonList("key"), asList(expectedValue, newValue));
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -41,7 +43,7 @@ public class Example {
|
||||
return false
|
||||
----
|
||||
|
||||
The XML above configures a `DefaultRedisScript` pointing to a file called `checkandset.lua`, which is expected to return a boolean value. The script `resultType` should be one of `Long`, `Boolean`, `List`, or deserialized value type. It can also be null if the script returns a throw-away status (i.e "OK"). It is ideal to configure a single instance of `DefaultRedisScript` in your application context to avoid re-calculation of the script's SHA1 on every script execution.
|
||||
The code above configures a `RedisScript` pointing to a file called `checkandset.lua`, which is expected to return a boolean value. The script `resultType` should be one of `Long`, `Boolean`, `List`, or deserialized value type. It can also be `null` if the script returns a throw-away status (i.e "OK"). It is ideal to configure a single instance of `DefaultRedisScript` in your application context to avoid re-calculation of the script's SHA1 on every script execution.
|
||||
|
||||
The checkAndSet method above then executes th
|
||||
Scripts can be executed within a `SessionCallback` as part of a transaction or pipeline. See <<tx>> and <<pipeline>> for more information.
|
||||
|
||||
@@ -19,11 +19,16 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Redis Scripting commands executed using reactive infrastructure.
|
||||
* Redis <a href="https://redis.io/commands/#scripting">Scripting</a> commands executed using reactive infrastructure.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveScriptingCommands {
|
||||
@@ -47,19 +52,31 @@ public interface ReactiveScriptingCommands {
|
||||
* Execute the script by calling {@link #evalSha(String, ReturnType, int, ByteBuffer...)}.
|
||||
*
|
||||
* @param script must not be {@literal null}.
|
||||
* @return
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/script-load">Redis Documentation: SCRIPT LOAD</a>
|
||||
*/
|
||||
Mono<String> scriptLoad(ByteBuffer script);
|
||||
|
||||
/**
|
||||
* Check if given {@code scriptSha} exist in script cache.
|
||||
*
|
||||
* @param scriptSha The sha1 of the script is present in script cache. Must not be {@literal null}.
|
||||
* @return a {@link Mono} indicating if script is present.
|
||||
*/
|
||||
default Mono<Boolean> scriptExists(String scriptSha) {
|
||||
|
||||
Assert.notNull(scriptSha, "ScriptSha must not be null!");
|
||||
return scriptExists(Collections.singletonList(scriptSha)).singleOrEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given {@code scriptShas} exist in script cache.
|
||||
*
|
||||
* @param scriptShas
|
||||
* @return one entry per given scriptSha in returned list.
|
||||
* @param scriptShas must not be {@literal null}.
|
||||
* @return {@link Flux} emitting one entry per scriptSha in given {@link List}.
|
||||
* @see <a href="http://redis.io/commands/script-exists">Redis Documentation: SCRIPT EXISTS</a>
|
||||
*/
|
||||
Flux<Boolean> scriptExists(String... scriptShas);
|
||||
Flux<Boolean> scriptExists(List<String> scriptShas);
|
||||
|
||||
/**
|
||||
* Evaluate given {@code script}.
|
||||
@@ -68,7 +85,7 @@ public interface ReactiveScriptingCommands {
|
||||
* @param returnType must not be {@literal null}.
|
||||
* @param numKeys
|
||||
* @param keysAndArgs must not be {@literal null}.
|
||||
* @return
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/eval">Redis Documentation: EVAL</a>
|
||||
*/
|
||||
<T> Flux<T> eval(ByteBuffer script, ReturnType returnType, int numKeys, ByteBuffer... keysAndArgs);
|
||||
@@ -80,7 +97,7 @@ public interface ReactiveScriptingCommands {
|
||||
* @param returnType must not be {@literal null}.
|
||||
* @param numKeys
|
||||
* @param keysAndArgs must not be {@literal null}.
|
||||
* @return
|
||||
* @return never {@literal null}.
|
||||
* @see <a href="http://redis.io/commands/evalsha">Redis Documentation: EVALSHA</a>
|
||||
*/
|
||||
<T> Flux<T> evalSha(String scriptSha, ReturnType returnType, int numKeys, ByteBuffer... keysAndArgs);
|
||||
|
||||
@@ -22,13 +22,17 @@ import reactor.core.publisher.Mono;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.redis.connection.ReactiveScriptingCommands;
|
||||
import org.springframework.data.redis.connection.ReturnType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ReactiveScriptingCommands} implementation for the <a href="https://lettuce.io/">Lettuce</a> Redis driver.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
class LettuceReactiveScriptingCommands implements ReactiveScriptingCommands {
|
||||
@@ -81,14 +85,14 @@ class LettuceReactiveScriptingCommands implements ReactiveScriptingCommands {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.ReactiveScriptingCommands#scriptExists(java.lang.String[])
|
||||
* @see org.springframework.data.redis.connection.ReactiveScriptingCommands#scriptExists(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public Flux<Boolean> scriptExists(String... scriptShas) {
|
||||
public Flux<Boolean> scriptExists(List<String> scriptShas) {
|
||||
|
||||
Assert.notNull(scriptShas, "Script SHAs must not be null!");
|
||||
Assert.notEmpty(scriptShas, "Script SHAs must not be empty!");
|
||||
|
||||
return connection.execute(cmd -> cmd.scriptExists(scriptShas));
|
||||
return connection.execute(cmd -> cmd.scriptExists(scriptShas.toArray(new String[scriptShas.size()])));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -20,13 +20,15 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.redis.connection.DataType;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.data.redis.serializer.RedisElementReader;
|
||||
import org.springframework.data.redis.serializer.RedisElementWriter;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
/**
|
||||
@@ -181,31 +183,54 @@ public interface ReactiveRedisOperations<K, V> {
|
||||
// Methods dealing with Redis Lua scripts
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Executes the given {@link RedisScript}.
|
||||
*
|
||||
* @param script must not be {@literal null}.
|
||||
* @return result value of the script {@link Flux#empty()} if {@link RedisScript#getResultType()} is {@literal null},
|
||||
* likely indicating a throw-away status reply (i.e. "OK").
|
||||
*/
|
||||
default <T> Flux<T> execute(RedisScript<T> script) {
|
||||
return execute(script, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link RedisScript}.
|
||||
*
|
||||
* @param script must not be {@literal null}.
|
||||
* @param keys must not be {@literal null}.
|
||||
* @return result value of the script {@link Flux#empty()} if {@link RedisScript#getResultType()} is {@literal null},
|
||||
* likely indicating a throw-away status reply (i.e. "OK").
|
||||
*/
|
||||
default <T> Flux<T> execute(RedisScript<T> script, List<K> keys) {
|
||||
return execute(script, keys, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link RedisScript}
|
||||
*
|
||||
* @param script The script to execute
|
||||
* @param keys keys that need to be passed to the script.
|
||||
* @param args args that need to be passed to the script.
|
||||
* @return return value of the script or raw {@link java.nio.ByteBuffer} if {@link RedisScript#getResultType()} is
|
||||
* {@literal null}, likely indicating a throw-away status reply (i.e. "OK").
|
||||
* @param script The script to execute. Must not be {@literal null}.
|
||||
* @param keys keys that need to be passed to the script. Must not be {@literal null}.
|
||||
* @param args args that need to be passed to the script. Must not be {@literal null}.
|
||||
* @return result value of the script {@link Flux#empty()} if {@link RedisScript#getResultType()} is {@literal null},
|
||||
* likely indicating a throw-away status reply (i.e. "OK").
|
||||
*/
|
||||
<T> Flux<T> execute(RedisScript<T> script, List<K> keys, Object... args);
|
||||
<T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args);
|
||||
|
||||
/**
|
||||
* Executes the given {@link RedisScript}, using the provided {@link RedisSerializer}s to serialize the script
|
||||
* arguments and result.
|
||||
*
|
||||
* @param script The script to execute
|
||||
* @param argsSerializerPair The {@link SerializationPair} to use for serializing args
|
||||
* @param resultSerializerPair The {@link SerializationPair} to use for serializing the script return value
|
||||
* @param argsWriter The {@link RedisElementWriter} to use for serializing args
|
||||
* @param resultReader The {@link RedisElementReader} to use for serializing the script return value
|
||||
* @param keys keys that need to be passed to the script.
|
||||
* @param args args that need to be passed to the script.
|
||||
* @return return value of the script or raw {@link java.nio.ByteBuffer} if {@link RedisScript#getResultType()} is
|
||||
* {@literal null}, likely indicating a throw-away status reply (i.e. "OK").
|
||||
* @return result value of the script {@link Flux#empty()} if {@link RedisScript#getResultType()} is {@literal null},
|
||||
* likely indicating a throw-away status reply (i.e. "OK").
|
||||
*/
|
||||
<T> Flux<T> execute(RedisScript<T> script, SerializationPair<?> argsSerializerPair,
|
||||
SerializationPair<T> resultSerializerPair, List<K> keys, Object... args);
|
||||
<T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args, RedisElementWriter<?> argsWriter,
|
||||
RedisElementReader<T> resultReader);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods to obtain specific operations interface objects.
|
||||
|
||||
@@ -30,11 +30,12 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.script.DefaultScriptOperators;
|
||||
import org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor;
|
||||
import org.springframework.data.redis.core.script.ReactiveScriptExecutor;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.data.redis.core.script.ScriptOperators;
|
||||
import org.springframework.data.redis.serializer.RedisElementReader;
|
||||
import org.springframework.data.redis.serializer.RedisElementWriter;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -58,7 +59,7 @@ public class ReactiveRedisTemplate<K, V> implements ReactiveRedisOperations<K, V
|
||||
private final ReactiveRedisConnectionFactory connectionFactory;
|
||||
private final RedisSerializationContext<K, V> serializationContext;
|
||||
private final boolean exposeConnection;
|
||||
private final ScriptOperators<K> scriptOperators;
|
||||
private final ReactiveScriptExecutor<K> reactiveScriptExecutor;
|
||||
|
||||
/**
|
||||
* Creates new {@link ReactiveRedisTemplate} using given {@link ReactiveRedisConnectionFactory} and
|
||||
@@ -89,7 +90,7 @@ public class ReactiveRedisTemplate<K, V> implements ReactiveRedisOperations<K, V
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.serializationContext = serializationContext;
|
||||
this.exposeConnection = exposeConnection;
|
||||
this.scriptOperators = new DefaultScriptOperators<>(connectionFactory, serializationContext);
|
||||
this.reactiveScriptExecutor = new DefaultReactiveScriptExecutor<>(connectionFactory, serializationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -491,21 +492,23 @@ public class ReactiveRedisTemplate<K, V> implements ReactiveRedisOperations<K, V
|
||||
// Methods dealing with Redis Lua scripts
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveRedisOperations#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.lang.Object[])
|
||||
/*
|
||||
*(non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveRedisOperations#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public <T> Flux<T> execute(RedisScript<T> script, List<K> keys, Object... args) {
|
||||
return scriptOperators.execute(script, keys, args);
|
||||
public <T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args) {
|
||||
return reactiveScriptExecutor.execute(script, keys, args);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveRedisOperations#execute(org.springframework.data.redis.core.script.RedisScript, org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair, org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair, java.util.List, java.lang.Object[])
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.ReactiveRedisOperations#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.util.List, org.springframework.data.redis.serializer.RedisElementWriter, org.springframework.data.redis.serializer.RedisElementReader)
|
||||
*/
|
||||
@Override
|
||||
public <T> Flux<T> execute(RedisScript<T> script, SerializationPair<?> argsSerializerPair,
|
||||
SerializationPair<T> resultSerializerPair, List<K> keys, Object... args) {
|
||||
return scriptOperators.execute(script, argsSerializerPair, resultSerializerPair, keys, args);
|
||||
public <T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args, RedisElementWriter<?> argsWriter,
|
||||
RedisElementReader<T> resultReader) {
|
||||
return reactiveScriptExecutor.execute(script, keys, args, argsWriter, resultReader);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -19,37 +19,41 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.redis.RedisSystemException;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.ReturnType;
|
||||
import org.springframework.data.redis.core.ReactiveRedisCallback;
|
||||
import org.springframework.data.redis.serializer.RedisElementReader;
|
||||
import org.springframework.data.redis.serializer.RedisElementWriter;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ScriptOperators}. Optimizes performance by attempting to execute script first using
|
||||
* evalsha, then falling back to eval if Redis has not yet cached the script.
|
||||
* Default implementation of {@link ReactiveScriptExecutor}. Optimizes performance by attempting to execute script first
|
||||
* using {@code EVALSHA}, then falling back to {@code EVAL} if Redis has not yet cached the script.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @param <K> The type of keys that may be passed during script execution
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DefaultScriptOperators<K> implements ScriptOperators<K> {
|
||||
public class DefaultReactiveScriptExecutor<K> implements ReactiveScriptExecutor<K> {
|
||||
|
||||
private final ReactiveRedisConnectionFactory connectionFactory;
|
||||
private final RedisSerializationContext<K, ?> serializationContext;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultScriptOperators} given {@link ReactiveRedisConnectionFactory} and
|
||||
* Creates a new {@link DefaultReactiveScriptExecutor} given {@link ReactiveRedisConnectionFactory} and
|
||||
* {@link RedisSerializationContext}.
|
||||
*
|
||||
* @param connectionFactory must not be {@literal null}.
|
||||
* @param serializationContext must not be {@literal null}.
|
||||
*/
|
||||
public DefaultScriptOperators(ReactiveRedisConnectionFactory connectionFactory,
|
||||
public DefaultReactiveScriptExecutor(ReactiveRedisConnectionFactory connectionFactory,
|
||||
RedisSerializationContext<K, ?> serializationContext) {
|
||||
|
||||
Assert.notNull(connectionFactory, "ReactiveRedisConnectionFactory must not be null!");
|
||||
@@ -59,49 +63,51 @@ public class DefaultScriptOperators<K> implements ScriptOperators<K> {
|
||||
this.serializationContext = serializationContext;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.script.ScriptOperators#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.lang.Object[])
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.script.ReactiveScriptExecutor#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Flux<T> execute(RedisScript<T> script, List<K> keys, Object... args) {
|
||||
public <T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args) {
|
||||
|
||||
Assert.notNull(script, "RedisScript must not be null!");
|
||||
Assert.notNull(keys, "Keys must not be null!");
|
||||
Assert.notNull(args, "Args must not be null!");
|
||||
|
||||
// use the Template's value serializer for args and result
|
||||
return execute(script, serializationContext.getKeySerializationPair(),
|
||||
(SerializationPair<T>) serializationContext.getValueSerializationPair(), keys, args);
|
||||
return execute(script, keys, args, serializationContext.getKeySerializationPair().getWriter(),
|
||||
(RedisElementReader<T>) serializationContext.getValueSerializationPair().getReader());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.script.ScriptOperators#execute(org.springframework.data.redis.core.script.RedisScript, org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair, org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair, java.util.List, java.lang.Object[])
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.script.ReactiveScriptExecutor#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.util.List, org.springframework.data.redis.serializer.RedisElementWriter, org.springframework.data.redis.serializer.RedisElementReader)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Flux<T> execute(RedisScript<T> script, SerializationPair<?> argsSerializerPair,
|
||||
SerializationPair<T> resultSerializationPair, List<K> keys, Object... args) {
|
||||
public <T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args, RedisElementWriter<?> argsWriter,
|
||||
RedisElementReader<T> resultReader) {
|
||||
|
||||
Assert.notNull(script, "RedisScript must not be null!");
|
||||
Assert.notNull(argsSerializerPair, "Argument SerializationPair must not be null!");
|
||||
Assert.notNull(resultSerializationPair, "Result SerializationPair must not be null!");
|
||||
Assert.notNull(argsWriter, "Argument Writer must not be null!");
|
||||
Assert.notNull(resultReader, "Result Reader must not be null!");
|
||||
Assert.notNull(keys, "Keys must not be null!");
|
||||
Assert.notNull(args, "Args must not be null!");
|
||||
|
||||
return execute(connection -> {
|
||||
|
||||
ReturnType returnType = ReturnType.fromJavaType(script.getResultType());
|
||||
ByteBuffer[] keysAndArgs = keysAndArgs((SerializationPair<Object>) argsSerializerPair, keys, args);
|
||||
ByteBuffer[] keysAndArgs = keysAndArgs(argsWriter, keys, args);
|
||||
int keySize = keys.size();
|
||||
|
||||
return eval(connection, script, returnType, keySize, keysAndArgs, resultSerializationPair);
|
||||
return eval(connection, script, returnType, keySize, keysAndArgs, resultReader);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
protected <T> Flux<T> eval(ReactiveRedisConnection connection, RedisScript<T> script, ReturnType returnType,
|
||||
int numKeys, ByteBuffer[] keysAndArgs, SerializationPair<T> resultSerializer) {
|
||||
int numKeys, ByteBuffer[] keysAndArgs, RedisElementReader<T> resultReader) {
|
||||
|
||||
Flux<T> result = connection.scriptingCommands().evalSha(script.getSha1(), returnType, numKeys, keysAndArgs);
|
||||
|
||||
@@ -115,41 +121,25 @@ public class DefaultScriptOperators<K> implements ScriptOperators<K> {
|
||||
.error(e instanceof RuntimeException ? (RuntimeException) e : new RedisSystemException(e.getMessage(), e));
|
||||
});
|
||||
|
||||
return script.getResultType() == null ? result : deserializeResult(resultSerializer, result);
|
||||
return script.returnsRawValue() ? result : deserializeResult(resultReader, result);
|
||||
}
|
||||
|
||||
protected ByteBuffer[] keysAndArgs(SerializationPair<Object> argsSerializer, List<K> keys, Object[] args) {
|
||||
protected ByteBuffer[] keysAndArgs(RedisElementWriter argsWriter, List<K> keys, List<?> args) {
|
||||
|
||||
int keySize = keys != null ? keys.size() : 0;
|
||||
ByteBuffer[] keysAndArgs = new ByteBuffer[args.length + keySize];
|
||||
int i = 0;
|
||||
|
||||
if (keys != null) {
|
||||
for (K key : keys) {
|
||||
if (key instanceof ByteBuffer) {
|
||||
keysAndArgs[i++] = (ByteBuffer) key;
|
||||
} else {
|
||||
keysAndArgs[i++] = keySerializer().getWriter().write(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Object arg : args) {
|
||||
if (arg instanceof ByteBuffer) {
|
||||
keysAndArgs[i++] = (ByteBuffer) arg;
|
||||
} else {
|
||||
keysAndArgs[i++] = argsSerializer.getWriter().write(arg);
|
||||
}
|
||||
}
|
||||
return keysAndArgs;
|
||||
return Stream.concat(keys.stream().map(t -> keySerializer().getWriter().write(t)),
|
||||
args.stream().map(t -> argsWriter.write(t))).toArray(size -> new ByteBuffer[size]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param script
|
||||
* @return
|
||||
*/
|
||||
protected ByteBuffer scriptBytes(RedisScript<?> script) {
|
||||
return serializationContext.getStringSerializationPair().getWriter().write(script.getScriptAsString());
|
||||
}
|
||||
|
||||
protected <T> Flux<T> deserializeResult(SerializationPair<T> pair, Flux<T> result) {
|
||||
return result.map(it -> ScriptUtils.deserializeResult(pair.getReader(), it));
|
||||
protected <T> Flux<T> deserializeResult(RedisElementReader<T> reader, Flux<T> result) {
|
||||
return result.map(it -> ScriptUtils.deserializeResult(reader, it));
|
||||
}
|
||||
|
||||
protected SerializationPair<K> keySerializer() {
|
||||
@@ -174,6 +164,7 @@ public class DefaultScriptOperators<K> implements ScriptOperators<K> {
|
||||
try {
|
||||
return Flux.defer(() -> action.doInRedis(conn)).doFinally(signal -> conn.close());
|
||||
} catch (RuntimeException e) {
|
||||
|
||||
conn.close();
|
||||
throw e;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-2017 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.
|
||||
@@ -37,23 +37,32 @@ import org.springframework.util.Assert;
|
||||
public class DefaultRedisScript<T> implements RedisScript<T>, InitializingBean {
|
||||
|
||||
private ScriptSource scriptSource;
|
||||
|
||||
private String sha1;
|
||||
|
||||
private Class<T> resultType;
|
||||
|
||||
private final Object shaModifiedMonitor = new Object();
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultRedisScript}
|
||||
*/
|
||||
public DefaultRedisScript() {}
|
||||
public DefaultRedisScript() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultRedisScript}
|
||||
*
|
||||
* @param script must not be {@literal null}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public DefaultRedisScript(String script) {
|
||||
this(script, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultRedisScript}
|
||||
*
|
||||
* @param script
|
||||
* @param resultType
|
||||
* @param script must not be {@literal null}.
|
||||
* @param resultType can be {@literal null}.
|
||||
*/
|
||||
public DefaultRedisScript(String script, Class<T> resultType) {
|
||||
|
||||
@@ -61,11 +70,20 @@ public class DefaultRedisScript<T> implements RedisScript<T>, InitializingBean {
|
||||
this.resultType = resultType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(this.scriptSource, "Either script, script location," + " or script source is required");
|
||||
Assert.state(this.scriptSource != null, "Either script, script location," + " or script source is required");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.script.RedisScript#getSha1()
|
||||
*/
|
||||
public String getSha1() {
|
||||
|
||||
synchronized (shaModifiedMonitor) {
|
||||
if (sha1 == null || scriptSource.isModified()) {
|
||||
this.sha1 = DigestUtils.sha1DigestAsHex(getScriptAsString());
|
||||
@@ -74,11 +92,20 @@ public class DefaultRedisScript<T> implements RedisScript<T>, InitializingBean {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.script.RedisScript#getResultType()
|
||||
*/
|
||||
public Class<T> getResultType() {
|
||||
return this.resultType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.script.RedisScript#getScriptAsString()
|
||||
*/
|
||||
public String getScriptAsString() {
|
||||
|
||||
try {
|
||||
return scriptSource.getScriptAsString();
|
||||
} catch (IOException e) {
|
||||
@@ -95,7 +122,7 @@ public class DefaultRedisScript<T> implements RedisScript<T>, InitializingBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param script The script text
|
||||
* @param scriptText The script text
|
||||
*/
|
||||
public void setScriptText(String scriptText) {
|
||||
this.scriptSource = new StaticScriptSource(scriptText);
|
||||
|
||||
@@ -17,7 +17,6 @@ package org.springframework.data.redis.core.script;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
import org.springframework.data.redis.RedisSystemException;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.ReturnType;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2017 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.script;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.redis.serializer.RedisElementReader;
|
||||
import org.springframework.data.redis.serializer.RedisElementWriter;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
/**
|
||||
* Executes {@link RedisScript}s using reactive infrastructure.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @param <K> The type of keys that may be passed during script execution
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ReactiveScriptExecutor<K> {
|
||||
|
||||
/**
|
||||
* Execute the given {@link RedisScript}
|
||||
*
|
||||
* @param script must not be {@literal null}.
|
||||
* @return the return value of the script or {@link Flux#empty()} if {@link RedisScript#getResultType()} is
|
||||
* {@literal null}, likely indicating a throw-away status reply (i.e. "OK")
|
||||
*/
|
||||
default <T> Flux<T> execute(RedisScript<T> script) {
|
||||
return execute(script, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given {@link RedisScript}
|
||||
*
|
||||
* @param script must not be {@literal null}.
|
||||
* @param keys must not be {@literal null}.
|
||||
* @return the return value of the script or {@link Flux#empty()} if {@link RedisScript#getResultType()} is
|
||||
* {@literal null}, likely indicating a throw-away status reply (i.e. "OK")
|
||||
*/
|
||||
default <T> Flux<T> execute(RedisScript<T> script, List<K> keys) {
|
||||
return execute(script, keys, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link RedisScript}
|
||||
*
|
||||
* @param script The script to execute. Must not be {@literal null}.
|
||||
* @param keys Any keys that need to be passed to the script. Must not be {@literal null}.
|
||||
* @param args Any args that need to be passed to the script. Can be {@literal empty}.
|
||||
* @return The return value of the script or {@link Flux#empty()} if {@link RedisScript#getResultType()} is
|
||||
* {@literal null}, likely indicating a throw-away status reply (i.e. "OK")
|
||||
*/
|
||||
<T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args);
|
||||
|
||||
/**
|
||||
* Executes the given {@link RedisScript}, using the provided {@link RedisSerializer}s to serialize the script
|
||||
* arguments and result.
|
||||
*
|
||||
* @param script The script to execute. must not be {@literal null}.
|
||||
* @param keys Any keys that need to be passed to the script
|
||||
* @param args Any args that need to be passed to the script
|
||||
* @param argsWriter The {@link RedisElementWriter} to use for serializing args. Must not be {@literal null}.
|
||||
* @param resultReader The {@link RedisElementReader} to use for serializing the script return value. Must not be
|
||||
* {@literal null}.
|
||||
* @return The return value of the script or {@link Flux#empty()} if {@link RedisScript#getResultType()} is
|
||||
* {@literal null}, likely indicating a throw-away status reply (i.e. "OK")
|
||||
*/
|
||||
<T> Flux<T> execute(RedisScript<T> script, List<K> keys, List<?> args, RedisElementWriter<?> argsWriter,
|
||||
RedisElementReader<T> resultReader);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2017 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.
|
||||
@@ -15,30 +15,67 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core.script;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A script to be executed using the <a href="http://redis.io/commands/eval">Redis scripting support</a> available as of
|
||||
* version 2.6
|
||||
*
|
||||
* @author Jennifer Hickey
|
||||
* @param <T> The script result type. Should be one of Long, Boolean, List, or deserialized value type. Can be null if
|
||||
* the script returns a throw-away status (i.e "OK")
|
||||
* @author Christoph Strobl
|
||||
* @param <T> The script result type. Should be one of Long, Boolean, List, or deserialized value type. Can be
|
||||
* {@litearl null} if the script returns a throw-away status (i.e "OK")
|
||||
*/
|
||||
public interface RedisScript<T> {
|
||||
|
||||
/**
|
||||
* @return The SHA1 of the script, used for executing Redis evalsha command
|
||||
* @return The SHA1 of the script, used for executing Redis evalsha command.
|
||||
*/
|
||||
String getSha1();
|
||||
|
||||
/**
|
||||
* @return The script result type. Should be one of Long, Boolean, List, or deserialized value type. Can be null if
|
||||
* the script returns a throw-away status (i.e "OK")
|
||||
* @return The script result type. Should be one of Long, Boolean, List, or deserialized value type. {@literal null}
|
||||
* if the script returns a throw-away status (i.e "OK").
|
||||
*/
|
||||
Class<T> getResultType();
|
||||
|
||||
/**
|
||||
* @return The script contents
|
||||
* @return The script contents.
|
||||
*/
|
||||
String getScriptAsString();
|
||||
|
||||
/**
|
||||
* @return {@literal true} if result type is {@literal null} and does not need any further deserialization.
|
||||
* @since 2.0
|
||||
*/
|
||||
default boolean returnsRawValue() {
|
||||
return getResultType() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RedisScript} from {@link String}.
|
||||
*
|
||||
* @param script must not be {@literal null}.
|
||||
* @return new instance of {@link RedisScript}.
|
||||
* @since 2.0
|
||||
*/
|
||||
static <T> RedisScript<T> of(String script) {
|
||||
return new DefaultRedisScript<>(script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RedisScript} from {@link String}.
|
||||
*
|
||||
* @param script must not be {@literal null}.
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @return new instance of {@link RedisScript}.
|
||||
* @since 2.0
|
||||
*/
|
||||
static <T> RedisScript of(String script, Class<T> resultType) {
|
||||
|
||||
Assert.notNull(script, "Script must not be null!");
|
||||
Assert.notNull(resultType, "ResultType must not be null!");
|
||||
|
||||
return new DefaultRedisScript(script, resultType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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.script;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
/**
|
||||
* Executes {@link RedisScript}s using reactive infrastructure.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @param <K> The type of keys that may be passed during script execution
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface ScriptOperators<K> {
|
||||
|
||||
/**
|
||||
* Executes the given {@link RedisScript}
|
||||
*
|
||||
* @param script The script to execute
|
||||
* @param keys Any keys that need to be passed to the script
|
||||
* @param args Any args that need to be passed to the script
|
||||
* @return The return value of the script or null if {@link RedisScript#getResultType()} is null, likely indicating a
|
||||
* throw-away status reply (i.e. "OK")
|
||||
*/
|
||||
<T> Flux<T> execute(RedisScript<T> script, List<K> keys, Object... args);
|
||||
|
||||
/**
|
||||
* Executes the given {@link RedisScript}, using the provided {@link RedisSerializer}s to serialize the script
|
||||
* arguments and result.
|
||||
*
|
||||
* @param script The script to execute
|
||||
* @param argsSerializer The {@link SerializationPair} to use for serializing args
|
||||
* @param resultSerializer The {@link SerializationPair} to use for serializing the script return value
|
||||
* @param keys Any keys that need to be passed to the script
|
||||
* @param args Any args that need to be passed to the script
|
||||
* @return The return value of the script or null if {@link RedisScript#getResultType()} is null, likely indicating a
|
||||
* throw-away status reply (i.e. "OK")
|
||||
*/
|
||||
<T> Flux<T> execute(RedisScript<T> script, SerializationPair<?> argsSerializer, SerializationPair<T> resultSerializer,
|
||||
List<K> keys, Object... args);
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
* Utilities for Lua script execution and result deserialization.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
class ScriptUtils {
|
||||
@@ -45,7 +46,7 @@ class ScriptUtils {
|
||||
static <T> T deserializeResult(RedisSerializer<T> resultSerializer, Object result) {
|
||||
|
||||
if (result instanceof byte[]) {
|
||||
return resultSerializer == null ? (T) result : resultSerializer.deserialize((byte[]) result);
|
||||
return resultSerializer.deserialize((byte[]) result);
|
||||
}
|
||||
|
||||
if (result instanceof List) {
|
||||
@@ -66,7 +67,7 @@ class ScriptUtils {
|
||||
* Deserialize {@code result} using {@link RedisElementReader} to the reader type. Collection types and intermediate
|
||||
* collection elements are deserialized recursivly.
|
||||
*
|
||||
* @param resultSerializer must not be {@literal null}.
|
||||
* @param reader must not be {@literal null}.
|
||||
* @param result must not be {@literal null}.
|
||||
* @return the deserialized result.
|
||||
*/
|
||||
@@ -75,7 +76,7 @@ class ScriptUtils {
|
||||
|
||||
if (result instanceof ByteBuffer) {
|
||||
|
||||
return reader == null ? (T) result : reader.read((ByteBuffer) result);
|
||||
return reader.read((ByteBuffer) result);
|
||||
}
|
||||
|
||||
if (result instanceof List) {
|
||||
|
||||
@@ -17,11 +17,14 @@ package org.springframework.data.redis.serializer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Strategy interface that specifies a deserializer that can deserialize a binary element representation stored in Redis
|
||||
* into an object.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@@ -34,4 +37,16 @@ public interface RedisElementReader<T> {
|
||||
* @return the deserialized value.
|
||||
*/
|
||||
T read(ByteBuffer buffer);
|
||||
|
||||
/**
|
||||
* Create new {@link RedisElementReader} using given {@link RedisSerializer}.
|
||||
*
|
||||
* @param serializer must not be {@literal null}.
|
||||
* @return new instance of {@link RedisElementReader}.
|
||||
*/
|
||||
static <T> RedisElementReader<T> from(RedisSerializer<T> serializer) {
|
||||
|
||||
Assert.notNull(serializer, "Serializer must not be null!");
|
||||
return new DefaultRedisElementReader<>(serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,15 @@ package org.springframework.data.redis.serializer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Strategy interface that specifies a serializer that can serialize an element to its binary representation to be used
|
||||
* as Redis protocol payload.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RedisElementWriter<T> {
|
||||
@@ -29,8 +33,20 @@ public interface RedisElementWriter<T> {
|
||||
/**
|
||||
* Serialize a {@code element} to its {@link ByteBuffer} representation.
|
||||
*
|
||||
* @param element
|
||||
* @param element can be {@literal null}.
|
||||
* @return the {@link ByteBuffer} representing {@code element} in its binary form.
|
||||
*/
|
||||
ByteBuffer write(T element);
|
||||
|
||||
/**
|
||||
* Create new {@link RedisElementWriter} using given {@link RedisSerializer}.
|
||||
*
|
||||
* @param serializer must not be {@literal null}.
|
||||
* @return new instance of {@link RedisElementWriter}.
|
||||
*/
|
||||
static <T> RedisElementWriter<T> from(RedisSerializer<T> serializer) {
|
||||
|
||||
Assert.notNull(serializer, "Serializer must not be null!");
|
||||
return new DefaultRedisElementWriter<>(serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import io.lettuce.core.ScriptOutputType;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -34,6 +35,7 @@ import org.springframework.data.redis.connection.ReturnType;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class LettuceReactiveScriptingCommandsTests extends LettuceReactiveCommandsTestsBase {
|
||||
|
||||
@@ -44,7 +46,7 @@ public class LettuceReactiveScriptingCommandsTests extends LettuceReactiveComman
|
||||
|
||||
String sha1 = nativeCommands.scriptLoad("return KEYS[1]");
|
||||
|
||||
StepVerifier.create(connection.scriptingCommands().scriptExists("foo", sha1)) //
|
||||
StepVerifier.create(connection.scriptingCommands().scriptExists(Arrays.asList("foo", sha1))) //
|
||||
.expectNext(false) //
|
||||
.expectNext(true) //
|
||||
.verifyComplete();
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisElementReader;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
@@ -167,7 +168,7 @@ public class ReactiveRedisTemplateIntegrationTests<K, V> {
|
||||
|
||||
@Test // DATAREDIS-683
|
||||
@SuppressWarnings("unchecked")
|
||||
public void execute() {
|
||||
public void executeScript() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
V value = valueFactory.instance();
|
||||
@@ -184,25 +185,26 @@ public class ReactiveRedisTemplateIntegrationTests<K, V> {
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-683
|
||||
public void executeWithSerializationPairs() {
|
||||
public void executeScriptWithElementReaderAndWriter() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
V value = valueFactory.instance();
|
||||
|
||||
SerializationPair<Person> json = SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Person.class));
|
||||
SerializationPair<String> string = SerializationPair.fromSerializer(new StringRedisSerializer());
|
||||
SerializationPair json = SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Person.class));
|
||||
RedisElementReader<String> resultReader = RedisElementReader.from(new StringRedisSerializer());
|
||||
|
||||
assumeFalse(value instanceof Long);
|
||||
|
||||
Person person = new Person("Walter", "White", 51);
|
||||
StepVerifier.create(
|
||||
redisTemplate.execute(new DefaultRedisScript<>("return redis.call('set', KEYS[1], ARGV[1])", String.class),
|
||||
json, string, Collections.singletonList(key), person))
|
||||
StepVerifier
|
||||
.create(
|
||||
redisTemplate.execute(new DefaultRedisScript<>("return redis.call('set', KEYS[1], ARGV[1])", String.class),
|
||||
Collections.singletonList(key), Collections.singletonList(person), json.getWriter(), resultReader))
|
||||
.expectNext("OK").verifyComplete();
|
||||
|
||||
Flux<Person> execute = redisTemplate.execute(
|
||||
new DefaultRedisScript<>("return redis.call('get', KEYS[1])", Person.class), json, json,
|
||||
Collections.singletonList(key));
|
||||
new DefaultRedisScript<>("return redis.call('get', KEYS[1])", Person.class), Collections.singletonList(key),
|
||||
Collections.emptyList(), json.getWriter(), json.getReader());
|
||||
|
||||
StepVerifier.create(execute).expectNext(person).verifyComplete();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.redis.Person;
|
||||
import org.springframework.data.redis.RedisSystemException;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
@@ -37,9 +38,10 @@ import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DefaultScriptOperatorsUnitTests {
|
||||
public class DefaultReactiveScriptExecutorUnitTests {
|
||||
|
||||
private final DefaultRedisScript<String> SCRIPT = new DefaultRedisScript<>("return KEYS[0]", String.class);
|
||||
|
||||
@@ -47,7 +49,7 @@ public class DefaultScriptOperatorsUnitTests {
|
||||
@Mock ReactiveRedisConnection connectionMock;
|
||||
@Mock ReactiveScriptingCommands scriptingCommandsMock;
|
||||
|
||||
DefaultScriptOperators<String> executor;
|
||||
DefaultReactiveScriptExecutor<String> executor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -55,7 +57,7 @@ public class DefaultScriptOperatorsUnitTests {
|
||||
when(connectionFactoryMock.getReactiveConnection()).thenReturn(connectionMock);
|
||||
when(connectionMock.scriptingCommands()).thenReturn(scriptingCommandsMock);
|
||||
|
||||
executor = new DefaultScriptOperators<>(connectionFactoryMock, RedisSerializationContext.string());
|
||||
executor = new DefaultReactiveScriptExecutor<>(connectionFactoryMock, RedisSerializationContext.string());
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-683
|
||||
@@ -64,7 +66,7 @@ public class DefaultScriptOperatorsUnitTests {
|
||||
when(scriptingCommandsMock.evalSha(anyString(), any(ReturnType.class), anyInt()))
|
||||
.thenReturn(Flux.just(ByteBuffer.wrap("FOO".getBytes())));
|
||||
|
||||
StepVerifier.create(executor.execute(SCRIPT, Collections.emptyList())).expectNext("FOO").verifyComplete();
|
||||
StepVerifier.create(executor.execute(SCRIPT)).expectNext("FOO").verifyComplete();
|
||||
|
||||
verify(scriptingCommandsMock).evalSha(anyString(), any(ReturnType.class), anyInt());
|
||||
verify(scriptingCommandsMock, never()).eval(any(), any(ReturnType.class), anyInt());
|
||||
@@ -79,7 +81,7 @@ public class DefaultScriptOperatorsUnitTests {
|
||||
when(scriptingCommandsMock.eval(any(), any(ReturnType.class), anyInt()))
|
||||
.thenReturn(Flux.just(ByteBuffer.wrap("FOO".getBytes())));
|
||||
|
||||
StepVerifier.create(executor.execute(SCRIPT, Collections.emptyList())).expectNext("FOO").verifyComplete();
|
||||
StepVerifier.create(executor.execute(SCRIPT)).expectNext("FOO").verifyComplete();
|
||||
|
||||
verify(scriptingCommandsMock).evalSha(anyString(), any(ReturnType.class), anyInt());
|
||||
verify(scriptingCommandsMock).eval(any(), any(ReturnType.class), anyInt());
|
||||
@@ -91,8 +93,7 @@ public class DefaultScriptOperatorsUnitTests {
|
||||
when(scriptingCommandsMock.evalSha(anyString(), any(ReturnType.class), anyInt())).thenReturn(Flux
|
||||
.error(new UnsupportedOperationException("NOSCRIPT No matching script. Please use EVAL.", new Exception())));
|
||||
|
||||
StepVerifier.create(executor.execute(SCRIPT, Collections.emptyList()))
|
||||
.expectError(UnsupportedOperationException.class).verify();
|
||||
StepVerifier.create(executor.execute(SCRIPT)).expectError(UnsupportedOperationException.class).verify();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-683
|
||||
@@ -116,8 +117,19 @@ public class DefaultScriptOperatorsUnitTests {
|
||||
when(scriptingCommandsMock.evalSha(anyString(), any(ReturnType.class), anyInt()))
|
||||
.thenReturn(Flux.error(new RuntimeException()));
|
||||
|
||||
StepVerifier.create(executor.execute(SCRIPT, Collections.emptyList())).expectError().verify();
|
||||
StepVerifier.create(executor.execute(SCRIPT)).expectError().verify();
|
||||
|
||||
verify(connectionMock).close();
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-683
|
||||
public void doesNotConvertRawResult() {
|
||||
|
||||
Person returnValue = new Person();
|
||||
|
||||
when(scriptingCommandsMock.evalSha(anyString(), any(ReturnType.class), anyInt()))
|
||||
.thenReturn(Flux.just(returnValue));
|
||||
|
||||
StepVerifier.create(executor.execute(RedisScript.of("return KEYS[0]"))).expectNext(returnValue).verifyComplete();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2017 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.
|
||||
@@ -26,11 +26,13 @@ import org.springframework.scripting.support.StaticScriptSource;
|
||||
* Test of {@link DefaultRedisScript}
|
||||
*
|
||||
* @author Jennifer Hickey
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class DefaultRedisScriptTests {
|
||||
|
||||
@Test
|
||||
public void testGetSha1() {
|
||||
|
||||
StaticScriptSource script = new StaticScriptSource("return KEYS[1]");
|
||||
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.setScriptSource(script);
|
||||
@@ -45,6 +47,7 @@ public class DefaultRedisScriptTests {
|
||||
|
||||
@Test
|
||||
public void testGetScriptAsString() {
|
||||
|
||||
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.setScriptText("return ARGS[1]");
|
||||
redisScript.setResultType(String.class);
|
||||
@@ -53,14 +56,16 @@ public class DefaultRedisScriptTests {
|
||||
|
||||
@Test(expected = ScriptingException.class)
|
||||
public void testGetScriptAsStringError() {
|
||||
|
||||
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("nonexistent")));
|
||||
redisScript.setResultType(Long.class);
|
||||
redisScript.getScriptAsString();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void initializeWithNoScript() throws Exception {
|
||||
|
||||
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
||||
redisScript.afterPropertiesSet();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user