diff --git a/src/main/java/io/vertx/redis/client/LuaScript.java b/src/main/java/io/vertx/redis/client/LuaScript.java
new file mode 100644
index 00000000..47e1324a
--- /dev/null
+++ b/src/main/java/io/vertx/redis/client/LuaScript.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * The Apache License v2.0 is available at
+ * http://www.opensource.org/licenses/apache2.0.php
+ *
+ * You may elect to redistribute this code under either of these licenses.
+ */
+package io.vertx.redis.client;
+
+import io.vertx.redis.client.util.Hash;
+
+/**
+ * LuaScript
+ *
+ *
Lua script with pre-computed sha hash.
+ */
+public class LuaScript {
+
+ // this lua script
+ private final String lua;
+
+ // pre-computed sha
+ private final String sha;
+
+ public LuaScript(String lua) {
+ this.lua = lua;
+ this.sha = Hash.sha1(lua);
+ }
+
+ /**
+ * @return the lua
+ */
+ public String getLua() {
+ return lua;
+ }
+
+ /**
+ * @return the sha
+ */
+ public String getSha() {
+ return sha;
+ }
+}
diff --git a/src/main/java/io/vertx/redis/client/RedisAPI.java b/src/main/java/io/vertx/redis/client/RedisAPI.java
index 78c4cce8..4fef59ce 100644
--- a/src/main/java/io/vertx/redis/client/RedisAPI.java
+++ b/src/main/java/io/vertx/redis/client/RedisAPI.java
@@ -21,6 +21,8 @@
import io.vertx.core.Future;
import io.vertx.redis.client.impl.RedisAPIImpl;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import static io.vertx.codegen.annotations.GenIgnore.PERMITTED_TYPE;
@@ -3165,4 +3167,97 @@ static RedisAPI api(RedisConnection connection) {
*/
@GenIgnore
Future<@Nullable Response> send(Command cmd, String... args);
+
+ /**
+ * Run redis command eval lua with arguments but will try evalsha first, if
+ * evalsha failed with `NOSCRIPT` error, will retry with the script, so the
+ * next try evalsha will success as script has already been cached.
+ *
+ * Example:
+ * {@code
+ * final static LuaScript luaScript = new LuaScript("return ARGV[1]", 0);
+ * ... some codes ...
+ * evalLua(luaScript, Arrays.asList("hello"));
+ * }
+ *
+ * @param luaScript lua script with pre-computed sha hash
+ * @return Future response.
+ */
+ @GenIgnore
+ default Future evalLua(LuaScript luaScript, List args) {
+ // pass empty args if no args, no null check
+ List redisArgs = new ArrayList<>(args.size() + 1);
+ redisArgs.add(luaScript.getSha());
+ redisArgs.addAll(args);
+
+ return evalsha(redisArgs)
+ .compose(
+ // directly use result when succeeded
+ result -> Future.succeededFuture(result),
+ t -> {
+ // load script and try ONCE again when failed cause script missing
+ if (t.getMessage().startsWith("NOSCRIPT")) {
+ // directly eval script and then script will be cached in redis, so
+ // evalhash will success next time
+ redisArgs.set(0, luaScript.getLua());
+ return eval(redisArgs);
+ }
+
+ // fail when failed cause not script missing
+ return Future.failedFuture(t);
+ });
+ }
+
+ /**
+ * evalLua with no pre-computed sha.
+ *
+ * NOTE: prefer to define a static variable with type of LuaScript and use the
+ * `evalLua(LuaScript, List)`, with this method, sha will always be computed.
+ *
+ * @param lua string of lua script
+ * @param keys list of keys, size of keys will be numkeys argument
+ * @param args list of args
+ * @return Future response.
+ */
+ @GenIgnore
+ default Future evalLua(String lua, List keys, List args) {
+ LuaScript luaScript = new LuaScript(lua);
+
+ List allArgs = new ArrayList<>();
+
+ int numKeys;
+ if (keys != null) {
+ numKeys = keys.size();
+ } else {
+ numKeys = 0;
+ }
+ allArgs.add(String.valueOf(numKeys));
+
+ if (numKeys != 0) {
+ allArgs.addAll(keys);
+ }
+
+ if (args != null && args.size() != 0) {
+ allArgs.addAll(args);
+ }
+
+ return evalLua(luaScript, allArgs);
+ }
+
+ /**
+ * evalLua with no pre-computed sha.
+ *
+ * NOTE: prefer to define a static variable with type of LuaScript and use the
+ * `evalLua(LuaScript, List)`, with this method, sha will always be computed.
+ *
+ * @param numkeys, keys, args. If args is empty, a numkeys = 0 will add by default
+ * @return Future response.
+ */
+ @GenIgnore
+ default Future evalLua(String lua, String... args) {
+ if (args.length == 0) {
+ return evalLua(new LuaScript(lua), Arrays.asList("0"));
+ }
+ return evalLua(new LuaScript(lua), Arrays.asList(args));
+ }
}
diff --git a/src/main/java/io/vertx/redis/client/util/Hash.java b/src/main/java/io/vertx/redis/client/util/Hash.java
new file mode 100644
index 00000000..3fb6f728
--- /dev/null
+++ b/src/main/java/io/vertx/redis/client/util/Hash.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * The Apache License v2.0 is available at
+ * http://www.opensource.org/licenses/apache2.0.php
+ *
+ * You may elect to redistribute this code under either of these licenses.
+ */
+package io.vertx.redis.client.util;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class Hash {
+
+ private static String bytesToHex(byte[] hash) {
+ StringBuilder hexString = new StringBuilder(2 * hash.length);
+ for (int i = 0; i < hash.length; i++) {
+ String hex = Integer.toHexString(0xff & hash[i]);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ }
+
+ public static String hash(String input, String name) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance(name);
+ byte[] encodedhash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
+ return bytesToHex(encodedhash);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String sha1(String input) {
+ return hash(input, "SHA-1");
+ }
+}
diff --git a/src/test/java/io/vertx/redis/client/test/RedisTest.java b/src/test/java/io/vertx/redis/client/test/RedisTest.java
index ba75a579..2b2d3e73 100644
--- a/src/test/java/io/vertx/redis/client/test/RedisTest.java
+++ b/src/test/java/io/vertx/redis/client/test/RedisTest.java
@@ -334,4 +334,52 @@ public void testBatch2(TestContext should) {
})
.onSuccess(responses -> should.fail("Commands are wrong"));
}
+
+ @Test
+ public void testEvalLua(TestContext should) {
+ final String lua = "return ARGV[2]";
+ final Async test = should.async();
+
+ Redis
+ .createClient(rule.vertx(),
+ new RedisOptions().setConnectionString("redis://" + redis.getHost() + ":" + redis.getFirstMappedPort()))
+ .connect().onComplete(create -> {
+ should.assertTrue(create.succeeded());
+
+ RedisAPI redis = RedisAPI.api(create.result());
+
+ redis.evalLua(lua, "0", "hello", "world").onComplete(should.asyncAssertSuccess(r -> {
+ should.assertNotNull(r);
+ should.assertEquals("world", r.toString());
+ test.complete();
+ }));
+ });
+ }
+
+ @Test
+ public void testEvalLua2(TestContext should) {
+ final String script = "return ARGV[3]";
+ final List keys = Arrays.asList("hello");
+ final List args = Arrays.asList("1", "22", "333");
+ final Async test = should.async();
+
+ Redis
+ .createClient(rule.vertx(),
+ new RedisOptions().setConnectionString("redis://" + redis.getHost() + ":" + redis.getFirstMappedPort()))
+ .connect().onComplete(create -> {
+ should.assertTrue(create.succeeded());
+
+ RedisAPI redis = RedisAPI.api(create.result());
+
+ redis.script(Arrays.asList("flush"))
+ .compose(_resp -> redis.evalLua(script, keys, args))
+ .compose(_resp -> redis.script(Arrays.asList("flush")))
+ .compose(_resp -> redis.evalLua(script, keys, args))
+ .onComplete(should.asyncAssertSuccess(r -> {
+ should.assertNotNull(r);
+ should.assertEquals("333", r.toString());
+ test.complete();
+ }));
+ });
+ }
}