Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions src/valkeymodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion tests/modules/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ TEST_MODULES = \
crash.so \
cluster.so \
helloscripting.so \
unsupported_features.so
unsupported_features.so \
test_module_listapi.so

.PHONY: all

Expand Down
120 changes: 120 additions & 0 deletions tests/modules/test_module_listapi.c
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>

static ValkeyModuleList *Queue;

/* LISTAPI.ADD <value>
*
* 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 <count>
*
* 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;
}
24 changes: 24 additions & 0 deletions tests/unit/moduleapi/listapi.tcl
Original file line number Diff line number Diff line change
@@ -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]
}
}
Loading