mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 16:18:28 -05:00
03d144a452
Replaces Redis => Redict API References: https://codeberg.org/redict/redict/issues/21 Signed-off-by: Drew DeVault <sir@cmpwn.com>
412 lines
14 KiB
C
412 lines
14 KiB
C
// SPDX-FileCopyrightText: 2024 Redict Contributors
|
|
// SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo <antirez at gmail dot com>
|
|
//
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include "redictmodule.h"
|
|
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
/* Module configuration, save aux or not? */
|
|
#define CONF_AUX_OPTION_NO_AUX 0
|
|
#define CONF_AUX_OPTION_SAVE2 1 << 0
|
|
#define CONF_AUX_OPTION_BEFORE_KEYSPACE 1 << 1
|
|
#define CONF_AUX_OPTION_AFTER_KEYSPACE 1 << 2
|
|
#define CONF_AUX_OPTION_NO_DATA 1 << 3
|
|
long long conf_aux_count = 0;
|
|
|
|
/* Registered type */
|
|
RedictModuleType *testrdb_type = NULL;
|
|
|
|
/* Global values to store and persist to aux */
|
|
RedictModuleString *before_str = NULL;
|
|
RedictModuleString *after_str = NULL;
|
|
|
|
/* Global values used to keep aux from db being loaded (in case of async_loading) */
|
|
RedictModuleString *before_str_temp = NULL;
|
|
RedictModuleString *after_str_temp = NULL;
|
|
|
|
/* Indicates whether there is an async replication in progress.
|
|
* We control this value from RedictModuleEvent_ReplAsyncLoad events. */
|
|
int async_loading = 0;
|
|
|
|
int n_aux_load_called = 0;
|
|
|
|
void replAsyncLoadCallback(RedictModuleCtx *ctx, RedictModuleEvent e, uint64_t sub, void *data)
|
|
{
|
|
REDICTMODULE_NOT_USED(e);
|
|
REDICTMODULE_NOT_USED(data);
|
|
|
|
switch (sub) {
|
|
case REDICTMODULE_SUBEVENT_REPL_ASYNC_LOAD_STARTED:
|
|
assert(async_loading == 0);
|
|
async_loading = 1;
|
|
break;
|
|
case REDICTMODULE_SUBEVENT_REPL_ASYNC_LOAD_ABORTED:
|
|
/* Discard temp aux */
|
|
if (before_str_temp)
|
|
RedictModule_FreeString(ctx, before_str_temp);
|
|
if (after_str_temp)
|
|
RedictModule_FreeString(ctx, after_str_temp);
|
|
before_str_temp = NULL;
|
|
after_str_temp = NULL;
|
|
|
|
async_loading = 0;
|
|
break;
|
|
case REDICTMODULE_SUBEVENT_REPL_ASYNC_LOAD_COMPLETED:
|
|
if (before_str)
|
|
RedictModule_FreeString(ctx, before_str);
|
|
if (after_str)
|
|
RedictModule_FreeString(ctx, after_str);
|
|
before_str = before_str_temp;
|
|
after_str = after_str_temp;
|
|
|
|
before_str_temp = NULL;
|
|
after_str_temp = NULL;
|
|
|
|
async_loading = 0;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
void *testrdb_type_load(RedictModuleIO *rdb, int encver) {
|
|
int count = RedictModule_LoadSigned(rdb);
|
|
RedictModuleString *str = RedictModule_LoadString(rdb);
|
|
float f = RedictModule_LoadFloat(rdb);
|
|
long double ld = RedictModule_LoadLongDouble(rdb);
|
|
if (RedictModule_IsIOError(rdb)) {
|
|
RedictModuleCtx *ctx = RedictModule_GetContextFromIO(rdb);
|
|
if (str)
|
|
RedictModule_FreeString(ctx, str);
|
|
return NULL;
|
|
}
|
|
/* Using the values only after checking for io errors. */
|
|
assert(count==1);
|
|
assert(encver==1);
|
|
assert(f==1.5f);
|
|
assert(ld==0.333333333333333333L);
|
|
return str;
|
|
}
|
|
|
|
void testrdb_type_save(RedictModuleIO *rdb, void *value) {
|
|
RedictModuleString *str = (RedictModuleString*)value;
|
|
RedictModule_SaveSigned(rdb, 1);
|
|
RedictModule_SaveString(rdb, str);
|
|
RedictModule_SaveFloat(rdb, 1.5);
|
|
RedictModule_SaveLongDouble(rdb, 0.333333333333333333L);
|
|
}
|
|
|
|
void testrdb_aux_save(RedictModuleIO *rdb, int when) {
|
|
if (!(conf_aux_count & CONF_AUX_OPTION_BEFORE_KEYSPACE)) assert(when == REDICTMODULE_AUX_AFTER_RDB);
|
|
if (!(conf_aux_count & CONF_AUX_OPTION_AFTER_KEYSPACE)) assert(when == REDICTMODULE_AUX_BEFORE_RDB);
|
|
assert(conf_aux_count!=CONF_AUX_OPTION_NO_AUX);
|
|
if (when == REDICTMODULE_AUX_BEFORE_RDB) {
|
|
if (before_str) {
|
|
RedictModule_SaveSigned(rdb, 1);
|
|
RedictModule_SaveString(rdb, before_str);
|
|
} else {
|
|
RedictModule_SaveSigned(rdb, 0);
|
|
}
|
|
} else {
|
|
if (after_str) {
|
|
RedictModule_SaveSigned(rdb, 1);
|
|
RedictModule_SaveString(rdb, after_str);
|
|
} else {
|
|
RedictModule_SaveSigned(rdb, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
int testrdb_aux_load(RedictModuleIO *rdb, int encver, int when) {
|
|
assert(encver == 1);
|
|
if (!(conf_aux_count & CONF_AUX_OPTION_BEFORE_KEYSPACE)) assert(when == REDICTMODULE_AUX_AFTER_RDB);
|
|
if (!(conf_aux_count & CONF_AUX_OPTION_AFTER_KEYSPACE)) assert(when == REDICTMODULE_AUX_BEFORE_RDB);
|
|
assert(conf_aux_count!=CONF_AUX_OPTION_NO_AUX);
|
|
RedictModuleCtx *ctx = RedictModule_GetContextFromIO(rdb);
|
|
if (when == REDICTMODULE_AUX_BEFORE_RDB) {
|
|
if (async_loading == 0) {
|
|
if (before_str)
|
|
RedictModule_FreeString(ctx, before_str);
|
|
before_str = NULL;
|
|
int count = RedictModule_LoadSigned(rdb);
|
|
if (RedictModule_IsIOError(rdb))
|
|
return REDICTMODULE_ERR;
|
|
if (count)
|
|
before_str = RedictModule_LoadString(rdb);
|
|
} else {
|
|
if (before_str_temp)
|
|
RedictModule_FreeString(ctx, before_str_temp);
|
|
before_str_temp = NULL;
|
|
int count = RedictModule_LoadSigned(rdb);
|
|
if (RedictModule_IsIOError(rdb))
|
|
return REDICTMODULE_ERR;
|
|
if (count)
|
|
before_str_temp = RedictModule_LoadString(rdb);
|
|
}
|
|
} else {
|
|
if (async_loading == 0) {
|
|
if (after_str)
|
|
RedictModule_FreeString(ctx, after_str);
|
|
after_str = NULL;
|
|
int count = RedictModule_LoadSigned(rdb);
|
|
if (RedictModule_IsIOError(rdb))
|
|
return REDICTMODULE_ERR;
|
|
if (count)
|
|
after_str = RedictModule_LoadString(rdb);
|
|
} else {
|
|
if (after_str_temp)
|
|
RedictModule_FreeString(ctx, after_str_temp);
|
|
after_str_temp = NULL;
|
|
int count = RedictModule_LoadSigned(rdb);
|
|
if (RedictModule_IsIOError(rdb))
|
|
return REDICTMODULE_ERR;
|
|
if (count)
|
|
after_str_temp = RedictModule_LoadString(rdb);
|
|
}
|
|
}
|
|
|
|
if (RedictModule_IsIOError(rdb))
|
|
return REDICTMODULE_ERR;
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
void testrdb_type_free(void *value) {
|
|
if (value)
|
|
RedictModule_FreeString(NULL, (RedictModuleString*)value);
|
|
}
|
|
|
|
int testrdb_set_before(RedictModuleCtx *ctx, RedictModuleString **argv, int argc)
|
|
{
|
|
if (argc != 2) {
|
|
RedictModule_WrongArity(ctx);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
if (before_str)
|
|
RedictModule_FreeString(ctx, before_str);
|
|
before_str = argv[1];
|
|
RedictModule_RetainString(ctx, argv[1]);
|
|
RedictModule_ReplyWithLongLong(ctx, 1);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
int testrdb_get_before(RedictModuleCtx *ctx, RedictModuleString **argv, int argc)
|
|
{
|
|
REDICTMODULE_NOT_USED(argv);
|
|
if (argc != 1){
|
|
RedictModule_WrongArity(ctx);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
if (before_str)
|
|
RedictModule_ReplyWithString(ctx, before_str);
|
|
else
|
|
RedictModule_ReplyWithStringBuffer(ctx, "", 0);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
/* For purpose of testing module events, expose variable state during async_loading. */
|
|
int testrdb_async_loading_get_before(RedictModuleCtx *ctx, RedictModuleString **argv, int argc)
|
|
{
|
|
REDICTMODULE_NOT_USED(argv);
|
|
if (argc != 1){
|
|
RedictModule_WrongArity(ctx);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
if (before_str_temp)
|
|
RedictModule_ReplyWithString(ctx, before_str_temp);
|
|
else
|
|
RedictModule_ReplyWithStringBuffer(ctx, "", 0);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
int testrdb_set_after(RedictModuleCtx *ctx, RedictModuleString **argv, int argc)
|
|
{
|
|
if (argc != 2){
|
|
RedictModule_WrongArity(ctx);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
if (after_str)
|
|
RedictModule_FreeString(ctx, after_str);
|
|
after_str = argv[1];
|
|
RedictModule_RetainString(ctx, argv[1]);
|
|
RedictModule_ReplyWithLongLong(ctx, 1);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
int testrdb_get_after(RedictModuleCtx *ctx, RedictModuleString **argv, int argc)
|
|
{
|
|
REDICTMODULE_NOT_USED(argv);
|
|
if (argc != 1){
|
|
RedictModule_WrongArity(ctx);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
if (after_str)
|
|
RedictModule_ReplyWithString(ctx, after_str);
|
|
else
|
|
RedictModule_ReplyWithStringBuffer(ctx, "", 0);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
int testrdb_set_key(RedictModuleCtx *ctx, RedictModuleString **argv, int argc)
|
|
{
|
|
if (argc != 3){
|
|
RedictModule_WrongArity(ctx);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_WRITE);
|
|
RedictModuleString *str = RedictModule_ModuleTypeGetValue(key);
|
|
if (str)
|
|
RedictModule_FreeString(ctx, str);
|
|
RedictModule_ModuleTypeSetValue(key, testrdb_type, argv[2]);
|
|
RedictModule_RetainString(ctx, argv[2]);
|
|
RedictModule_CloseKey(key);
|
|
RedictModule_ReplyWithLongLong(ctx, 1);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
int testrdb_get_key(RedictModuleCtx *ctx, RedictModuleString **argv, int argc)
|
|
{
|
|
if (argc != 2){
|
|
RedictModule_WrongArity(ctx);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
RedictModuleKey *key = RedictModule_OpenKey(ctx, argv[1], REDICTMODULE_READ);
|
|
RedictModuleString *str = RedictModule_ModuleTypeGetValue(key);
|
|
RedictModule_CloseKey(key);
|
|
RedictModule_ReplyWithString(ctx, str);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
int testrdb_get_n_aux_load_called(RedictModuleCtx *ctx, RedictModuleString **argv, int argc)
|
|
{
|
|
REDICTMODULE_NOT_USED(ctx);
|
|
REDICTMODULE_NOT_USED(argv);
|
|
REDICTMODULE_NOT_USED(argc);
|
|
RedictModule_ReplyWithLongLong(ctx, n_aux_load_called);
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
int test2rdb_aux_load(RedictModuleIO *rdb, int encver, int when) {
|
|
REDICTMODULE_NOT_USED(rdb);
|
|
REDICTMODULE_NOT_USED(encver);
|
|
REDICTMODULE_NOT_USED(when);
|
|
n_aux_load_called++;
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
void test2rdb_aux_save(RedictModuleIO *rdb, int when) {
|
|
REDICTMODULE_NOT_USED(rdb);
|
|
REDICTMODULE_NOT_USED(when);
|
|
}
|
|
|
|
int RedictModule_OnLoad(RedictModuleCtx *ctx, RedictModuleString **argv, int argc) {
|
|
REDICTMODULE_NOT_USED(argv);
|
|
REDICTMODULE_NOT_USED(argc);
|
|
if (RedictModule_Init(ctx,"testrdb",1,REDICTMODULE_APIVER_1) == REDICTMODULE_ERR)
|
|
return REDICTMODULE_ERR;
|
|
|
|
RedictModule_SetModuleOptions(ctx, REDICTMODULE_OPTIONS_HANDLE_IO_ERRORS | REDICTMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD);
|
|
|
|
if (argc > 0)
|
|
RedictModule_StringToLongLong(argv[0], &conf_aux_count);
|
|
|
|
if (conf_aux_count==CONF_AUX_OPTION_NO_AUX) {
|
|
RedictModuleTypeMethods datatype_methods = {
|
|
.version = 1,
|
|
.rdb_load = testrdb_type_load,
|
|
.rdb_save = testrdb_type_save,
|
|
.aof_rewrite = NULL,
|
|
.digest = NULL,
|
|
.free = testrdb_type_free,
|
|
};
|
|
|
|
testrdb_type = RedictModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
|
|
if (testrdb_type == NULL)
|
|
return REDICTMODULE_ERR;
|
|
} else if (!(conf_aux_count & CONF_AUX_OPTION_NO_DATA)) {
|
|
RedictModuleTypeMethods datatype_methods = {
|
|
.version = REDICTMODULE_TYPE_METHOD_VERSION,
|
|
.rdb_load = testrdb_type_load,
|
|
.rdb_save = testrdb_type_save,
|
|
.aof_rewrite = NULL,
|
|
.digest = NULL,
|
|
.free = testrdb_type_free,
|
|
.aux_load = testrdb_aux_load,
|
|
.aux_save = testrdb_aux_save,
|
|
.aux_save_triggers = ((conf_aux_count & CONF_AUX_OPTION_BEFORE_KEYSPACE) ? REDICTMODULE_AUX_BEFORE_RDB : 0) |
|
|
((conf_aux_count & CONF_AUX_OPTION_AFTER_KEYSPACE) ? REDICTMODULE_AUX_AFTER_RDB : 0)
|
|
};
|
|
|
|
if (conf_aux_count & CONF_AUX_OPTION_SAVE2) {
|
|
datatype_methods.aux_save2 = testrdb_aux_save;
|
|
}
|
|
|
|
testrdb_type = RedictModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
|
|
if (testrdb_type == NULL)
|
|
return REDICTMODULE_ERR;
|
|
} else {
|
|
|
|
/* Used to verify that aux_save2 api without any data, saves nothing to the RDB. */
|
|
RedictModuleTypeMethods datatype_methods = {
|
|
.version = REDICTMODULE_TYPE_METHOD_VERSION,
|
|
.aux_load = test2rdb_aux_load,
|
|
.aux_save = test2rdb_aux_save,
|
|
.aux_save_triggers = ((conf_aux_count & CONF_AUX_OPTION_BEFORE_KEYSPACE) ? REDICTMODULE_AUX_BEFORE_RDB : 0) |
|
|
((conf_aux_count & CONF_AUX_OPTION_AFTER_KEYSPACE) ? REDICTMODULE_AUX_AFTER_RDB : 0)
|
|
};
|
|
if (conf_aux_count & CONF_AUX_OPTION_SAVE2) {
|
|
datatype_methods.aux_save2 = test2rdb_aux_save;
|
|
}
|
|
|
|
RedictModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
|
|
}
|
|
|
|
if (RedictModule_CreateCommand(ctx,"testrdb.set.before", testrdb_set_before,"deny-oom",0,0,0) == REDICTMODULE_ERR)
|
|
return REDICTMODULE_ERR;
|
|
|
|
if (RedictModule_CreateCommand(ctx,"testrdb.get.before", testrdb_get_before,"",0,0,0) == REDICTMODULE_ERR)
|
|
return REDICTMODULE_ERR;
|
|
|
|
if (RedictModule_CreateCommand(ctx,"testrdb.async_loading.get.before", testrdb_async_loading_get_before,"",0,0,0) == REDICTMODULE_ERR)
|
|
return REDICTMODULE_ERR;
|
|
|
|
if (RedictModule_CreateCommand(ctx,"testrdb.set.after", testrdb_set_after,"deny-oom",0,0,0) == REDICTMODULE_ERR)
|
|
return REDICTMODULE_ERR;
|
|
|
|
if (RedictModule_CreateCommand(ctx,"testrdb.get.after", testrdb_get_after,"",0,0,0) == REDICTMODULE_ERR)
|
|
return REDICTMODULE_ERR;
|
|
|
|
if (RedictModule_CreateCommand(ctx,"testrdb.set.key", testrdb_set_key,"deny-oom",1,1,1) == REDICTMODULE_ERR)
|
|
return REDICTMODULE_ERR;
|
|
|
|
if (RedictModule_CreateCommand(ctx,"testrdb.get.key", testrdb_get_key,"",1,1,1) == REDICTMODULE_ERR)
|
|
return REDICTMODULE_ERR;
|
|
|
|
if (RedictModule_CreateCommand(ctx,"testrdb.get.n_aux_load_called", testrdb_get_n_aux_load_called,"",1,1,1) == REDICTMODULE_ERR)
|
|
return REDICTMODULE_ERR;
|
|
|
|
RedictModule_SubscribeToServerEvent(ctx,
|
|
RedictModuleEvent_ReplAsyncLoad, replAsyncLoadCallback);
|
|
|
|
return REDICTMODULE_OK;
|
|
}
|
|
|
|
int RedictModule_OnUnload(RedictModuleCtx *ctx) {
|
|
if (before_str)
|
|
RedictModule_FreeString(ctx, before_str);
|
|
if (after_str)
|
|
RedictModule_FreeString(ctx, after_str);
|
|
if (before_str_temp)
|
|
RedictModule_FreeString(ctx, before_str_temp);
|
|
if (after_str_temp)
|
|
RedictModule_FreeString(ctx, after_str_temp);
|
|
return REDICTMODULE_OK;
|
|
}
|