summaryrefslogtreecommitdiff
path: root/examples/redis-unstable/tests/modules/test_keymeta.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/redis-unstable/tests/modules/test_keymeta.c')
-rw-r--r--examples/redis-unstable/tests/modules/test_keymeta.c585
1 files changed, 585 insertions, 0 deletions
diff --git a/examples/redis-unstable/tests/modules/test_keymeta.c b/examples/redis-unstable/tests/modules/test_keymeta.c
new file mode 100644
index 0000000..ca5d622
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/test_keymeta.c
@@ -0,0 +1,585 @@
+/* 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;
+}