Unverified Commit ae020e3d authored by Meir Shpilraien (Spielrein)'s avatar Meir Shpilraien (Spielrein) Committed by GitHub
Browse files

Functions: Move library meta data to be part of the library payload. (#10500)

## Move library meta data to be part of the library payload.

Following the discussion on https://github.com/redis/redis/issues/10429 and the intention to add (in the future) library versioning support, we believe that the entire library metadata (like name and engine) should be part of the library payload and not provided by the `FUNCTION LOAD` command. The reasoning behind this is that the programmer who developed the library should be the one who set those values (name, engine, and in the future also version). **It is not the responsibility of the admin who load the library into the database.**

The PR moves all the library metadata (engine and function name) to be part of the library payload. The metadata needs to be provided on the first line of the payload using the shebang format (`#!<engine> name=<name>`), example:

```lua
#!lua name=test
redis.register_function('foo', function() return 1 end)
```

The above script will run on the Lua engine and will create a library called `test`.

## API Changes (compare to 7.0 rc2)

* `FUNCTION LOAD` command was change and now it simply gets the library payload and extract the engine and name from the payload. In addition, the command will now return the function name which can later be used on `FUNCTION DELETE` and `FUNCTION LIST`.
* The description field was completely removed from`FUNCTION LOAD`, and `FUNCTION LIST`


## Breaking Changes (compare to 7.0 rc2)

* Library description was removed (we can re-add it in the future either as part of the shebang line or an additional line).
* Loading an AOF file that was generated by either 7.0 rc1 or 7.0 rc2 will fail because the old command syntax is invalid.

## Notes

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