diff --git a/src/aof.c b/src/aof.c index 5c64c06f9..39d452390 100644 --- a/src/aof.c +++ b/src/aof.c @@ -2142,19 +2142,9 @@ static int rewriteFunctions(rio *aof) { dictEntry *entry = NULL; while ((entry = dictNext(iter))) { functionLibInfo *li = dictGetVal(entry); - if (li->desc) { - if (rioWrite(aof, "*7\r\n", 4) == 0) goto werr; - } else { - if (rioWrite(aof, "*5\r\n", 4) == 0) goto werr; - } + if (rioWrite(aof, "*3\r\n", 4) == 0) goto werr; char function_load[] = "$8\r\nFUNCTION\r\n$4\r\nLOAD\r\n"; if (rioWrite(aof, function_load, sizeof(function_load) - 1) == 0) goto werr; - if (rioWriteBulkString(aof, li->ei->name, sdslen(li->ei->name)) == 0) goto werr; - if (rioWriteBulkString(aof, li->name, sdslen(li->name)) == 0) goto werr; - if (li->desc) { - if (rioWriteBulkString(aof, "description", 11) == 0) goto werr; - if (rioWriteBulkString(aof, li->desc, sdslen(li->desc)) == 0) goto werr; - } if (rioWriteBulkString(aof, li->code, sdslen(li->code)) == 0) goto werr; } dictReleaseIterator(iter); diff --git a/src/commands.c b/src/commands.c index 0730a71c1..cd61cc262 100644 --- a/src/commands.c +++ b/src/commands.c @@ -3425,10 +3425,7 @@ NULL /* FUNCTION LOAD argument table */ struct redisCommandArg FUNCTION_LOAD_Args[] = { -{"engine-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE}, -{"library-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE}, {"replace",ARG_TYPE_PURE_TOKEN,-1,"REPLACE",NULL,NULL,CMD_ARG_OPTIONAL}, -{"library-description",ARG_TYPE_STRING,-1,"DESCRIPTION",NULL,NULL,CMD_ARG_OPTIONAL}, {"function-code",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE}, {0} }; @@ -3481,7 +3478,7 @@ struct redisCommand FUNCTION_Subcommands[] = { {"help","Show helpful text about the different subcommands","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FUNCTION_HELP_History,FUNCTION_HELP_tips,functionHelpCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SCRIPTING}, {"kill","Kill the function currently in execution.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FUNCTION_KILL_History,FUNCTION_KILL_tips,functionKillCommand,2,CMD_NOSCRIPT|CMD_ALLOW_BUSY,ACL_CATEGORY_SCRIPTING}, {"list","List information about all the functions","O(N) where N is the number of functions","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FUNCTION_LIST_History,FUNCTION_LIST_tips,functionListCommand,-2,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,.args=FUNCTION_LIST_Args}, -{"load","Create a function with the given arguments (name, code, description)","O(1) (considering compilation time is redundant)","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FUNCTION_LOAD_History,FUNCTION_LOAD_tips,functionLoadCommand,-5,CMD_NOSCRIPT|CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SCRIPTING,.args=FUNCTION_LOAD_Args}, +{"load","Create a function with the given arguments (name, code, description)","O(1) (considering compilation time is redundant)","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FUNCTION_LOAD_History,FUNCTION_LOAD_tips,functionLoadCommand,-3,CMD_NOSCRIPT|CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SCRIPTING,.args=FUNCTION_LOAD_Args}, {"restore","Restore all the functions on the given payload","O(N) where N is the number of functions on the payload","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FUNCTION_RESTORE_History,FUNCTION_RESTORE_tips,functionRestoreCommand,-3,CMD_NOSCRIPT|CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SCRIPTING,.args=FUNCTION_RESTORE_Args}, {"stats","Return information about the function currently running (name, description, duration)","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,FUNCTION_STATS_History,FUNCTION_STATS_tips,functionStatsCommand,2,CMD_NOSCRIPT|CMD_ALLOW_BUSY,ACL_CATEGORY_SCRIPTING}, {0} diff --git a/src/commands/function-load.json b/src/commands/function-load.json index 0a363e328..d04721279 100644 --- a/src/commands/function-load.json +++ b/src/commands/function-load.json @@ -4,7 +4,7 @@ "complexity": "O(1) (considering compilation time is redundant)", "group": "scripting", "since": "7.0.0", - "arity": -5, + "arity": -3, "container": "FUNCTION", "function": "functionLoadCommand", "command_flags": [ @@ -20,26 +20,12 @@ "RESPONSE_POLICY:ALL_SUCCEEDED" ], "arguments": [ - { - "name": "engine-name", - "type": "string" - }, - { - "name": "library-name", - "type": "string" - }, { "name": "replace", "type": "pure-token", "token": "REPLACE", "optional": true }, - { - "name": "library-description", - "type": "string", - "token": "DESCRIPTION", - "optional": true - }, { "name": "function-code", "type": "string" diff --git a/src/functions.c b/src/functions.c index 739d178aa..d327d3358 100644 --- a/src/functions.c +++ b/src/functions.c @@ -57,6 +57,12 @@ struct functionsLibCtx { dict *engines_stats; /* Per engine statistics */ }; +typedef struct functionsLibMataData { + sds engine; + sds name; + sds code; +} functionsLibMataData; + dictType engineDictType = { dictSdsCaseHash, /* hash function */ dictSdsDup, /* key dup */ @@ -124,7 +130,6 @@ static size_t functionMallocSize(functionInfo *fi) { static size_t libraryMallocSize(functionLibInfo *li) { return zmalloc_size(li) + sdsZmallocSize(li->name) - + (li->desc ? sdsZmallocSize(li->desc) : 0) + sdsZmallocSize(li->code); } @@ -157,7 +162,6 @@ static void engineLibraryFree(functionLibInfo* li) { dictRelease(li->functions); sdsfree(li->name); sdsfree(li->code); - if (li->desc) sdsfree(li->desc); zfree(li); } @@ -265,14 +269,13 @@ int functionLibCreateFunction(sds name, void *function, functionLibInfo *li, sds return C_OK; } -static functionLibInfo* engineLibraryCreate(sds name, engineInfo *ei, sds desc, sds code) { +static functionLibInfo* engineLibraryCreate(sds name, engineInfo *ei, sds code) { functionLibInfo *li = zmalloc(sizeof(*li)); *li = (functionLibInfo) { .name = sdsdup(name), .functions = dictCreate(&libraryFunctionDictType), .ei = ei, .code = sdsdup(code), - .desc = desc ? sdsdup(desc) : NULL, }; return li; } @@ -540,17 +543,11 @@ void functionListCommand(client *c) { } } ++reply_len; - addReplyMapLen(c, with_code? 5 : 4); + addReplyMapLen(c, with_code? 4 : 3); addReplyBulkCString(c, "library_name"); addReplyBulkCBuffer(c, li->name, sdslen(li->name)); addReplyBulkCString(c, "engine"); addReplyBulkCBuffer(c, li->ei->name, sdslen(li->ei->name)); - addReplyBulkCString(c, "description"); - if (li->desc) { - addReplyBulkCBuffer(c, li->desc, sdslen(li->desc)); - } else { - addReplyNull(c); - } addReplyBulkCString(c, "functions"); addReplyArrayLen(c, dictSize(li->functions)); @@ -745,11 +742,11 @@ void functionRestoreCommand(client *c) { err = sdsnew("can not read data type"); goto load_error; } - if (type != RDB_OPCODE_FUNCTION) { + if (type != RDB_OPCODE_FUNCTION && type != RDB_OPCODE_FUNCTION2) { err = sdsnew("given type is not a function"); goto load_error; } - if (rdbFunctionLoad(&payload, rdbver, functions_lib_ctx, RDBFLAGS_NONE, &err) != C_OK) { + if (rdbFunctionLoad(&payload, rdbver, functions_lib_ctx, type, RDBFLAGS_NONE, &err) != C_OK) { if (!err) { err = sdsnew("failed loading the given functions payload"); } @@ -868,36 +865,111 @@ static int functionsVerifyName(sds name) { return C_OK; } -/* Compile and save the given library, return C_OK on success and C_ERR on failure. - * In case on failure the err out param is set with relevant error message */ -int functionsCreateWithLibraryCtx(sds lib_name,sds engine_name, sds desc, sds code, - int replace, sds* err, functionsLibCtx *lib_ctx) { - dictIterator *iter = NULL; - dictEntry *entry = NULL; - if (functionsVerifyName(lib_name)) { - *err = sdsnew("Library names can only contain letters and numbers and must be at least one character long"); +int functionExtractLibMetaData(sds payload, functionsLibMataData *md, sds *err) { + sds name = NULL; + sds desc = NULL; + sds engine = NULL; + sds code = NULL; + if (strncmp(payload, "#!", 2) != 0) { + *err = sdsnew("Missing library metadata"); return C_ERR; } - - engineInfo *ei = dictFetchValue(engines, engine_name); - if (!ei) { - *err = sdsnew("Engine not found"); + char *shebang_end = strchr(payload, '\n'); + if (shebang_end == NULL) { + *err = sdsnew("Invalid library metadata"); return C_ERR; } + size_t shebang_len = shebang_end - payload; + sds shebang = sdsnewlen(payload, shebang_len); + int numparts; + sds *parts = sdssplitargs(shebang, &numparts); + sdsfree(shebang); + if (!parts || numparts == 0) { + *err = sdsnew("Invalid library metadata"); + sdsfreesplitres(parts, numparts); + return C_ERR; + } + engine = sdsdup(parts[0]); + sdsrange(engine, 2, -1); + for (int i = 1 ; i < numparts ; ++i) { + sds part = parts[i]; + if (strncasecmp(part, "name=", 5) == 0) { + if (name) { + *err = sdscatfmt(sdsempty(), "Invalid metadata value, name argument was given multiple times"); + goto error; + } + name = sdsdup(part); + sdsrange(name, 5, -1); + continue; + } + *err = sdscatfmt(sdsempty(), "Invalid metadata value given: %s", part); + goto error; + } + + if (!name) { + *err = sdsnew("Library name was not given"); + goto error; + } + + sdsfreesplitres(parts, numparts); + + md->name = name; + md->code = sdsnewlen(shebang_end, sdslen(payload) - shebang_len); + md->engine = engine; + + return C_OK; + +error: + if (name) sdsfree(name); + if (desc) sdsfree(desc); + if (engine) sdsfree(engine); + if (code) sdsfree(code); + sdsfreesplitres(parts, numparts); + return C_ERR; +} + +void functionFreeLibMetaData(functionsLibMataData *md) { + if (md->code) sdsfree(md->code); + if (md->name) sdsfree(md->name); + if (md->engine) sdsfree(md->engine); +} + +/* Compile and save the given library, return the loaded library name on success + * and NULL on failure. In case on failure the err out param is set with relevant error message */ +sds functionsCreateWithLibraryCtx(sds code, int replace, sds* err, functionsLibCtx *lib_ctx) { + dictIterator *iter = NULL; + dictEntry *entry = NULL; + functionLibInfo *new_li = NULL; + functionLibInfo *old_li = NULL; + functionsLibMataData md = {0}; + if (functionExtractLibMetaData(code, &md, err) != C_OK) { + return NULL; + } + + if (functionsVerifyName(md.name)) { + *err = sdsnew("Library names can only contain letters and numbers and must be at least one character long"); + goto error; + } + + engineInfo *ei = dictFetchValue(engines, md.engine); + if (!ei) { + *err = sdscatfmt(sdsempty(), "Engine '%S' not found", md.engine); + goto error; + } engine *engine = ei->engine; - functionLibInfo *old_li = dictFetchValue(lib_ctx->libraries, lib_name); + old_li = dictFetchValue(lib_ctx->libraries, md.name); if (old_li && !replace) { - *err = sdsnew("Library already exists"); - return C_ERR; + *err = sdscatfmt(sdsempty(), "Library '%S' already exists", md.name); + goto error; } if (old_li) { libraryUnlink(lib_ctx, old_li); } - functionLibInfo *new_li = engineLibraryCreate(lib_name, ei, desc, code); - if (engine->create(engine->engine_ctx, new_li, code, err) != C_OK) { + new_li = engineLibraryCreate(md.name, ei, code); + if (engine->create(engine->engine_ctx, new_li, md.code, err) != C_OK) { goto error; } @@ -925,48 +997,34 @@ int functionsCreateWithLibraryCtx(sds lib_name,sds engine_name, sds desc, sds co engineLibraryFree(old_li); } - return C_OK; + sds loaded_lib_name = md.name; + md.name = NULL; + functionFreeLibMetaData(&md); + + return loaded_lib_name; error: if (iter) dictReleaseIterator(iter); - engineLibraryFree(new_li); - if (old_li) { - libraryLink(lib_ctx, old_li); - } - return C_ERR; + if (new_li) engineLibraryFree(new_li); + if (old_li) libraryLink(lib_ctx, old_li); + functionFreeLibMetaData(&md); + return NULL; } /* - * FUNCTION LOAD - * [REPLACE] [DESC ] - * - * ENGINE NAME - name of the engine to use the run the library - * LIBRARY NAME - name of the library + * FUNCTION LOAD [REPLACE] * REPLACE - optional, replace existing library - * DESCRIPTION - optional, library description * LIBRARY CODE - library code to pass to the engine */ void functionLoadCommand(client *c) { - robj *engine_name = c->argv[2]; - robj *library_name = c->argv[3]; - int replace = 0; - int argc_pos = 4; - sds desc = NULL; + int argc_pos = 2; while (argc_pos < c->argc - 1) { robj *next_arg = c->argv[argc_pos++]; if (!strcasecmp(next_arg->ptr, "replace")) { replace = 1; continue; } - if (!strcasecmp(next_arg->ptr, "description")) { - if (argc_pos >= c->argc) { - addReplyError(c, "Bad function description"); - return; - } - desc = c->argv[argc_pos++]->ptr; - continue; - } addReplyErrorFormat(c, "Unknown option given: %s", (char*)next_arg->ptr); return; } @@ -978,8 +1036,8 @@ void functionLoadCommand(client *c) { robj *code = c->argv[argc_pos]; sds err = NULL; - if (functionsCreateWithLibraryCtx(library_name->ptr, engine_name->ptr, - desc, code->ptr, replace, &err, curr_functions_lib_ctx) != C_OK) + sds library_name = NULL; + if (!(library_name = functionsCreateWithLibraryCtx(code->ptr, replace, &err, curr_functions_lib_ctx))) { addReplyErrorSds(c, err); return; @@ -987,7 +1045,7 @@ void functionLoadCommand(client *c) { /* Indicate that the command changed the data so it will be replicated and * counted as a data change (for persistence configuration) */ server.dirty++; - addReply(c, shared.ok); + addReplyBulkSds(c, library_name); } /* Return memory usage of all the engines combine */ diff --git a/src/functions.h b/src/functions.h index fb2b74de9..40716dbc7 100644 --- a/src/functions.h +++ b/src/functions.h @@ -106,12 +106,10 @@ struct functionLibInfo { dict *functions; /* Functions dictionary */ engineInfo *ei; /* Pointer to the function engine */ sds code; /* Library code */ - sds desc; /* Library description */ }; int functionsRegisterEngine(const char *engine_name, engine *engine_ctx); -int functionsCreateWithLibraryCtx(sds lib_name, sds engine_name, sds desc, sds code, - int replace, sds* err, functionsLibCtx *lib_ctx); +sds functionsCreateWithLibraryCtx(sds code, int replace, sds* err, functionsLibCtx *lib_ctx); unsigned long functionsMemory(); unsigned long functionsMemoryOverhead(); unsigned long functionsNum(); diff --git a/src/rdb.c b/src/rdb.c index d5f853dd8..0283630f7 100644 --- a/src/rdb.c +++ b/src/rdb.c @@ -1242,24 +1242,9 @@ ssize_t rdbSaveFunctions(rio *rdb) { ssize_t written = 0; ssize_t ret; while ((entry = dictNext(iter))) { - if ((ret = rdbSaveType(rdb, RDB_OPCODE_FUNCTION)) < 0) goto werr; + if ((ret = rdbSaveType(rdb, RDB_OPCODE_FUNCTION2)) < 0) goto werr; written += ret; functionLibInfo *li = dictGetVal(entry); - if ((ret = rdbSaveRawString(rdb, (unsigned char *) li->name, sdslen(li->name))) < 0) goto werr; - written += ret; - if ((ret = rdbSaveRawString(rdb, (unsigned char *) li->ei->name, sdslen(li->ei->name))) < 0) goto werr; - written += ret; - if (li->desc) { - /* desc exists */ - if ((ret = rdbSaveLen(rdb, 1)) < 0) goto werr; - written += ret; - if ((ret = rdbSaveRawString(rdb, (unsigned char *) li->desc, sdslen(li->desc))) < 0) goto werr; - written += ret; - } else { - /* desc not exists */ - if ((ret = rdbSaveLen(rdb, 0)) < 0) goto werr; - written += ret; - } if ((ret = rdbSaveRawString(rdb, (unsigned char *) li->code, sdslen(li->code))) < 0) goto werr; written += ret; } @@ -2811,56 +2796,79 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) { * * The lib_ctx argument is also optional. If NULL is given, only verify rdb * structure with out performing the actual functions loading. */ -int rdbFunctionLoad(rio *rdb, int ver, functionsLibCtx* lib_ctx, int rdbflags, sds *err) { +int rdbFunctionLoad(rio *rdb, int ver, functionsLibCtx* lib_ctx, int type, int rdbflags, sds *err) { UNUSED(ver); - sds name = NULL; - sds engine_name = NULL; - sds desc = NULL; - sds blob = NULL; - uint64_t has_desc; sds error = NULL; + sds final_payload = NULL; int res = C_ERR; - if (!(name = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { - error = sdsnew("Failed loading library name"); - goto error; - } + if (type == RDB_OPCODE_FUNCTION) { + /* RDB that was generated on versions 7.0 rc1 and 7.0 rc2 has another + * an old format that contains the library name, engine and description. + * To support this format we must read those values. */ + sds name = NULL; + sds engine_name = NULL; + sds desc = NULL; + sds blob = NULL; + uint64_t has_desc; - if (!(engine_name = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { - error = sdsnew("Failed loading engine name"); - goto error; - } + if (!(name = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { + error = sdsnew("Failed loading library name"); + goto cleanup; + } - if ((has_desc = rdbLoadLen(rdb, NULL)) == RDB_LENERR) { - error = sdsnew("Failed loading library description indicator"); - goto error; - } + if (!(engine_name = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { + error = sdsnew("Failed loading engine name"); + goto cleanup; + } - if (has_desc && !(desc = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { - error = sdsnew("Failed loading library description"); - goto error; - } + if ((has_desc = rdbLoadLen(rdb, NULL)) == RDB_LENERR) { + error = sdsnew("Failed loading library description indicator"); + goto cleanup; + } - if (!(blob = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { - error = sdsnew("Failed loading library blob"); - goto error; + if (has_desc && !(desc = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { + error = sdsnew("Failed loading library description"); + goto cleanup; + } + + if (!(blob = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { + error = sdsnew("Failed loading library blob"); + goto cleanup; + } + /* Translate old format (versions 7.0 rc1 and 7.0 rc2) to new format. + * The new format has the library name and engine inside the script payload. + * Add those parameters to the original script payload (ignore the description if exists). */ + final_payload = sdscatfmt(sdsempty(), "#!%s name=%s\n%s", engine_name, name, blob); +cleanup: + if (name) sdsfree(name); + if (engine_name) sdsfree(engine_name); + if (desc) sdsfree(desc); + if (blob) sdsfree(blob); + if (error) goto done; + } else if (type == RDB_OPCODE_FUNCTION2) { + if (!(final_payload = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { + error = sdsnew("Failed loading library payload"); + goto done; + } + } else { + serverPanic("Bad function type was given to rdbFunctionLoad"); } if (lib_ctx) { - if (functionsCreateWithLibraryCtx(name, engine_name, desc, blob, rdbflags & RDBFLAGS_ALLOW_DUP, &error, lib_ctx) != C_OK) { + sds library_name = NULL; + if (!(library_name = functionsCreateWithLibraryCtx(final_payload, rdbflags & RDBFLAGS_ALLOW_DUP, &error, lib_ctx))) { if (!error) { error = sdsnew("Failed creating the library"); } - goto error; + goto done; } + sdsfree(library_name); } res = C_OK; -error: - if (name) sdsfree(name); - if (engine_name) sdsfree(engine_name); - if (desc) sdsfree(desc); - if (blob) sdsfree(blob); +done: + if (final_payload) sdsfree(final_payload); if (error) { if (err) { *err = error; @@ -3091,9 +3099,9 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin decrRefCount(aux); continue; /* Read next opcode. */ } - } else if (type == RDB_OPCODE_FUNCTION) { + } else if (type == RDB_OPCODE_FUNCTION || type == RDB_OPCODE_FUNCTION2) { sds err = NULL; - if (rdbFunctionLoad(rdb, rdbver, rdb_loading_ctx->functions_lib_ctx, rdbflags, &err) != C_OK) { + if (rdbFunctionLoad(rdb, rdbver, rdb_loading_ctx->functions_lib_ctx, type, rdbflags, &err) != C_OK) { serverLog(LL_WARNING,"Failed loading library, %s", err); sdsfree(err); goto eoferr; diff --git a/src/rdb.h b/src/rdb.h index 0d298c40d..4f057a252 100644 --- a/src/rdb.h +++ b/src/rdb.h @@ -101,7 +101,8 @@ #define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 19)) /* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */ -#define RDB_OPCODE_FUNCTION 246 /* engine data */ +#define RDB_OPCODE_FUNCTION2 245 /* function library data */ +#define RDB_OPCODE_FUNCTION 246 /* old function library data for 7.0 rc1 and rc2 */ #define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */ #define RDB_OPCODE_IDLE 248 /* LRU idle time. */ #define RDB_OPCODE_FREQ 249 /* LFU frequency. */ @@ -170,7 +171,7 @@ int rdbSaveBinaryFloatValue(rio *rdb, float val); int rdbLoadBinaryFloatValue(rio *rdb, float *val); int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi); int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadingCtx *rdb_loading_ctx); -int rdbFunctionLoad(rio *rdb, int ver, functionsLibCtx* lib_ctx, int rdbflags, sds *err); +int rdbFunctionLoad(rio *rdb, int ver, functionsLibCtx* lib_ctx, int type, int rdbflags, sds *err); int rdbSaveRio(int req, rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi); ssize_t rdbSaveFunctions(rio *rdb); rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi); diff --git a/src/redis-check-rdb.c b/src/redis-check-rdb.c index 7fc798e45..accdc35b0 100644 --- a/src/redis-check-rdb.c +++ b/src/redis-check-rdb.c @@ -63,6 +63,7 @@ struct { #define RDB_CHECK_DOING_READ_LEN 6 #define RDB_CHECK_DOING_READ_AUX 7 #define RDB_CHECK_DOING_READ_MODULE_AUX 8 +#define RDB_CHECK_DOING_READ_FUNCTIONS 9 char *rdb_check_doing_string[] = { "start", @@ -73,7 +74,8 @@ char *rdb_check_doing_string[] = { "check-sum", "read-len", "read-aux", - "read-module-aux" + "read-module-aux", + "read-functions" }; char *rdb_type_string[] = { @@ -303,9 +305,10 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) { robj *o = rdbLoadCheckModuleValue(&rdb,name); decrRefCount(o); continue; /* Read type again. */ - } else if (type == RDB_OPCODE_FUNCTION) { + } else if (type == RDB_OPCODE_FUNCTION || type == RDB_OPCODE_FUNCTION2) { sds err = NULL; - if (rdbFunctionLoad(&rdb, rdbver, NULL, 0, &err) != C_OK) { + rdbstate.doing = RDB_CHECK_DOING_READ_FUNCTIONS; + if (rdbFunctionLoad(&rdb, rdbver, NULL, type, 0, &err) != C_OK) { rdbCheckError("Failed loading library, %s", err); sdsfree(err); goto err; diff --git a/tests/cluster/tests/00-base.tcl b/tests/cluster/tests/00-base.tcl index 656128e53..12d8244a8 100644 --- a/tests/cluster/tests/00-base.tcl +++ b/tests/cluster/tests/00-base.tcl @@ -64,7 +64,7 @@ test "It is possible to write and read from the cluster" { } test "Function no-cluster flag" { - R 1 function load lua test { + R 1 function load {#!lua name=test redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-cluster'}} } catch {R 1 fcall f1 0} e diff --git a/tests/integration/redis-cli.tcl b/tests/integration/redis-cli.tcl index 7479ac8b4..e159fb17d 100644 --- a/tests/integration/redis-cli.tcl +++ b/tests/integration/redis-cli.tcl @@ -346,7 +346,7 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS set dir [lindex [r config get dir] 1] assert_equal "OK" [r debug populate 100000 key 1000] - assert_equal "OK" [r function load lua lib1 "redis.register_function('func1', function() return 123 end)"] + assert_equal "lib1" [r function load "#!lua name=lib1\nredis.register_function('func1', function() return 123 end)"] if {$functions_only} { set args "--functions-rdb $dir/cli.rdb" } else { @@ -359,10 +359,10 @@ if {!$::tls} { ;# fake_redis_node doesn't support TLS file rename "$dir/cli.rdb" "$dir/dump.rdb" assert_equal "OK" [r set should-not-exist 1] - assert_equal "OK" [r function load lua should_not_exist_func "redis.register_function('should_not_exist_func', function() return 456 end)"] + assert_equal "should_not_exist_func" [r function load "#!lua name=should_not_exist_func\nredis.register_function('should_not_exist_func', function() return 456 end)"] assert_equal "OK" [r debug reload nosave] assert_equal {} [r get should-not-exist] - assert_equal {{library_name lib1 engine LUA description {} functions {{name func1 description {} flags {}}}}} [r function list] + assert_equal {{library_name lib1 engine LUA functions {{name func1 description {} flags {}}}}} [r function list] if {$functions_only} { assert_equal 0 [r dbsize] } else { diff --git a/tests/integration/replication-4.tcl b/tests/integration/replication-4.tcl index b8c50308a..281d5a8eb 100644 --- a/tests/integration/replication-4.tcl +++ b/tests/integration/replication-4.tcl @@ -47,7 +47,7 @@ start_server {tags {"repl external:skip"}} { set slave [srv 0 client] # Load some functions to be used later - $master FUNCTION load lua test replace { + $master FUNCTION load replace {#!lua name=test redis.register_function{function_name='f_default_flags', callback=function(keys, args) return redis.call('get',keys[1]) end, flags={}} redis.register_function{function_name='f_no_writes', callback=function(keys, args) return redis.call('get',keys[1]) end, flags={'no-writes'}} } diff --git a/tests/integration/replication.tcl b/tests/integration/replication.tcl index 05f62d5e8..44915be1b 100644 --- a/tests/integration/replication.tcl +++ b/tests/integration/replication.tcl @@ -523,10 +523,14 @@ foreach testType {Successful Aborted} { $replica set mykey myvalue # Set a function value on replica to check status during loading, on failure and after swapping db - $replica function load LUA test {redis.register_function('test', function() return 'hello1' end)} + $replica function load {#!lua name=test + redis.register_function('test', function() return 'hello1' end) + } # Set a function value on master to check it reaches the replica when replication ends - $master function load LUA test {redis.register_function('test', function() return 'hello2' end)} + $master function load {#!lua name=test + redis.register_function('test', function() return 'hello2' end) + } # Force the replica to try another full sync (this time it will have matching master replid) $master multi @@ -659,7 +663,9 @@ test {diskless loading short read} { set start [clock clicks -milliseconds] # Set a function value to check short read handling on functions - r function load LUA test {redis.register_function('test', function() return 'hello1' end)} + r function load {#!lua name=test + redis.register_function('test', function() return 'hello1' end) + } for {set k 0} {$k < 3} {incr k} { for {set i 0} {$i < 10} {incr i} { diff --git a/tests/support/redis.tcl b/tests/support/redis.tcl index 5743be5f4..edcc1fb48 100644 --- a/tests/support/redis.tcl +++ b/tests/support/redis.tcl @@ -188,6 +188,10 @@ proc ::redis::__method__readraw {id fd val} { set ::redis::readraw($id) $val } +proc ::redis::__method__readingraw {id fd} { + return $::redis::readraw($id) +} + proc ::redis::__method__attributes {id fd} { set _ $::redis::attributes($id) } diff --git a/tests/unit/aofrw.tcl b/tests/unit/aofrw.tcl index ac861a653..45db1939f 100644 --- a/tests/unit/aofrw.tcl +++ b/tests/unit/aofrw.tcl @@ -185,7 +185,7 @@ start_server {tags {"aofrw external:skip"} overrides {aof-use-rdb-preamble no}} test "AOF rewrite functions" { r flushall - r FUNCTION LOAD LUA test DESCRIPTION {desc} { + r FUNCTION LOAD {#!lua name=test redis.register_function('test', function() return 1 end) } r bgrewriteaof @@ -194,7 +194,7 @@ start_server {tags {"aofrw external:skip"} overrides {aof-use-rdb-preamble no}} r debug loadaof assert_equal [r fcall test 0] 1 r FUNCTION LIST - } {{library_name test engine LUA description desc functions {{name test description {} flags {}}}}} + } {{library_name test engine LUA functions {{name test description {} flags {}}}}} test {BGREWRITEAOF is delayed if BGSAVE is in progress} { r flushall diff --git a/tests/unit/cluster.tcl b/tests/unit/cluster.tcl index 48ff92536..9d49a2dee 100644 --- a/tests/unit/cluster.tcl +++ b/tests/unit/cluster.tcl @@ -173,7 +173,9 @@ start_multiple_servers 5 [list overrides $base_conf] { # upload a function to all the cluster exec src/redis-cli --cluster-yes --cluster call 127.0.0.1:[srv 0 port] \ - FUNCTION LOAD LUA TEST {redis.register_function('test', function() return 'hello' end)} + FUNCTION LOAD {#!lua name=TEST + redis.register_function('test', function() return 'hello' end) + } # adding node to the cluster exec src/redis-cli --cluster-yes --cluster add-node \ @@ -190,13 +192,15 @@ start_multiple_servers 5 [list overrides $base_conf] { } # make sure 'test' function was added to the new node - assert_equal {{library_name TEST engine LUA description {} functions {{name test description {} flags {}}}}} [$node4_rd FUNCTION LIST] + assert_equal {{library_name TEST engine LUA functions {{name test description {} flags {}}}}} [$node4_rd FUNCTION LIST] # add function to node 5 - assert_equal {OK} [$node5_rd FUNCTION LOAD LUA TEST {redis.register_function('test', function() return 'hello' end)}] + assert_equal {TEST} [$node5_rd FUNCTION LOAD {#!lua name=TEST + redis.register_function('test', function() return 'hello' end) + }] # make sure functions was added to node 5 - assert_equal {{library_name TEST engine LUA description {} functions {{name test description {} flags {}}}}} [$node5_rd FUNCTION LIST] + assert_equal {{library_name TEST engine LUA functions {{name test description {} flags {}}}}} [$node5_rd FUNCTION LIST] # adding node 5 to the cluster should failed because it already contains the 'test' function catch { diff --git a/tests/unit/functions.tcl b/tests/unit/functions.tcl index 3a0458f68..4c08261ed 100644 --- a/tests/unit/functions.tcl +++ b/tests/unit/functions.tcl @@ -1,61 +1,61 @@ proc get_function_code {args} { - return [format "redis.register_function('%s', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0] [lindex $args 1]] + return [format "#!%s name=%s\nredis.register_function('%s', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0] [lindex $args 1] [lindex $args 2] [lindex $args 3]] } proc get_no_writes_function_code {args} { - return [format "redis.register_function{function_name='%s', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0] [lindex $args 1]] + return [format "#!%s name=%s\nredis.register_function{function_name='%s', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0] [lindex $args 1] [lindex $args 2] [lindex $args 3]] } start_server {tags {"scripting"}} { test {FUNCTION - Basic usage} { - r function load LUA test [get_function_code test {return 'hello'}] + r function load [get_function_code LUA test test {return 'hello'}] r fcall test 0 } {hello} test {FUNCTION - Load with unknown argument} { catch { - r function load LUA test foo bar [get_function_code test {return 'hello'}] + r function load foo bar [get_function_code LUA test test {return 'hello'}] } e set _ $e } {*Unknown option given*} test {FUNCTION - Create an already exiting library raise error} { catch { - r function load LUA test [get_function_code test {return 'hello1'}] + r function load [get_function_code LUA test test {return 'hello1'}] } e set _ $e } {*already exists*} test {FUNCTION - Create an already exiting library raise error (case insensitive)} { catch { - r function load LUA TEST [get_function_code test {return 'hello1'}] + r function load [get_function_code LUA test test {return 'hello1'}] } e set _ $e } {*already exists*} test {FUNCTION - Create a library with wrong name format} { catch { - r function load LUA {bad\0foramat} [get_function_code test {return 'hello1'}] + r function load [get_function_code LUA {bad\0foramat} test {return 'hello1'}] } e set _ $e } {*Library names can only contain letters and numbers*} test {FUNCTION - Create library with unexisting engine} { catch { - r function load bad_engine test [get_function_code test {return 'hello1'}] + r function load [get_function_code bad_engine test test {return 'hello1'}] } e set _ $e - } {*Engine not found*} + } {*Engine 'bad_engine' not found*} test {FUNCTION - Test uncompiled script} { catch { - r function load LUA test1 {bad script} + r function load replace [get_function_code LUA test test {bad script}] } e set _ $e } {*Error compiling function*} test {FUNCTION - test replace argument} { - r function load LUA test REPLACE [get_function_code test {return 'hello1'}] + r function load REPLACE [get_function_code LUA test test {return 'hello1'}] r fcall test 0 } {hello1} @@ -76,12 +76,8 @@ start_server {tags {"scripting"}} { set _ $e } {*Function not found*} - test {FUNCTION - test description argument} { - r function load LUA test DESCRIPTION {some description} [get_function_code test {return 'hello'}] - r function list - } {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}} - test {FUNCTION - test fcall bad arguments} { + r function load [get_function_code LUA test test {return 'hello'}] catch { r fcall test bad_arg } e @@ -133,14 +129,14 @@ start_server {tags {"scripting"}} { assert_match "*Error trying to load the RDB*" $e r debug reload noflush merge r function list - } {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}} {needs:debug} + } {{library_name test engine LUA functions {{name test description {} flags {}}}}} {needs:debug} test {FUNCTION - test debug reload with nosave and noflush} { r function delete test r set x 1 - r function load LUA test1 DESCRIPTION {some description} [get_function_code test1 {return 'hello'}] + r function load [get_function_code LUA test1 test1 {return 'hello'}] r debug reload - r function load LUA test2 DESCRIPTION {some description} [get_function_code test2 {return 'hello'}] + r function load [get_function_code LUA test2 test2 {return 'hello'}] r debug reload nosave noflush merge assert_equal [r fcall test1 0] {hello} assert_equal [r fcall test2 0] {hello} @@ -148,21 +144,21 @@ start_server {tags {"scripting"}} { test {FUNCTION - test flushall and flushdb do not clean functions} { r function flush - r function load lua test REPLACE [get_function_code test {return redis.call('set', 'x', '1')}] + r function load REPLACE [get_function_code lua test test {return redis.call('set', 'x', '1')}] r flushall r flushdb r function list - } {{library_name test engine LUA description {} functions {{name test description {} flags {}}}}} + } {{library_name test engine LUA functions {{name test description {} flags {}}}}} test {FUNCTION - test function dump and restore} { r function flush - r function load lua test description {some description} [get_function_code test {return 'hello'}] + r function load [get_function_code lua test test {return 'hello'}] set e [r function dump] r function delete test assert_match {} [r function list] r function restore $e r function list - } {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}} + } {{library_name test engine LUA functions {{name test description {} flags {}}}}} test {FUNCTION - test function dump and restore with flush argument} { set e [r function dump] @@ -170,17 +166,17 @@ start_server {tags {"scripting"}} { assert_match {} [r function list] r function restore $e FLUSH r function list - } {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}} + } {{library_name test engine LUA functions {{name test description {} flags {}}}}} test {FUNCTION - test function dump and restore with append argument} { set e [r function dump] r function flush assert_match {} [r function list] - r function load lua test [get_function_code test {return 'hello1'}] + r function load [get_function_code lua test test {return 'hello1'}] catch {r function restore $e APPEND} err assert_match {*already exists*} $err r function flush - r function load lua test1 [get_function_code test1 {return 'hello1'}] + r function load [get_function_code lua test1 test1 {return 'hello1'}] r function restore $e APPEND assert_match {hello} [r fcall test 0] assert_match {hello1} [r fcall test1 0] @@ -188,11 +184,11 @@ start_server {tags {"scripting"}} { test {FUNCTION - test function dump and restore with replace argument} { r function flush - r function load LUA test DESCRIPTION {some description} [get_function_code test {return 'hello'}] + r function load [get_function_code LUA test test {return 'hello'}] set e [r function dump] r function flush assert_match {} [r function list] - r function load lua test [get_function_code test {return 'hello1'}] + r function load [get_function_code lua test test {return 'hello1'}] assert_match {hello1} [r fcall test 0] r function restore $e REPLACE assert_match {hello} [r fcall test 0] @@ -200,11 +196,11 @@ start_server {tags {"scripting"}} { test {FUNCTION - test function restore with bad payload do not drop existing functions} { r function flush - r function load LUA test DESCRIPTION {some description} [get_function_code test {return 'hello'}] + r function load [get_function_code LUA test test {return 'hello'}] catch {r function restore bad_payload} e assert_match {*payload version or checksum are wrong*} $e r function list - } {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}} + } {{library_name test engine LUA functions {{name test description {} flags {}}}}} test {FUNCTION - test function restore with wrong number of arguments} { catch {r function restore arg1 args2 arg3} e @@ -212,19 +208,19 @@ start_server {tags {"scripting"}} { } {*Unknown subcommand or wrong number of arguments for 'restore'. Try FUNCTION HELP.} test {FUNCTION - test fcall_ro with write command} { - r function load lua test REPLACE [get_no_writes_function_code test {return redis.call('set', 'x', '1')}] + r function load REPLACE [get_no_writes_function_code lua test test {return redis.call('set', 'x', '1')}] catch { r fcall_ro test 0 } e set _ $e } {*Write commands are not allowed from read-only scripts*} test {FUNCTION - test fcall_ro with read only commands} { - r function load lua test REPLACE [get_no_writes_function_code test {return redis.call('get', 'x')}] + r function load REPLACE [get_no_writes_function_code lua test test {return redis.call('get', 'x')}] r set x 1 r fcall_ro test 0 } {1} test {FUNCTION - test keys and argv} { - r function load lua test REPLACE [get_function_code test {return redis.call('set', KEYS[1], ARGV[1])}] + r function load REPLACE [get_function_code lua test test {return redis.call('set', KEYS[1], ARGV[1])}] r fcall test 1 x foo r get x } {foo} @@ -240,7 +236,7 @@ start_server {tags {"scripting"}} { test {FUNCTION - test function kill} { set rd [redis_deferring_client] r config set busy-reply-threshold 10 - r function load lua test REPLACE [get_function_code test {local a = 1 while true do a = a + 1 end}] + r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] $rd fcall test 0 after 200 catch {r ping} e @@ -254,7 +250,7 @@ start_server {tags {"scripting"}} { test {FUNCTION - test script kill not working on function} { set rd [redis_deferring_client] r config set busy-reply-threshold 10 - r function load lua test REPLACE [get_function_code test {local a = 1 while true do a = a + 1 end}] + r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] $rd fcall test 0 after 200 catch {r ping} e @@ -281,18 +277,18 @@ start_server {tags {"scripting"}} { } test {FUNCTION - test function flush} { - r function load lua test REPLACE [get_function_code test {local a = 1 while true do a = a + 1 end}] - assert_match {{library_name test engine LUA description {} functions {{name test description {} flags {}}}}} [r function list] + r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] + assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list] r function flush assert_match {} [r function list] - r function load lua test REPLACE [get_function_code test {local a = 1 while true do a = a + 1 end}] - assert_match {{library_name test engine LUA description {} functions {{name test description {} flags {}}}}} [r function list] + r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] + assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list] r function flush async assert_match {} [r function list] - r function load lua test REPLACE [get_function_code test {local a = 1 while true do a = a + 1 end}] - assert_match {{library_name test engine LUA description {} functions {{name test description {} flags {}}}}} [r function list] + r function load REPLACE [get_function_code lua test test {local a = 1 while true do a = a + 1 end}] + assert_match {{library_name test engine LUA functions {{name test description {} flags {}}}}} [r function list] r function flush sync assert_match {} [r function list] } @@ -319,9 +315,9 @@ start_server {tags {"scripting repl external:skip"}} { } test {FUNCTION - creation is replicated to replica} { - r function load LUA test DESCRIPTION {some description} [get_no_writes_function_code test {return 'hello'}] + r function load [get_no_writes_function_code LUA test test {return 'hello'}] wait_for_condition 150 100 { - [r -1 function list] eq {{library_name test engine LUA description {some description} functions {{name test description {} flags no-writes}}}} + [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags no-writes}}}} } else { fail "Failed waiting for function to replicate to replica" } @@ -344,7 +340,7 @@ start_server {tags {"scripting repl external:skip"}} { assert_equal [r function restore $e] {OK} wait_for_condition 150 100 { - [r -1 function list] eq {{library_name test engine LUA description {some description} functions {{name test description {} flags no-writes}}}} + [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags no-writes}}}} } else { fail "Failed waiting for function to replicate to replica" } @@ -360,9 +356,9 @@ start_server {tags {"scripting repl external:skip"}} { } test {FUNCTION - flush is replicated to replica} { - r function load LUA test DESCRIPTION {some description} [get_function_code test {return 'hello'}] + r function load [get_function_code LUA test test {return 'hello'}] wait_for_condition 150 100 { - [r -1 function list] eq {{library_name test engine LUA description {some description} functions {{name test description {} flags {}}}}} + [r -1 function list] eq {{library_name test engine LUA functions {{name test description {} flags {}}}}} } else { fail "Failed waiting for function to replicate to replica" } @@ -378,7 +374,7 @@ start_server {tags {"scripting repl external:skip"}} { r -1 slaveof no one # creating a function after disconnect to make sure function # is replicated on rdb phase - r function load LUA test DESCRIPTION {some description} [get_no_writes_function_code test {return 'hello'}] + r function load [get_no_writes_function_code LUA test test {return 'hello'}] # reconnect the replica r -1 slaveof [srv 0 host] [srv 0 port] @@ -396,11 +392,11 @@ start_server {tags {"scripting repl external:skip"}} { test "FUNCTION - test replication to replica on rdb phase info command" { r -1 function list - } {{library_name test engine LUA description {some description} functions {{name test description {} flags no-writes}}}} + } {{library_name test engine LUA functions {{name test description {} flags no-writes}}}} test "FUNCTION - create on read only replica" { catch { - r -1 function load LUA test DESCRIPTION {some description} [get_function_code test {return 'hello'}] + r -1 function load [get_function_code LUA test test {return 'hello'}] } e set _ $e } {*can't write against a read only replica*} @@ -413,7 +409,7 @@ start_server {tags {"scripting repl external:skip"}} { } {*can't write against a read only replica*} test "FUNCTION - function effect is replicated to replica" { - r function load LUA test REPLACE [get_function_code test {return redis.call('set', 'x', '1')}] + r function load REPLACE [get_function_code LUA test test {return redis.call('set', 'x', '1')}] r fcall test 0 assert {[r get x] eq {1}} wait_for_condition 150 100 { @@ -436,12 +432,12 @@ test {FUNCTION can processes create, delete and flush commands in AOF when doing start_server {} { r config set appendonly yes waitForBgrewriteaof r - r FUNCTION LOAD lua test "redis.register_function('test', function() return 'hello' end)" + r FUNCTION LOAD "#!lua name=test\nredis.register_function('test', function() return 'hello' end)" r config set slave-read-only yes r slaveof 127.0.0.1 0 r debug loadaof r slaveof no one - assert_equal [r function list] {{library_name test engine LUA description {} functions {{name test description {} flags {}}}}} + assert_equal [r function list] {{library_name test engine LUA functions {{name test description {} flags {}}}}} r FUNCTION DELETE test @@ -450,7 +446,7 @@ test {FUNCTION can processes create, delete and flush commands in AOF when doing r slaveof no one assert_equal [r function list] {} - r FUNCTION LOAD lua test "redis.register_function('test', function() return 'hello' end)" + r FUNCTION LOAD "#!lua name=test\nredis.register_function('test', function() return 'hello' end)" r FUNCTION FLUSH r slaveof 127.0.0.1 0 @@ -462,7 +458,7 @@ test {FUNCTION can processes create, delete and flush commands in AOF when doing start_server {tags {"scripting"}} { test {LIBRARIES - test shared function can access default globals} { - r function load LUA lib1 { + r function load {#!lua name=lib1 local function ping() return redis.call('ping') end @@ -477,7 +473,7 @@ start_server {tags {"scripting"}} { } {PONG} test {LIBRARIES - usage and code sharing} { - r function load LUA lib1 REPLACE { + r function load REPLACE {#!lua name=lib1 local function add1(a) return a + 1 end @@ -497,11 +493,11 @@ start_server {tags {"scripting"}} { assert_equal [r fcall f1 0] {2} assert_equal [r fcall f2 0] {3} r function list - } {{library_name lib1 engine LUA description {} functions {*}}} + } {{library_name lib1 engine LUA functions {*}}} test {LIBRARIES - test registration failure revert the entire load} { catch { - r function load LUA lib1 replace { + r function load replace {#!lua name=lib1 local function add1(a) return a + 2 end @@ -524,7 +520,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - test registration function name collision} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 redis.register_function( 'f1', function(keys, args) @@ -540,7 +536,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - test registration function name collision on same library} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 redis.register_function( 'f1', function(keys, args) @@ -560,7 +556,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - test registration with no argument} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 redis.register_function() } } e @@ -569,7 +565,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - test registration with only name} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 redis.register_function('f1') } } e @@ -578,7 +574,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - test registration with to many arguments} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 redis.register_function('f1', function() return 1 end, {}, 'description', 'extra arg') } } e @@ -587,7 +583,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - test registration with no string name} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 redis.register_function(nil, function() return 1 end) } } e @@ -596,7 +592,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - test registration with wrong name format} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 redis.register_function('test\0test', function() return 1 end) } } e @@ -605,7 +601,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - test registration with empty name} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 redis.register_function('', function() return 1 end) } } e @@ -614,7 +610,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - math.random from function load} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 return math.random() } } e @@ -623,7 +619,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - redis.call from function load} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 return redis.call('ping') } } e @@ -632,7 +628,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - redis.call from function load} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 return redis.setresp(3) } } e @@ -641,7 +637,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - redis.set_repl from function load} { catch { - r function load LUA lib2 replace { + r function load replace {#!lua name=lib2 return redis.set_repl(redis.REPL_NONE) } } e @@ -657,7 +653,7 @@ start_server {tags {"scripting"}} { # have another level of protection on the C # code itself and we want to test it and verify # that it works properly. - r function load LUA lib1 replace { + r function load replace {#!lua name=lib1 local lib = redis lib.register_function('f1', function () lib.redis = redis @@ -675,22 +671,34 @@ start_server {tags {"scripting"}} { } assert_equal {OK} [r fcall f1 0] - catch {[r function load LUA lib2 {redis.math.random()}]} e + catch {[r function load {#!lua name=lib2 + redis.math.random() + }]} e assert_match {*can only be called inside a script invocation*} $e - catch {[r function load LUA lib2 {redis.math.randomseed()}]} e + catch {[r function load {#!lua name=lib2 + redis.math.randomseed() + }]} e assert_match {*can only be called inside a script invocation*} $e - catch {[r function load LUA lib2 {redis.redis.call('ping')}]} e + catch {[r function load {#!lua name=lib2 + redis.redis.call('ping') + }]} e assert_match {*can only be called inside a script invocation*} $e - catch {[r function load LUA lib2 {redis.redis.pcall('ping')}]} e + catch {[r function load {#!lua name=lib2 + redis.redis.pcall('ping') + }]} e assert_match {*can only be called inside a script invocation*} $e - catch {[r function load LUA lib2 {redis.redis.setresp(3)}]} e + catch {[r function load {#!lua name=lib2 + redis.redis.setresp(3) + }]} e assert_match {*can only be called inside a script invocation*} $e - catch {[r function load LUA lib2 {redis.redis.set_repl(redis.redis.REPL_NONE)}]} e + catch {[r function load {#!lua name=lib2 + redis.redis.set_repl(redis.redis.REPL_NONE) + }]} e assert_match {*can only be called inside a script invocation*} $e catch {[r fcall f2 0]} e @@ -703,7 +711,7 @@ start_server {tags {"scripting"}} { } {} test {LIBRARIES - register function inside a function} { - r function load LUA lib { + r function load {#!lua name=lib redis.register_function( 'f1', function(keys, args) @@ -724,7 +732,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - register library with no functions} { r function flush catch { - r function load LUA lib { + r function load {#!lua name=lib return 1 } } e @@ -733,7 +741,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - load timeout} { catch { - r function load LUA lib { + r function load {#!lua name=lib local a = 1 while 1 do a = a + 1 end } @@ -743,7 +751,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - verify global protection on the load run} { catch { - r function load LUA lib { + r function load {#!lua name=lib a = 1 } } e @@ -751,7 +759,7 @@ start_server {tags {"scripting"}} { } {*attempted to create global variable 'a'*} test {LIBRARIES - named arguments} { - r function load LUA lib { + r function load {#!lua name=lib redis.register_function{ function_name='f1', callback=function() @@ -761,11 +769,11 @@ start_server {tags {"scripting"}} { } } r function list - } {{library_name lib engine LUA description {} functions {{name f1 description {some desc} flags {}}}}} + } {{library_name lib engine LUA functions {{name f1 description {some desc} flags {}}}}} test {LIBRARIES - named arguments, bad function name} { catch { - r function load LUA lib replace { + r function load replace {#!lua name=lib redis.register_function{ function_name=function() return 1 end, callback=function() @@ -780,7 +788,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - named arguments, bad callback type} { catch { - r function load LUA lib replace { + r function load replace {#!lua name=lib redis.register_function{ function_name='f1', callback='bad', @@ -793,7 +801,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - named arguments, bad description} { catch { - r function load LUA lib replace { + r function load replace {#!lua name=lib redis.register_function{ function_name='f1', callback=function() @@ -808,7 +816,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - named arguments, unknown argument} { catch { - r function load LUA lib replace { + r function load replace {#!lua name=lib redis.register_function{ function_name='f1', callback=function() @@ -824,7 +832,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - named arguments, missing function name} { catch { - r function load LUA lib replace { + r function load replace {#!lua name=lib redis.register_function{ callback=function() return 'hello' @@ -838,7 +846,7 @@ start_server {tags {"scripting"}} { test {LIBRARIES - named arguments, missing callback} { catch { - r function load LUA lib replace { + r function load replace {#!lua name=lib redis.register_function{ function_name='f1', description='desc' @@ -850,7 +858,7 @@ start_server {tags {"scripting"}} { test {FUNCTION - test function restore with function name collision} { r function flush - r function load lua lib1 { + r function load {#!lua name=lib1 local function add1(a) return a + 1 end @@ -877,7 +885,7 @@ start_server {tags {"scripting"}} { r function flush # load a library with different name but with the same function name - r function load lua lib1 { + r function load {#!lua name=lib1 redis.register_function( 'f6', function(keys, args) @@ -885,7 +893,7 @@ start_server {tags {"scripting"}} { end ) } - r function load lua lib2 { + r function load {#!lua name=lib2 local function add1(a) return a + 1 end @@ -926,14 +934,18 @@ start_server {tags {"scripting"}} { test {FUNCTION - test function list with code} { r function flush - r function load lua library1 {redis.register_function('f6', function(keys, args) return 7 end)} + r function load {#!lua name=library1 + redis.register_function('f6', function(keys, args) return 7 end) + } r function list withcode - } {{library_name library1 engine LUA description {} functions {{name f6 description {} flags {}}} library_code {redis.register_function('f6', function(keys, args) return 7 end)}}} + } {{library_name library1 engine LUA functions {{name f6 description {} flags {}}} library_code {*redis.register_function('f6', function(keys, args) return 7 end)*}}} test {FUNCTION - test function list with pattern} { - r function load lua lib1 {redis.register_function('f7', function(keys, args) return 7 end)} + r function load {#!lua name=lib1 + redis.register_function('f7', function(keys, args) return 7 end) + } r function list libraryname library* - } {{library_name library1 engine LUA description {} functions {{name f6 description {} flags {}}}}} + } {{library_name library1 engine LUA functions {{name f6 description {} flags {}}}}} test {FUNCTION - test function list wrong argument} { catch {r function list bad_argument} e @@ -957,12 +969,16 @@ start_server {tags {"scripting"}} { test {FUNCTION - verify OOM on function load and function restore} { r function flush - r function load lua test replace {redis.register_function('f1', function() return 1 end)} + r function load replace {#!lua name=test + redis.register_function('f1', function() return 1 end) + } set payload [r function dump] r config set maxmemory 1 r function flush - catch {r function load lua test replace {redis.register_function('f1', function() return 1 end)}} e + catch {r function load replace {#!lua name=test + redis.register_function('f1', function() return 1 end) + }} e assert_match {*command not allowed when used memory*} $e r function flush @@ -973,11 +989,13 @@ start_server {tags {"scripting"}} { } test {FUNCTION - verify allow-omm allows running any command} { - r FUNCTION load lua f1 replace { redis.register_function{ - function_name='f1', - callback=function() return redis.call('set', 'x', '1') end, - flags={'allow-oom'} - }} + r FUNCTION load replace {#!lua name=f1 + redis.register_function{ + function_name='f1', + callback=function() return redis.call('set', 'x', '1') end, + flags={'allow-oom'} + } + } r config set maxmemory 1 @@ -990,53 +1008,65 @@ start_server {tags {"scripting"}} { start_server {tags {"scripting"}} { test {FUNCTION - wrong flags type named arguments} { - catch {r function load lua test replace {redis.register_function{ - function_name = 'f1', - callback = function() return 1 end, - flags = 'bad flags type' - }}} e + catch {r function load replace {#!lua name=test + redis.register_function{ + function_name = 'f1', + callback = function() return 1 end, + flags = 'bad flags type' + } + }} e set _ $e } {*flags argument to redis.register_function must be a table representing function flags*} test {FUNCTION - wrong flag type} { - catch {r function load lua test replace {redis.register_function{ - function_name = 'f1', - callback = function() return 1 end, - flags = {function() return 1 end} - }}} e + catch {r function load replace {#!lua name=test + redis.register_function{ + function_name = 'f1', + callback = function() return 1 end, + flags = {function() return 1 end} + } + }} e set _ $e } {*unknown flag given*} test {FUNCTION - unknown flag} { - catch {r function load lua test replace {redis.register_function{ - function_name = 'f1', - callback = function() return 1 end, - flags = {'unknown'} - }}} e + catch {r function load replace {#!lua name=test + redis.register_function{ + function_name = 'f1', + callback = function() return 1 end, + flags = {'unknown'} + } + }} e set _ $e } {*unknown flag given*} test {FUNCTION - write script on fcall_ro} { - r function load lua test replace {redis.register_function{ - function_name = 'f1', - callback = function() return redis.call('set', 'x', 1) end - }} + r function load replace {#!lua name=test + redis.register_function{ + function_name = 'f1', + callback = function() return redis.call('set', 'x', 1) end + } + } catch {r fcall_ro f1 0} e set _ $e } {*Can not execute a script with write flag using \*_ro command*} test {FUNCTION - write script with no-writes flag} { - r function load lua test replace {redis.register_function{ - function_name = 'f1', - callback = function() return redis.call('set', 'x', 1) end, - flags = {'no-writes'} - }} + r function load replace {#!lua name=test + redis.register_function{ + function_name = 'f1', + callback = function() return redis.call('set', 'x', 1) end, + flags = {'no-writes'} + } + } catch {r fcall f1 0} e set _ $e } {*Write commands are not allowed from read-only scripts*} test {FUNCTION - deny oom} { - r FUNCTION load lua test replace { redis.register_function('f1', function() return redis.call('set', 'x', '1') end) } + r FUNCTION load replace {#!lua name=test + redis.register_function('f1', function() return redis.call('set', 'x', '1') end) + } r config set maxmemory 1 @@ -1047,7 +1077,9 @@ start_server {tags {"scripting"}} { } test {FUNCTION - deny oom on no-writes function} { - r FUNCTION load lua test replace {redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-writes'}}} + r FUNCTION load replace {#!lua name=test + redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-writes'}} + } r config set maxmemory 1 @@ -1061,7 +1093,7 @@ start_server {tags {"scripting"}} { } test {FUNCTION - allow stale} { - r FUNCTION load lua test replace { + r FUNCTION load replace {#!lua name=test redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-writes'}} redis.register_function{function_name='f2', callback=function() return 'hello' end, flags={'allow-stale', 'no-writes'}} redis.register_function{function_name='f3', callback=function() return redis.call('get', 'x') end, flags={'allow-stale', 'no-writes'}} @@ -1087,7 +1119,7 @@ start_server {tags {"scripting"}} { } {} {external:skip} test {FUNCTION - redis version api} { - r FUNCTION load lua test replace { + r FUNCTION load replace {#!lua name=test local version = redis.REDIS_VERSION_NUM redis.register_function{function_name='get_version_v1', callback=function() @@ -1106,12 +1138,12 @@ start_server {tags {"scripting"}} { test {FUNCTION - function stats} { r FUNCTION FLUSH - r FUNCTION load lua test1 { + r FUNCTION load {#!lua name=test1 redis.register_function('f1', function() return 1 end) redis.register_function('f2', function() return 1 end) } - r FUNCTION load lua test2 { + r FUNCTION load {#!lua name=test2 redis.register_function('f3', function() return 1 end) } @@ -1132,4 +1164,38 @@ start_server {tags {"scripting"}} { r function flush r function stats } {running_script {} engines {LUA {libraries_count 0 functions_count 0}}} + + test {FUNCTION - function test empty engine} { + catch {r function load replace {#! name=test + redis.register_function('foo', function() return 1 end) + }} e + set _ $e + } {ERR Engine '' not found} + + test {FUNCTION - function test unknown metadata value} { + catch {r function load replace {#!lua name=test foo=bar + redis.register_function('foo', function() return 1 end) + }} e + set _ $e + } {ERR Invalid metadata value given: foo=bar} + + test {FUNCTION - function test no name} { + catch {r function load replace {#!lua + redis.register_function('foo', function() return 1 end) + }} e + set _ $e + } {ERR Library name was not given} + + test {FUNCTION - function test multiple names} { + catch {r function load replace {#!lua name=foo name=bar + redis.register_function('foo', function() return 1 end) + }} e + set _ $e + } {ERR Invalid metadata value, name argument was given multiple times} + + test {FUNCTION - function test name with quotes} { + r function load replace {#!lua name="foo" + redis.register_function('foo', function() return 1 end) + } + } {foo} } diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl index 48e88dfc0..d9729b7bd 100644 --- a/tests/unit/scripting.tcl +++ b/tests/unit/scripting.tcl @@ -15,17 +15,25 @@ if {$is_eval == 1} { } } else { proc run_script {args} { - r function load LUA test replace [format "redis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0]] + r function load replace [format "#!lua name=test\nredis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 0]] + if {[r readingraw] eq 1} { + # read name + assert_equal {test} [r read] + } r fcall test {*}[lrange $args 1 end] } proc run_script_ro {args} { - r function load LUA test replace [format "redis.register_function{function_name='test', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0]] + r function load replace [format "#!lua name=test\nredis.register_function{function_name='test', callback=function(KEYS, ARGV)\n %s \nend, flags={'no-writes'}}" [lindex $args 0]] + if {[r readingraw] eq 1} { + # read name + assert_equal {test} [r read] + } r fcall_ro test {*}[lrange $args 1 end] } proc run_script_on_connection {args} { set rd [lindex $args 0] - $rd function load LUA test replace [format "redis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 1]] - # read the ok reply of function create + $rd function load replace [format "#!lua name=test\nredis.register_function('test', function(KEYS, ARGV)\n %s \nend)" [lindex $args 1]] + # read name $rd read $rd fcall test {*}[lrange $args 2 end] } @@ -784,7 +792,7 @@ start_server {tags {"scripting"}} { set buf "*3\r\n\$4\r\neval\r\n\$33\r\nwhile 1 do redis.call('ping') end\r\n\$1\r\n0\r\n" append buf "*1\r\n\$4\r\nping\r\n" } else { - set buf "*6\r\n\$8\r\nfunction\r\n\$4\r\nload\r\n\$3\r\nlua\r\n\$4\r\ntest\r\n\$7\r\nreplace\r\n\$81\r\nredis.register_function('test', function() while 1 do redis.call('ping') end end)\r\n" + set buf "*4\r\n\$8\r\nfunction\r\n\$4\r\nload\r\n\$7\r\nreplace\r\n\$97\r\n#!lua name=test\nredis.register_function('test', function() while 1 do redis.call('ping') end end)\r\n" append buf "*3\r\n\$5\r\nfcall\r\n\$4\r\ntest\r\n\$1\r\n0\r\n" append buf "*1\r\n\$4\r\nping\r\n" } @@ -808,8 +816,8 @@ start_server {tags {"scripting"}} { assert_equal [r ping] "PONG" if {$is_eval == 0} { - # read the ok reply of function create - assert_match {OK} [$rd read] + # read the function name + assert_match {test} [$rd read] } catch {$rd read} res