]> git.saurik.com Git - redis.git/blobdiff - src/scripting.c
full conversion from Lua return value to redis reply. Partial conversion from Redis...
[redis.git] / src / scripting.c
index d3c18663ffc8a53ad541d0498fcca48a1fca93b1..5f169037ae1b8f8252c3ef9e6724a2f6321310bb 100644 (file)
@@ -5,6 +5,76 @@
 #include <lauxlib.h>
 #include <lualib.h>
 
+char *redisProtocolToLuaType_Int(lua_State *lua, char *reply);
+char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
+char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
+
+/* Take a Redis reply in the Redis protocol format and convert it into a
+ * Lua type. Thanks to this function, and the introduction of not connected
+ * clients, it is trvial to implement the redis() lua function.
+ *
+ * Basically we take the arguments, execute the Redis command in the context
+ * of a non connected client, then take the generated reply and convert it
+ * into a suitable Lua type. With this trick the scripting feature does not
+ * need the introduction of a full Redis internals API. Basically the script
+ * is like a normal client that bypasses all the slow I/O paths.
+ *
+ * Note: in this function we do not do any sanity check as the reply is
+ * generated by Redis directly. This allows use to go faster.
+ * The reply string can be altered during the parsing as it is discared
+ * after the conversion is completed.
+ *
+ * Errors are returned as a table with a single 'err' field set to the
+ * error string.
+ */
+
+char *redisProtocolToLuaType(lua_State *lua, char* reply) {
+    char *p = reply;
+
+    switch(*p) {
+    case ':':
+        p = redisProtocolToLuaType_Int(lua,reply);
+        break;
+    case '$':
+        p = redisProtocolToLuaType_Bulk(lua,reply);
+        break;
+    case '+':
+        p = redisProtocolToLuaType_Status(lua,reply);
+        break;
+    }
+    return p;
+}
+
+char *redisProtocolToLuaType_Int(lua_State *lua, char *reply) {
+    char *p = strchr(reply+1,'\r');
+    long long value;
+
+    string2ll(reply+1,p-reply-1,&value);
+    lua_pushnumber(lua,(lua_Number)value);
+    return p+2;
+}
+
+char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply) {
+    char *p = strchr(reply+1,'\r');
+    long long bulklen;
+
+    string2ll(reply+1,p-reply-1,&bulklen);
+    if (bulklen == 0) {
+        lua_pushnil(lua);
+        return p+2;
+    } else {
+        lua_pushlstring(lua,p+2,bulklen);
+        return p+2+bulklen+2;
+    }
+}
+
+char *redisProtocolToLuaType_Status(lua_State *lua, char *reply) {
+    char *p = strchr(reply+1,'\r');
+
+    lua_pushlstring(lua,reply+1,p-reply-1);
+    return p+2;
+}
+
 int luaRedisCommand(lua_State *lua) {
     int j, argc = lua_gettop(lua);
     struct redisCommand *cmd;
@@ -12,18 +82,24 @@ int luaRedisCommand(lua_State *lua) {
     redisClient *c = server.lua_client;
     sds reply;
 
+    /* Build the arguments vector */
     argv = zmalloc(sizeof(robj*)*argc);
     for (j = 0; j < argc; j++)
-        argv[j] = createStringObject((char*)lua_tostring(lua,j+1),lua_strlen(lua,j+1));
+        argv[j] = createStringObject((char*)lua_tostring(lua,j+1),
+                                     lua_strlen(lua,j+1));
 
     /* Command lookup */
     cmd = lookupCommand(argv[0]->ptr);
     if (!cmd) {
+        for (j = 0; j < argc; j++) decrRefCount(argv[j]);
         zfree(argv);
-        lua_pushnil(lua);
+        lua_newtable(lua);
+        lua_pushstring(lua,"err");
         lua_pushstring(lua,"Unknown Redis command called from Lua script");
-        return 2;
+        lua_settable(lua,-3);
+        return 1;
     }
+
     /* Run the command in the context of a fake client */
     c->argv = argv;
     c->argc = argc;
@@ -43,7 +119,7 @@ int luaRedisCommand(lua_State *lua) {
         sdscatlen(reply,o->ptr,sdslen(o->ptr));
         listDelNode(c->reply,listFirst(c->reply));
     }
-    lua_pushlstring(lua,reply,sdslen(reply));
+    redisProtocolToLuaType(lua,reply);
     sdsfree(reply);
 
     /* Clean up. Command code may have changed argv/argc so we use the
@@ -104,15 +180,73 @@ void luaReplyToRedisReply(redisClient *c, lua_State *lua) {
     case LUA_TNUMBER:
         addReplyLongLong(c,(long long)lua_tonumber(lua,1));
         break;
+    case LUA_TTABLE:
+        /* We need to check if it is an array or an error.
+         * Error are returned as a single element table with 'err' field. */
+        lua_pushstring(lua,"err");
+        lua_gettable(lua,-2);
+        t = lua_type(lua,-1);
+        if (t == LUA_TSTRING) {
+            addReplyError(c,(char*)lua_tostring(lua,-1));
+            lua_pop(lua,1);
+        } else {
+            void *replylen = addDeferredMultiBulkLength(c);
+            int j = 1, mbulklen = 0;
+
+            lua_pop(lua,1); /* Discard the 'err' field value we popped */
+            while(1) {
+                lua_pushnumber(lua,j++);
+                lua_gettable(lua,-2);
+                t = lua_type(lua,-1);
+                if (t == LUA_TNIL) {
+                    lua_pop(lua,1);
+                    break;
+                } else if (t == LUA_TSTRING) {
+                    size_t len;
+                    char *s = (char*) lua_tolstring(lua,-1,&len);
+
+                    addReplyBulkCBuffer(c,s,len);
+                    mbulklen++;
+                } else if (t == LUA_TNUMBER) {
+                    addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
+                    mbulklen++;
+                }
+                lua_pop(lua,1);
+            }
+            setDeferredMultiBulkLength(c,replylen,mbulklen);
+        }
+        break;
     default:
         addReply(c,shared.nullbulk);
     }
     lua_pop(lua,1);
 }
 
+/* Set an array of Redis String Objects as a Lua array (table) stored into a
+ * global variable. */
+void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec) {
+    int j;
+
+    lua_newtable(lua);
+    for (j = 0; j < elec; j++) {
+        lua_pushlstring(lua,(char*)elev[j]->ptr,sdslen(elev[j]->ptr));
+        lua_rawseti(lua,-2,j+1);
+    }
+    lua_setglobal(lua,var);
+}
+
 void evalCommand(redisClient *c) {
     lua_State *lua = server.lua;
     char funcname[43];
+    long long numkeys;
+
+    /* Get the number of arguments that are keys */
+    if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK)
+        return;
+    if (numkeys > (c->argc - 3)) {
+        addReplyError(c,"Number of keys can't be greater than number of args");
+        return;
+    }
 
     /* We obtain the script SHA1, then check if this function is already
      * defined into the Lua state */
@@ -148,6 +282,11 @@ void evalCommand(redisClient *c) {
         }
         lua_getglobal(lua, funcname);
     }
+
+    /* Populate the argv and keys table accordingly to the arguments that
+     * EVAL received. */
+    luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys);
+    luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
     
     /* At this point whatever this script was never seen before or if it was
      * already defined, we can call it. We have zero arguments and expect