From 0f1d64ca577c8594e26b9e1663e49c8e1bc13757 Mon Sep 17 00:00:00 2001
From: antirez <antirez@gmail.com>
Date: Sat, 30 Apr 2011 22:29:21 +0200
Subject: [PATCH] Lua call of Redis command work in progress: sorry I have to
 go to the cinema to watch the Source Code movie

---
 src/networking.c | 22 +++++++++++-------
 src/redis.h      |  1 +
 src/scripting.c  | 58 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 73 insertions(+), 8 deletions(-)

diff --git a/src/networking.c b/src/networking.c
index 4a6a8afd8..b26c20595 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -14,14 +14,20 @@ redisClient *createClient(int fd) {
     redisClient *c = zmalloc(sizeof(redisClient));
     c->bufpos = 0;
 
-    anetNonBlock(NULL,fd);
-    anetTcpNoDelay(NULL,fd);
-    if (aeCreateFileEvent(server.el,fd,AE_READABLE,
-        readQueryFromClient, c) == AE_ERR)
-    {
-        close(fd);
-        zfree(c);
-        return NULL;
+    /* passing -1 as fd it is possible to create a non connected client.
+     * This is useful since all the Redis commands needs to be executed
+     * in the context of a client. When commands are executed in other
+     * contexts (for instance a Lua script) we need a non connected client. */
+    if (fd != -1) {
+        anetNonBlock(NULL,fd);
+        anetTcpNoDelay(NULL,fd);
+        if (aeCreateFileEvent(server.el,fd,AE_READABLE,
+            readQueryFromClient, c) == AE_ERR)
+        {
+            close(fd);
+            zfree(c);
+            return NULL;
+        }
     }
 
     selectDb(c,0);
diff --git a/src/redis.h b/src/redis.h
index 4848de95b..0071c0aa4 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -657,6 +657,7 @@ struct redisServer {
     clusterState cluster;
     /* Scripting */
     lua_State *lua;
+    redisClient *lua_client;
 };
 
 typedef struct pubsubPattern {
diff --git a/src/scripting.c b/src/scripting.c
index 42ba3bf95..9b957c92d 100644
--- a/src/scripting.c
+++ b/src/scripting.c
@@ -5,9 +5,67 @@
 #include <lauxlib.h>
 #include <lualib.h>
 
+int luaRedisCommand(lua_State *lua) {
+    int j, argc = lua_gettop(lua);
+    struct redisCommand *cmd;
+    robj **argv;
+    redisClient *c = server.lua_client;
+    sds reply;
+
+    argv = zmalloc(sizeof(robj*)*argc);
+    for (j = 0; j < argc; j++)
+        argv[j] = createStringObject(lua_tostring(lua,j+1),lua_strlen(lua,j+1));
+
+    /* Command lookup */
+    cmd = lookupCommand(argv[0]->ptr);
+    if (!cmd) {
+        zfree(argv);
+        lua_pushnil(lua);
+        lua_pushstring(lua,"Unknown Redis command called from Lua script");
+        return 2;
+    }
+    /* Run the command in the context of a fake client */
+    c->argv = argv;
+    c->argc = argc;
+    cmd->proc(c);
+
+    /* Convert the result of the Redis command into a suitable Lua type.
+     * The first thing we need is to create a single string from the client
+     * output buffers. */
+    reply = sdsempty();
+    if (c->bufpos) {
+        reply = sdscatlen(reply,c->bufpos,c->buf);
+        c->bufpos = 0;
+    }
+    while(listLength(c->reply)) {
+        robj *o = listNodeValue(listFirst(c->reply));
+
+        sdscatlen(reply,o->ptr,sdslen(o->ptr));
+        listDelNode(c->reply,listFirst(c->reply));
+    }
+    lua_pushnumber(lua,1);
+
+    /* Clean up. Command code may have changed argv/argc so we use the
+     * argv/argc of the client instead of the local variables. */
+    for (j = 0; j < c->argc; j++)
+        decrRefCount(c->argv[j]);
+    zfree(c->argv);
+
+    return 1;
+}
+
 void scriptingInit(void) {
     lua_State *lua = lua_open();
     luaL_openlibs(lua);
+
+    /* Register the 'r' command */
+    lua_pushcfunction(lua,luaRedisCommand);
+    lua_setglobal(lua,"r");
+
+    /* Create the (non connected) client that we use to execute Redis commands
+     * inside the Lua interpreter */
+    server.lua_client = createClient(-1);
+
     server.lua = lua;
 }