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).
This commit is contained in:
Meir Shpilraien (Spielrein) 2022-04-05 10:27:24 +03:00 committed by GitHub
parent 2db0d898f8
commit ae020e3d56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 428 additions and 299 deletions

View File

@ -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);

View File

@ -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}

View File

@ -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"

View File

@ -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 <ENGINE NAME> <LIBRARY NAME>
* [REPLACE] [DESC <LIBRARY DESCRIPTION>] <LIBRARY CODE>
*
* ENGINE NAME - name of the engine to use the run the library
* LIBRARY NAME - name of the library
* FUNCTION LOAD [REPLACE] <LIBRARY CODE>
* 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 */

View File

@ -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();

108
src/rdb.c
View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -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'}}
}

View File

@ -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} {

View File

@ -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)
}

View File

@ -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

View File

@ -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 {

View File

@ -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}
}

View File

@ -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