From: antirez Date: Sun, 1 May 2011 12:47:52 +0000 (+0200) Subject: full conversion from Lua return value to redis reply. Partial conversion from Redis... X-Git-Url: https://git.saurik.com/redis.git/commitdiff_plain/532e0f5ded866881d46f0aeac3756ebd3a3da98e full conversion from Lua return value to redis reply. Partial conversion from Redis reply to Lua type. --- diff --git a/src/scripting.c b/src/scripting.c index 09686ed3..5f169037 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -5,6 +5,76 @@ #include #include +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,6 +180,42 @@ 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); }