diff --git a/src/module.c b/src/module.c index d68991eea9..34c38e6907 100644 --- a/src/module.c +++ b/src/module.c @@ -123,6 +123,7 @@ struct AutoMemEntry { #define VALKEYMODULE_AM_FREED 3 /* Explicitly freed by user already. */ #define VALKEYMODULE_AM_DICT 4 #define VALKEYMODULE_AM_INFO 5 +#define VALKEYMODULE_AM_LIST 6 /* The pool allocator block. Modules can allocate memory via this special * allocator that will automatically release it all once the callback returns. @@ -368,6 +369,16 @@ typedef struct ValkeyModuleDictIter { raxIterator ri; } ValkeyModuleDictIter; +/* Data structures related to the list data structure. */ +typedef struct ValkeyModuleList { + list *list; +} ValkeyModuleList; + +typedef struct ValkeyModuleListIter { + ValkeyModuleList *list; + listIter *li; +} ValkeyModuleListIter; + typedef struct ValkeyModuleCommandFilterCtx { ValkeyModuleString **argv; int argv_len; @@ -515,6 +526,7 @@ void VM_ZsetRangeStop(ValkeyModuleKey *kp); static void zsetKeyReset(ValkeyModuleKey *key); static void moduleInitKeyTypeSpecific(ValkeyModuleKey *key); void VM_FreeDict(ValkeyModuleCtx *ctx, ValkeyModuleDict *d); +void VM_ListFree(ValkeyModuleCtx *ctx, ValkeyModuleList *l); void VM_FreeServerInfo(ValkeyModuleCtx *ctx, ValkeyModuleServerInfoData *data); /* Helpers for VM_SetCommandInfo. */ @@ -2696,6 +2708,7 @@ void autoMemoryCollect(ValkeyModuleCtx *ctx) { case VALKEYMODULE_AM_KEY: VM_CloseKey(ptr); break; case VALKEYMODULE_AM_DICT: VM_FreeDict(NULL, ptr); break; case VALKEYMODULE_AM_INFO: VM_FreeServerInfo(NULL, ptr); break; + case VALKEYMODULE_AM_LIST: VM_ListFree(NULL, ptr); break; } } ctx->flags |= VALKEYMODULE_CTX_AUTO_MEMORY; @@ -10492,6 +10505,50 @@ int VM_DictCompare(ValkeyModuleDictIter *di, const char *op, ValkeyModuleString return res ? VALKEYMODULE_OK : VALKEYMODULE_ERR; } +/* -------------------------------------------------------------------------- + * ## Modules List API + * + * Exports the engine singly linked list API implementation to modules, + * together with an iterator. + * -------------------------------------------------------------------------- */ + +ValkeyModuleList *VM_ListCreate(ValkeyModuleCtx *ctx) { + ValkeyModuleList *list = zmalloc(sizeof(*list)); + list->list = listCreate(); + if (ctx != NULL) autoMemoryAdd(ctx, VALKEYMODULE_AM_LIST, list); + return list; +} + +void VM_ListFree(ValkeyModuleCtx *ctx, ValkeyModuleList *list) { + if (ctx != NULL) autoMemoryFreed(ctx, VALKEYMODULE_AM_LIST, list); + listRelease(list->list); + zfree(list); +} + +size_t VM_ListLength(ValkeyModuleList *list) { + return listLength(list->list); +} + +void VM_ListAddToTail(ValkeyModuleList *list, void *val) { + listAddNodeTail(list->list, val); +} + +ValkeyModuleListIter *VM_ListGetIter(ValkeyModuleList *list, int dir) { + ValkeyModuleListIter *iter = zmalloc(sizeof(*iter)); + iter->list = list; + iter->li = listGetIterator(list->list, dir); + return iter; +} + +void *VM_ListIterNext(ValkeyModuleListIter *iter) { + listNode *node = listNext(iter->li); + return node != NULL ? listNodeValue(node) : NULL; +} + +void VM_ListReleaseIter(ValkeyModuleListIter *iter) { + listReleaseIterator(iter->li); + zfree(iter); +} /* -------------------------------------------------------------------------- * ## Modules Info fields @@ -14257,6 +14314,13 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(DictPrev); REGISTER_API(DictCompareC); REGISTER_API(DictCompare); + REGISTER_API(ListCreate); + REGISTER_API(ListFree); + REGISTER_API(ListLength); + REGISTER_API(ListAddToTail); + REGISTER_API(ListGetIter); + REGISTER_API(ListIterNext); + REGISTER_API(ListReleaseIter); REGISTER_API(ExportSharedAPI); REGISTER_API(GetSharedAPI); REGISTER_API(RegisterCommandFilter); diff --git a/src/valkeymodule.h b/src/valkeymodule.h index acbbbb14ed..17651a8057 100644 --- a/src/valkeymodule.h +++ b/src/valkeymodule.h @@ -1128,6 +1128,8 @@ typedef struct ValkeyModuleBlockedClient ValkeyModuleBlockedClient; typedef struct ValkeyModuleClusterInfo ValkeyModuleClusterInfo; typedef struct ValkeyModuleDict ValkeyModuleDict; typedef struct ValkeyModuleDictIter ValkeyModuleDictIter; +typedef struct ValkeyModuleList ValkeyModuleList; +typedef struct ValkeyModuleListIter ValkeyModuleListIter; typedef struct ValkeyModuleCommandFilterCtx ValkeyModuleCommandFilterCtx; typedef struct ValkeyModuleCommandFilter ValkeyModuleCommandFilter; typedef struct ValkeyModuleServerInfoData ValkeyModuleServerInfoData; @@ -1215,6 +1217,11 @@ typedef struct ValkeyModuleTypeMethods { ValkeyModuleTypeAuxSaveFunc aux_save2; } ValkeyModuleTypeMethods; +typedef enum ValkeyModuleListIterDirection { + START_HEAD = 0, + START_TAIL = 1, +} ValkeyModuleListIterDirection; + #define VALKEYMODULE_GET_API(name) ValkeyModule_GetApi("ValkeyModule_" #name, ((void **)&ValkeyModule_##name)) /* Default API declaration prefix (not 'extern' for backwards compatibility) */ @@ -1623,6 +1630,13 @@ VALKEYMODULE_API int (*ValkeyModule_DictCompareC)(ValkeyModuleDictIter *di, cons VALKEYMODULE_API int (*ValkeyModule_DictCompare)(ValkeyModuleDictIter *di, const char *op, ValkeyModuleString *key) VALKEYMODULE_ATTR; +VALKEYMODULE_API ValkeyModuleList *(*ValkeyModule_ListCreate)(ValkeyModuleCtx *ctx)VALKEYMODULE_ATTR; +VALKEYMODULE_API void (*ValkeyModule_ListFree)(ValkeyModuleCtx *ctx, ValkeyModuleList *l) VALKEYMODULE_ATTR; +VALKEYMODULE_API size_t (*ValkeyModule_ListLength)(ValkeyModuleList *list) VALKEYMODULE_ATTR; +VALKEYMODULE_API void (*ValkeyModule_ListAddToTail)(ValkeyModuleList *list, void *val) VALKEYMODULE_ATTR; +VALKEYMODULE_API ValkeyModuleListIter *(*ValkeyModule_ListGetIter)(ValkeyModuleList *list, ValkeyModuleListIterDirection dir)VALKEYMODULE_ATTR; +VALKEYMODULE_API void *(*ValkeyModule_ListIterNext)(ValkeyModuleListIter *iter)VALKEYMODULE_ATTR; +VALKEYMODULE_API void (*ValkeyModule_ListReleaseIter)(ValkeyModuleListIter *iter) VALKEYMODULE_ATTR; VALKEYMODULE_API int (*ValkeyModule_RegisterInfoFunc)(ValkeyModuleCtx *ctx, ValkeyModuleInfoFunc cb) VALKEYMODULE_ATTR; VALKEYMODULE_API void (*ValkeyModule_RegisterAuthCallback)(ValkeyModuleCtx *ctx, ValkeyModuleAuthCallback cb) VALKEYMODULE_ATTR; @@ -2180,6 +2194,13 @@ static int ValkeyModule_Init(ValkeyModuleCtx *ctx, const char *name, int ver, in VALKEYMODULE_GET_API(DictPrev); VALKEYMODULE_GET_API(DictCompare); VALKEYMODULE_GET_API(DictCompareC); + VALKEYMODULE_GET_API(ListCreate); + VALKEYMODULE_GET_API(ListAddToTail); + VALKEYMODULE_GET_API(ListFree); + VALKEYMODULE_GET_API(ListLength); + VALKEYMODULE_GET_API(ListGetIter); + VALKEYMODULE_GET_API(ListIterNext); + VALKEYMODULE_GET_API(ListReleaseIter); VALKEYMODULE_GET_API(RegisterInfoFunc); VALKEYMODULE_GET_API(RegisterAuthCallback); VALKEYMODULE_GET_API(InfoAddSection); diff --git a/tests/modules/Makefile b/tests/modules/Makefile index 72031c5ad3..210a8ffc0e 100644 --- a/tests/modules/Makefile +++ b/tests/modules/Makefile @@ -68,7 +68,8 @@ TEST_MODULES = \ crash.so \ cluster.so \ helloscripting.so \ - unsupported_features.so + unsupported_features.so \ + test_module_listapi.so .PHONY: all diff --git a/tests/modules/test_module_listapi.c b/tests/modules/test_module_listapi.c new file mode 100644 index 0000000000..032651d262 --- /dev/null +++ b/tests/modules/test_module_listapi.c @@ -0,0 +1,120 @@ +/* ListAPI -- An example of module list API + * + * This module implements a volatile queue on top of the list exported by the + * modules API. + * + * ----------------------------------------------------------------------------- + */ + +#include "valkeymodule.h" +#include +#include +#include + +static ValkeyModuleList *Queue; + +/* LISTAPI.ADD + * + * Adds a value onto the queue. */ +int cmd_ADD(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) { + if (argc != 2) return ValkeyModule_WrongArity(ctx); + ValkeyModule_ListAddToTail(Queue, argv[1]); + /* We need to keep a reference to the value stored at the key, otherwise + * it would be freed when this callback returns. */ + ValkeyModule_RetainString(NULL, argv[1]); + return ValkeyModule_ReplyWithSimpleString(ctx, "OK"); +} + +/* LISTAPI.SIZE + * + * Return the queue length. */ +int cmd_SIZE(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) { + VALKEYMODULE_NOT_USED(argv); + if (argc != 1) return ValkeyModule_WrongArity(ctx); + long long size = ValkeyModule_ListLength(Queue); + return ValkeyModule_ReplyWithLongLong(ctx, size); +} + +/* LISTAPI.RANGE + * + * Return a list of values in the queue in order. No more than 'count' items + * are emitted. */ +int cmd_RANGE(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) { + if (argc != 2) return ValkeyModule_WrongArity(ctx); + + long long count; + if (ValkeyModule_StringToLongLong(argv[1], &count) != VALKEYMODULE_OK) { + return ValkeyModule_ReplyWithError(ctx, "ERR invalid count"); + } + + ValkeyModuleListIter *iter = ValkeyModule_ListGetIter(Queue, VALKEYMODULE_LIST_HEAD); + + ValkeyModuleString *value; + long long replylen = 0; + ValkeyModule_ReplyWithArray(ctx, VALKEYMODULE_POSTPONED_LEN); + while ((value = ValkeyModule_ListIterNext(iter)) != NULL) { + if (replylen >= count) break; + size_t val_str_len; + const char *val_str = ValkeyModule_StringPtrLen(value, &val_str_len); + ValkeyModule_ReplyWithStringBuffer(ctx, val_str, val_str_len); + replylen++; + } + ValkeyModule_ReplySetArrayLength(ctx, replylen); + + ValkeyModule_ListReleaseIter(iter); + return VALKEYMODULE_OK; +} + +static void delete_queue(ValkeyModuleCtx *ctx) { + ValkeyModuleString *value; + ValkeyModuleListIter *iter = ValkeyModule_ListGetIter(Queue, VALKEYMODULE_LIST_HEAD); + while ((value = ValkeyModule_ListIterNext(iter)) != NULL) { + ValkeyModule_FreeString(NULL, value); + } + ValkeyModule_ListReleaseIter(iter); + + ValkeyModule_ListFree(ctx, Queue); +} + +/* LISTAPI.RESET + * + * Resets the queue. Removes all values from the queue. */ +int cmd_RESET(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) { + VALKEYMODULE_NOT_USED(argv); + if (argc != 1) return ValkeyModule_WrongArity(ctx); + + delete_queue(ctx); + + Queue = ValkeyModule_ListCreate(NULL); + + return ValkeyModule_ReplyWithSimpleString(ctx, "OK"); +} + +int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) { + VALKEYMODULE_NOT_USED(argv); + VALKEYMODULE_NOT_USED(argc); + + if (ValkeyModule_Init(ctx, "listapi", 1, VALKEYMODULE_APIVER_1) == VALKEYMODULE_ERR) return VALKEYMODULE_ERR; + + if (ValkeyModule_CreateCommand(ctx, "listapi.add", cmd_ADD, "write deny-oom", 1, 1, 1) == VALKEYMODULE_ERR) + return VALKEYMODULE_ERR; + + if (ValkeyModule_CreateCommand(ctx, "listapi.size", cmd_SIZE, "readonly", 1, 1, 1) == VALKEYMODULE_ERR) + return VALKEYMODULE_ERR; + + if (ValkeyModule_CreateCommand(ctx, "listapi.range", cmd_RANGE, "readonly", 1, 1, 1) == VALKEYMODULE_ERR) + return VALKEYMODULE_ERR; + + if (ValkeyModule_CreateCommand(ctx, "listapi.reset", cmd_RESET, "write deny-oom", 1, 1, 1) == VALKEYMODULE_ERR) + return VALKEYMODULE_ERR; + + /* Create our global list. Here we'll set our keys and values. */ + Queue = ValkeyModule_ListCreate(NULL); + + return VALKEYMODULE_OK; +} + +int ValkeyModule_OnUnload(ValkeyModuleCtx *ctx) { + delete_queue(ctx); + return VALKEYMODULE_OK; +} diff --git a/tests/unit/moduleapi/listapi.tcl b/tests/unit/moduleapi/listapi.tcl new file mode 100644 index 0000000000..111bf3cb03 --- /dev/null +++ b/tests/unit/moduleapi/listapi.tcl @@ -0,0 +1,24 @@ +set testmodule [file normalize tests/modules/test_module_listapi.so] + +start_server {tags {"modules"}} { + r module load $testmodule + + test {Module queue add, size, range} { + r listapi.add 1 + r listapi.add 2 + r listapi.add 3 + r listapi.add 4 + r listapi.add 5 + + assert_equal 5 [r listapi.size] + assert_equal {1 2 3} [r listapi.range 3] + assert_equal {1 2 3 4 5} [r listapi.range 7] + + assert_equal {OK} [r listapi.reset] + assert_equal 0 [r listapi.size] + } + + test "Unload the module - list" { + assert_equal {OK} [r module unload listapi] + } +}