diff options
Diffstat (limited to 'examples/redis-unstable/tests/modules/test_keymeta.c')
| -rw-r--r-- | examples/redis-unstable/tests/modules/test_keymeta.c | 585 |
1 files changed, 0 insertions, 585 deletions
diff --git a/examples/redis-unstable/tests/modules/test_keymeta.c b/examples/redis-unstable/tests/modules/test_keymeta.c deleted file mode 100644 index ca5d622..0000000 --- a/examples/redis-unstable/tests/modules/test_keymeta.c +++ /dev/null @@ -1,585 +0,0 @@ -/* An example module for attaching metadata to keys. - * - * This example lets tests create metadata-key classes and then SET and GET metadata - * to keys. The 8-byte slot stores a handle to a module-managed allocation; here - * we use to attach a string per-key. - * - * The module pre-registers several metadata classes during initialization and exposes - * the following commands (via RedisModule_CreateCommand): - * - * 1) KEYMETA.REGISTER <4-char-id> <version> [FLAGS] - * Register a new metadata-key class during module load. - * Returns the <keymeta-class-id> index (Returned from RedisModule_CreateKeyMetaClass) - * On failure, returns nil - * In a real module it should be registered "automatically" via OnLoad. - * - * FLAGS (colon-separated): - * KEEPONCOPY - Keep metadata on COPY operation - * KEEPONRENAME - Keep metadata on RENAME operation - * KEEPONMOVE - Keep metadata on MOVE operation - * UNLINKFREE - Use unlink callback for async free - * RDBLOAD - Enable rdb_load callback (metadata can be loaded from RDB) - * RDBSAVE - Enable rdb_save callback (metadata can be saved to RDB) - * ALLOWIGNORE - Enable ALLOW_IGNORE flag (graceful discard on load if - * class not registered or no rdb_load callback) - * - * Example: > keymeta.register KMT1 1 KEEPONCOPY:KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE - * Example: > keymeta.register KMT2 1 ALLOWIGNORE - * - * 2) KEYMETA.SET <4-char-id> <key> <string-value> - * Set the string value as metadata to given key. - * Note: - * - If already set earlier, then it is expected that it will released before setting a - * new string. That is why this command should start with trying to get first - * metadata for given key. - * - * 3) KEYMETA.GET <4-char-id> <key> - * Get the metadata attached to the key for the given class. - * Returns a string attached to the given key. Or nil if nothing is attached. - * - * 4) KEYMETA.UNREGISTER <4-char-id> - * This will mark the key metadata class as released. It can later be reused again - * by the same class (consider comment above). - * Return REDISMODULE_OK/REDISMODULE_ERR. - * - * 5) KEYMETA.ACTIVE - * Return total number of active metadata at the moment. - */ - -#include "redismodule.h" -#include <string.h> -#include <stdlib.h> -#include <assert.h> - -/* Virtualize class IDs for testing. Values: 0 unused, 1..7 used, -1 released */ -RedisModuleKeyMetaClassId class_ids[8] = { 0 }; - -/* Mapping from 4-char-id to class-id */ -typedef struct { - char name[5]; /* 4 chars + null terminator */ - RedisModuleKeyMetaClassId class_id; -} ClassMapping; - -#define MAX_CLASS_MAPPINGS 8 -static ClassMapping class_mappings[MAX_CLASS_MAPPINGS]; -static int num_class_mappings = 0; - -/* Reverse lookup: given a class_id, find the 4-char-id name */ -static const char* lookupClassName(RedisModuleKeyMetaClassId class_id) { - for (int i = 0; i < num_class_mappings; i++) { - if (class_mappings[i].class_id == class_id) { - return class_mappings[i].name; - } - } - return NULL; -} - -/* Track active metadata instances (not yet freed) */ -static long long active_metadata_count = 0; - -/* Helper functions for class mapping */ - -/* Add a mapping from 4-char-id to class-id */ -static int addClassMapping(const char *name, RedisModuleKeyMetaClassId class_id) { - if (num_class_mappings >= MAX_CLASS_MAPPINGS) { - return 0; /* No space */ - } - strncpy(class_mappings[num_class_mappings].name, name, 4); - class_mappings[num_class_mappings].name[4] = '\0'; - class_mappings[num_class_mappings].class_id = class_id; - num_class_mappings++; - return 1; -} - -/* Lookup class-id by 4-char-id. Returns -1 if not found. */ -static RedisModuleKeyMetaClassId lookupClassId(const char *name) { - for (int i = 0; i < num_class_mappings; i++) { - if (strncmp(class_mappings[i].name, name, 4) == 0) { - return class_mappings[i].class_id; - } - } - return -1; -} - -/* Remove a mapping by 4-char-id */ -static int removeClassMapping(const char *name) { - for (int i = 0; i < num_class_mappings; i++) { - if (strncmp(class_mappings[i].name, name, 4) == 0) { - /* Shift remaining entries down */ - for (int j = i; j < num_class_mappings - 1; j++) { - class_mappings[j] = class_mappings[j + 1]; - } - num_class_mappings--; - return 1; - } - } - return 0; -} - -/* Callback functions for metadata lifecycle */ - -/* Copy callback - called when a key is copied */ -static int KeyMetaCopyCallback(RedisModuleKeyOptCtx *ctx, uint64_t *meta) { - REDISMODULE_NOT_USED(ctx); - char *str = (char *)*meta; - /* Note, condition is redundant since cb only invoked when meta != reset_value */ - if (str) { - char *new_str = strdup(str); - *meta = (uint64_t)new_str; - active_metadata_count++; /* New metadata instance created */ - } - return 1; /* Keep metadata */ -} - -/* Rename callback - called when a key is renamed. */ -static int KeyMetaRenameDiscardCallback(RedisModuleKeyOptCtx *ctx, uint64_t *meta) { - REDISMODULE_NOT_USED(ctx); - REDISMODULE_NOT_USED(meta); - return 0; -} - -/* Unlink callback - called when a key is unlinked */ -static void KeyMetaUnlinkCallback(RedisModuleKeyOptCtx *ctx, uint64_t *meta) { - /* Let's challenge and free early on before free callback */ - /* Note, condition is redundant since cb only invoked when meta != reset_value */ - if (*meta != 0) { - char *str = (char *)*meta; - free(str); - *meta = 0; /* Set to reset_value !!! */ - active_metadata_count--; /* Metadata instance freed */ - } - REDISMODULE_NOT_USED(ctx); -} - -/* Free callback - called when metadata needs to be freed */ -static void KeyMetaFreeCallback(const char *keyname, uint64_t meta) { - REDISMODULE_NOT_USED(keyname); - /* Note, condition is redundant since cb only invoked when meta != reset_value */ - if (meta != 0) { - char *str = (char *)meta; - free(str); - active_metadata_count--; /* Metadata instance freed */ - } -} - -static int KeyMetaMoveDiscardCallback(RedisModuleKeyOptCtx *ctx, uint64_t *meta) { - REDISMODULE_NOT_USED(ctx); - REDISMODULE_NOT_USED(meta); - return 0; /* discard metadata */ -} - -/* RDB Save Callback - Serialize metadata to RDB - * This callback is invoked during RDB save to write the metadata value. - * - * Parameters: - * - rdb: RedisModuleIO context for writing to RDB - * - value: The kvobj (key-value object) - not used in this implementation - * - meta: Pointer to the 8-byte metadata value (pointer to our string) - */ -static void KeyMetaRDBSaveCallback(RedisModuleIO *rdb, void *value, uint64_t *meta) { - REDISMODULE_NOT_USED(value); - - /* If metadata is NULL (reset_value), don't save anything */ - if (*meta == 0) return; - - /* Extract the string from the metadata pointer */ - char *metadata_string = (char *)*meta; - - /* Save the string to RDB using SaveStringBuffer */ - RedisModule_SaveStringBuffer(rdb, metadata_string, strlen(metadata_string)); - /* Save more silly data */ - RedisModule_SaveSigned(rdb, 1); - RedisModule_SaveFloat(rdb, 1.5); - RedisModule_SaveLongDouble(rdb, 0.333333333333333333L); -} - -/* RDB Load Callback - Deserialize metadata from RDB - * This callback is invoked during RDB load to read the metadata value. - * - * Parameters: - * - rdb: RedisModuleIO context for reading from RDB - * - meta: Pointer to store the loaded 8-byte metadata value - * - encver: Encoding version (class version from RDB) - * - * Returns: - * - 1: Attach metadata to key (success) - * - 0: Ignore/skip metadata (not an error) - * - -1: Error - abort RDB load - */ -static int KeyMetaRDBLoadCallback(RedisModuleIO *rdb, uint64_t *meta, int encver) { - REDISMODULE_NOT_USED(encver); - - /* Load the string from RDB using LoadStringBuffer */ - size_t len; - char *loaded_string = RedisModule_LoadStringBuffer(rdb, &len); - - if (loaded_string == NULL) { - /* Error loading string */ - return -1; - } - - /* Allocate and copy the string (LoadStringBuffer returns a buffer that must be freed) */ - char *metadata_string = malloc(len + 1); - if (metadata_string == NULL) { - RedisModule_Free(loaded_string); - return -1; - } - - memcpy(metadata_string, loaded_string, len); - metadata_string[len] = '\0'; - RedisModule_Free(loaded_string); - - /* Load the additional data that was saved (must match rdb_save) */ - int64_t signed_val = RedisModule_LoadSigned(rdb); - float float_val = RedisModule_LoadFloat(rdb); - long double ldouble_val = RedisModule_LoadLongDouble(rdb); - /* We don't use these values, just need to consume them from the stream */ - (void)signed_val; - (void)float_val; - (void)ldouble_val; - - /* Store the pointer in metadata */ - *meta = (uint64_t)metadata_string; - active_metadata_count++; /* New metadata instance created */ - - /* Return 1 to attach metadata to the key */ - return 1; -} - -/* AOF Rewrite Callback - Common implementation for all classes - * This callback is invoked during AOF rewrite to emit commands that will - * recreate the metadata when the AOF is loaded. - * - * Parameters: - * - aof: RedisModuleIO context for writing to AOF - * - value: The kvobj (key-value object) - not used in this implementation - * - meta: The 8-byte metadata value (pointer to our string) - * - class_id: The class ID for this metadata - */ -static void KeyMetaAOFRewriteCallback_Class(RedisModuleIO *aof, void *value, uint64_t meta, RedisModuleKeyMetaClassId class_id) { - REDISMODULE_NOT_USED(value); - - /* If metadata is NULL (reset_value), don't emit anything */ - if (meta == 0) return; - - /* Extract the string from the metadata pointer */ - char *metadata_string = (char *)meta; - - /* Lookup the 9-byte-id name for this class */ - const char *class_name = lookupClassName(class_id); - if (!class_name) { - /* This shouldn't happen, but handle gracefully */ - return; - } - - /* Get the key name from the AOF IO context */ - const RedisModuleString *key = RedisModule_GetKeyNameFromIO(aof); - if (!key) { - /* Key name not available - shouldn't happen during AOF rewrite */ - return; - } - - /* Emit the KEYMETA.SET command to recreate this metadata - * Format: KEYMETA.SET <9-byte-id> <key> <string-value> */ - RedisModule_EmitAOF(aof, "KEYMETA.SET", "csc", - class_name, /* c: 9-byte-id (C string) */ - key, /* s: key name (RedisModuleString) */ - metadata_string); /* c: metadata value (C string) */ -} - -/* Individual AOF rewrite callbacks for each class (1-7) - * Each callback wraps the common implementation with its specific class ID */ -static void KeyMetaAOFRewriteCb1(RedisModuleIO *aof, void *value, uint64_t meta) { - KeyMetaAOFRewriteCallback_Class(aof, value, meta, 1); -} - -static void KeyMetaAOFRewriteCb2(RedisModuleIO *aof, void *value, uint64_t meta) { - KeyMetaAOFRewriteCallback_Class(aof, value, meta, 2); -} - -static void KeyMetaAOFRewriteCb3(RedisModuleIO *aof, void *value, uint64_t meta) { - KeyMetaAOFRewriteCallback_Class(aof, value, meta, 3); -} - -static void KeyMetaAOFRewriteCb4(RedisModuleIO *aof, void *value, uint64_t meta) { - KeyMetaAOFRewriteCallback_Class(aof, value, meta, 4); -} - -static void KeyMetaAOFRewriteCb5(RedisModuleIO *aof, void *value, uint64_t meta) { - KeyMetaAOFRewriteCallback_Class(aof, value, meta, 5); -} - -static void KeyMetaAOFRewriteCb6(RedisModuleIO *aof, void *value, uint64_t meta) { - KeyMetaAOFRewriteCallback_Class(aof, value, meta, 6); -} - -static void KeyMetaAOFRewriteCb7(RedisModuleIO *aof, void *value, uint64_t meta) { - KeyMetaAOFRewriteCallback_Class(aof, value, meta, 7); -} - -/* KEYMETA.REGISTER <4-char-id> <version> [KEEPONCOPY:KEEPONRENAME:UNLINKFREE:ALLOWIGNORE:NORDBLOAD:NORDBSAVE] */ -static int KeyMetaRegister_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { - if (argc < 3 || argc > 4) { - return RedisModule_WrongArity(ctx); - } - - /* argv[1]: key metadata class name */ - size_t namelen; - const char *metaname = RedisModule_StringPtrLen(argv[1], &namelen); - - /* argv[2]: key metadata class version */ - long long metaver; - if (RedisModule_StringToLongLong(argv[2], &metaver) != REDISMODULE_OK) { - RedisModule_ReplyWithError(ctx, "ERR invalid version number"); - return REDISMODULE_OK; - } - - /* Parse optional callback flags */ - int keep_on_copy = 0, keep_on_rename = 0, unlink_free = 0, keep_on_move = 0; - int allow_ignore = 0; /* Default: ALLOW_IGNORE disabled */ - int rdb_load = 0; /* Default: rdb_load disabled */ - int rdb_save = 0; /* Default: rdb_save disabled */ - - if (argc == 4) { - const char *flags = RedisModule_StringPtrLen(argv[3], NULL); - if (strstr(flags, "KEEPONCOPY")) keep_on_copy = 1; - if (strstr(flags, "KEEPONRENAME")) keep_on_rename = 1; - if (strstr(flags, "UNLINKFREE")) unlink_free = 1; - if (strstr(flags, "KEEPONMOVE")) keep_on_move = 1; - if (strstr(flags, "ALLOWIGNORE")) allow_ignore = 1; /* Enable ALLOW_IGNORE */ - if (strstr(flags, "RDBLOAD")) rdb_load = 1; /* Enable rdb_load */ - if (strstr(flags, "RDBSAVE")) rdb_save = 1; /* Enable rdb_save */ - } - - /* Setup configuration */ - RedisModuleKeyMetaClassConfig config = {0}; - config.version = REDISMODULE_KEY_META_VERSION; - config.flags = allow_ignore ? (1 << REDISMODULE_META_ALLOW_IGNORE) : 0; - config.reset_value = (uint64_t)NULL; /* NULL pointer means no resource to free */ - config.rdb_load = rdb_load ? KeyMetaRDBLoadCallback : NULL; - config.rdb_save = rdb_save ? KeyMetaRDBSaveCallback : NULL; - switch (num_class_mappings + 1) { /* distinct cb per class */ - case 1: config.aof_rewrite = KeyMetaAOFRewriteCb1; break; - case 2: config.aof_rewrite = KeyMetaAOFRewriteCb2; break; - case 3: config.aof_rewrite = KeyMetaAOFRewriteCb3; break; - case 4: config.aof_rewrite = KeyMetaAOFRewriteCb4; break; - case 5: config.aof_rewrite = KeyMetaAOFRewriteCb5; break; - case 6: config.aof_rewrite = KeyMetaAOFRewriteCb6; break; - case 7: config.aof_rewrite = KeyMetaAOFRewriteCb7; break; - default: config.aof_rewrite = NULL; break; - } - config.free = KeyMetaFreeCallback; - config.copy = keep_on_copy ? KeyMetaCopyCallback : NULL; - config.rename = keep_on_rename ? NULL : KeyMetaRenameDiscardCallback; - config.move = keep_on_move ? NULL : KeyMetaMoveDiscardCallback; - config.defrag = NULL; - config.unlink = unlink_free ? KeyMetaUnlinkCallback : NULL; - config.mem_usage = NULL; - config.free_effort = NULL; - - /* Create the metadata class */ - RedisModuleKeyMetaClassId class_id = RedisModule_CreateKeyMetaClass(ctx, metaname, (int)metaver, &config); - - if (class_id < 0) { - RedisModule_ReplyWithError(ctx, "ERR failed to create metadata class"); - return REDISMODULE_OK; - } else { - /* Store the mapping from 9-byte-id to class-id */ - if (!addClassMapping(metaname, class_id)) { - RedisModule_ReplyWithError(ctx, "ERR failed to store class mapping"); - return REDISMODULE_OK; - } - RedisModule_ReplyWithLongLong(ctx, class_id); - } - - return REDISMODULE_OK; -} - -/* KEYMETA.SET <9-byte-id> <key> <string-value> */ -static int KeyMetaSet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { - if (argc != 4) { - return RedisModule_WrongArity(ctx); - } - - /* Parse arguments */ - const char *metaname = RedisModule_StringPtrLen(argv[1], NULL); - RedisModuleString *keyname = argv[2]; - const char *value = RedisModule_StringPtrLen(argv[3], NULL); - - /* Lookup the metadata class by name */ - RedisModuleKeyMetaClassId class_id = lookupClassId(metaname); - if (class_id < 0) { - RedisModule_ReplyWithError(ctx, "ERR metadata class not found"); - return REDISMODULE_OK; - } - - /* Open the key for writing */ - RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, REDISMODULE_READ | REDISMODULE_WRITE); - - if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) { - RedisModule_ReplyWithNull(ctx); - RedisModule_CloseKey(key); - return REDISMODULE_OK; - } - - /* Check if metadata already exists and free it first. - * - * Note: The caller is responsible for retrieving and freeing any existing - * pointer-based metadata before RM_SetKeyMeta() to a new value - */ - uint64_t meta = 0; - if (RedisModule_GetKeyMeta(class_id, key, &meta) == REDISMODULE_OK) { - if (meta != 0) { - free((char *)meta); - active_metadata_count--; /* Old metadata freed */ - } - } - - char *new_str = strdup(value); - int res = RedisModule_SetKeyMeta(class_id, key, (uint64_t)new_str); - - if (res == REDISMODULE_OK) { - active_metadata_count++; /* New metadata instance created */ - } - - RedisModule_CloseKey(key); - - if (res == REDISMODULE_OK) { - RedisModule_ReplyWithSimpleString(ctx, "OK"); - } else { - free(new_str); - RedisModule_ReplyWithError(ctx, "ERR failed to set metadata"); - } - return REDISMODULE_OK; -} - -/* KEYMETA.GET <9-byte-id> <key> */ -static int KeyMetaGet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { - if (argc != 3) { - return RedisModule_WrongArity(ctx); - } - - /* Parse arguments */ - const char *metaname = RedisModule_StringPtrLen(argv[1], NULL); - RedisModuleString *keyname = argv[2]; - - /* Lookup the metadata class by name */ - RedisModuleKeyMetaClassId class_id = lookupClassId(metaname); - if (class_id < 0) { - RedisModule_ReplyWithError(ctx, "ERR metadata class not found"); - return REDISMODULE_OK; - } - - /* Open the key for reading */ - RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, REDISMODULE_READ); - if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) { - RedisModule_ReplyWithNull(ctx); - RedisModule_CloseKey(key); - return REDISMODULE_OK; - } - - /* Get the metadata */ - uint64_t meta = 0; - int result = RedisModule_GetKeyMeta(class_id, key, &meta); - - RedisModule_CloseKey(key); - - if (result == REDISMODULE_OK && meta != 0) { - char *str = (char *)meta; - RedisModule_ReplyWithCString(ctx, str); - } else { - RedisModule_ReplyWithNull(ctx); - } - - return REDISMODULE_OK; -} - -/* KEYMETA.UNREGISTER <9-byte-id> */ -static int KeyMetaUnregister_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { - if (argc != 2) { - return RedisModule_WrongArity(ctx); - } - - /* Parse arguments */ - const char *metaname = RedisModule_StringPtrLen(argv[1], NULL); - - /* Lookup the metadata class by name */ - RedisModuleKeyMetaClassId class_id = lookupClassId(metaname); - if (class_id < 0) { - RedisModule_ReplyWithError(ctx, "ERR metadata class not found"); - return REDISMODULE_OK; - } - - /* Release the metadata class */ - int result = RedisModule_ReleaseKeyMetaClass(class_id); - - if (result == REDISMODULE_OK) { - /* Remove the mapping */ - removeClassMapping(metaname); - RedisModule_ReplyWithSimpleString(ctx, "OK"); - } else { - RedisModule_ReplyWithError(ctx, "ERR failed to unregister class"); - } - return REDISMODULE_OK; -} - -/* KEYMETA.ACTIVE - * Returns the total number of active metadata instances that haven't been freed yet. - * This is useful for testing to verify that metadata is properly cleaned up. */ -static int KeyMetaActive_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { - if (argc != 1) { - return RedisModule_WrongArity(ctx); - } - REDISMODULE_NOT_USED(argv); - - RedisModule_ReplyWithLongLong(ctx, active_metadata_count); - return REDISMODULE_OK; -} - -/* Module initialization */ -int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { - REDISMODULE_NOT_USED(argv); - REDISMODULE_NOT_USED(argc); - - if (RedisModule_Init(ctx, "test_metakey", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) { - return REDISMODULE_ERR; - } - - /* Register commands */ - if (RedisModule_CreateCommand(ctx, "keymeta.register", - KeyMetaRegister_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR) { - return REDISMODULE_ERR; - } - - if (RedisModule_CreateCommand(ctx, "keymeta.set", - KeyMetaSet_RedisCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) { - return REDISMODULE_ERR; - } - - if (RedisModule_CreateCommand(ctx, "keymeta.get", - KeyMetaGet_RedisCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) { - return REDISMODULE_ERR; - } - - if (RedisModule_CreateCommand(ctx, "keymeta.unregister", - KeyMetaUnregister_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR) { - return REDISMODULE_ERR; - } - - if (RedisModule_CreateCommand(ctx, "keymeta.active", - KeyMetaActive_RedisCommand, "readonly fast", 0, 0, 0) == REDISMODULE_ERR) { - return REDISMODULE_ERR; - } - - return REDISMODULE_OK; -} - -int RedisModule_OnUnload(RedisModuleCtx *ctx) { - REDISMODULE_NOT_USED(ctx); - long unsigned int i; - for (i = 0 ; i < sizeof(class_ids) / sizeof(class_ids[0]); i++) { - if (class_ids[i] > 0) - RedisModule_ReleaseKeyMetaClass(class_ids[i]); - } - return REDISMODULE_OK; -} |
