From 646c958bbd506839f02dbe8801275e11e2657955 Mon Sep 17 00:00:00 2001 From: antirez Date: Tue, 10 May 2016 18:54:58 +0200 Subject: [PATCH] Modules: doc layout improved. --- src/module.c | 42 +- src/modules/API.md | 1215 ++++++++++++++++++++++------------------- src/modules/INTRO.md | 786 ++++++++++++++++++++++++++ src/modules/gendoc.rb | 7 +- 4 files changed, 1475 insertions(+), 575 deletions(-) create mode 100644 src/modules/INTRO.md diff --git a/src/module.c b/src/module.c index 08beda77..72d6aeb0 100644 --- a/src/module.c +++ b/src/module.c @@ -368,7 +368,7 @@ int commandFlagsFromString(char *s) { * * The command function type is the following: * - * int MyCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); + * int MyCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); * * And is supposed to always return REDISMODULE_OK. * @@ -451,7 +451,7 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c return REDISMODULE_OK; } -/* Called by RM_Init() to setup the ctx->module structure. +/* Called by RM_Init() to setup the `ctx->module` structure. * * This is an internal function, Redis modules developers don't need * to use it. */ @@ -578,7 +578,7 @@ const char *RM_StringPtrLen(RedisModuleString *str, size_t *len) { return str->ptr; } -/* Convert the string into a long long integer, storing it at *ll. +/* Convert the string into a long long integer, storing it at `*ll`. * Returns REDISMODULE_OK on success. If the string can't be parsed * as a valid, strict long long (no spaces before/after), REDISMODULE_ERR * is returned. */ @@ -587,7 +587,7 @@ int RM_StringToLongLong(RedisModuleString *str, long long *ll) { REDISMODULE_ERR; } -/* Convert the string into a double, storing it at *d. +/* Convert the string into a double, storing it at `*d`. * Returns REDISMODULE_OK on success or REDISMODULE_ERR if the string is * not a valid string representation of a double value. */ int RM_StringToDouble(RedisModuleString *str, double *d) { @@ -665,7 +665,7 @@ int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) { } /* Reply with an array type of 'len' elements. However 'len' other calls - * to ReplyWith* style functions must follow in order to emit the elements + * to `ReplyWith*` style functions must follow in order to emit the elements * of the array. * * When producing arrays with a number of element that is not known beforehand @@ -1219,9 +1219,9 @@ int RM_ZsetAddFlagsFromCoreFlags(int flags) { * On success the function returns REDISMODULE_OK. On the following errors * REDISMODULE_ERR is returned: * - * - The key was not opened for writing. - * - The key is of the wrong type. - * - 'score' double value is not a number (NaN). + * * The key was not opened for writing. + * * The key is of the wrong type. + * * 'score' double value is not a number (NaN). */ int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr) { int flags = 0; @@ -1274,8 +1274,8 @@ int RM_ZsetIncrby(RedisModuleKey *key, double score, RedisModuleString *ele, int * The function returns REDISMODULE_OK on success, and REDISMODULE_ERR * on one of the following conditions: * - * - The key was not opened for writing. - * - The key is of the wrong type. + * * The key was not opened for writing. + * * The key is of the wrong type. * * The return value does NOT indicate the fact the element was really * removed (since it existed) or not, just if the function was executed @@ -1303,9 +1303,9 @@ int RM_ZsetRem(RedisModuleKey *key, RedisModuleString *ele, int *deleted) { * 'ele' and returns REDISMODULE_OK. Otherwise REDISMODULE_ERR is returned * to signal one of the following conditions: * - * - There is no such element 'ele' in the sorted set. - * - The key is not a sorted set. - * - The key is an open empty key. + * * There is no such element 'ele' in the sorted set. + * * The key is not a sorted set. + * * The key is an open empty key. */ int RM_ZsetScore(RedisModuleKey *key, RedisModuleString *ele, double *score) { if (key->value == NULL) return REDISMODULE_ERR; @@ -1663,8 +1663,8 @@ int RM_ZsetRangePrev(RedisModuleKey *key) { * * In the following case the return value is always zero: * - * - The key was not open for writing. - * - The key was associated with a non Hash value. + * * The key was not open for writing. + * * The key was associated with a non Hash value. */ int RM_HashSet(RedisModuleKey *key, int flags, ...) { va_list ap; @@ -1731,8 +1731,8 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) { * * This is an example usage: * - * RedisModuleString *first, *second; - * RedisModule_HashGet(mykey,REDISMODULE_HASH_NONE,argv[1],&first, + * RedisModuleString *first, *second; + * RedisModule_HashGet(mykey,REDISMODULE_HASH_NONE,argv[1],&first, * argv[2],&second,NULL); * * As with RedisModule_HashSet() the behavior of the command can be specified @@ -1747,13 +1747,13 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) { * * Example of REDISMODULE_HASH_CFIELD: * - * RedisModuleString *username, *hashedpass; - * RedisModule_HashGet(mykey,"username",&username,"hp",&hashedpass, NULL); + * RedisModuleString *username, *hashedpass; + * RedisModule_HashGet(mykey,"username",&username,"hp",&hashedpass, NULL); * * Example of REDISMODULE_HASH_EXISTS: * - * int exists; - * RedisModule_HashGet(mykey,argv[1],&exists,NULL); + * int exists; + * RedisModule_HashGet(mykey,argv[1],&exists,NULL); * * The function returns REDISMODULE_OK on success and REDISMODULE_ERR if * the key is not an hash value. diff --git a/src/modules/API.md b/src/modules/API.md index c64a5007..0ff303d0 100644 --- a/src/modules/API.md +++ b/src/modules/API.md @@ -1,786 +1,895 @@ -Redis Modules API reference manual -=== +# Modules API reference + +## `RM_GetApi` + + int RM_GetApi(const char *funcname, void **targetPtrPtr); -Redis modules make possible to extend Redis functionality using external -modules, implementing new Redis commands at a speed and with features -similar to what can be done inside the core itself. +Lookup the requested module API and store the function pointer into the +target pointer. The function returns `REDISMODULE_ERR` if there is no such +named API, otherwise `REDISMODULE_OK`. -Redis modules are dynamic libraries, that can be loaded into Redis at -startup or using the `MODULE LOAD` command. Redis exports a C API, in the -form of a single C header file called `redismodule.h`. Modules are meant -to be written in C, however it will be possible to use C++ or other languages -that have C binding functionalities. +This function is not meant to be used by modules developer, it is only +used implicitly by including redismodule.h. -Modules are designed in order to be loaded into different versions of Redis, -so a given module does not need to be designed, or recompiled, in order to -run with a specific version of Redis. For this reason, the module will -register to the Redis core using a specific API version. The current API -version is "1". +## `RM_IsKeysPositionRequest` -This document is about an alpha version of Redis modules. API, functionalities -and other details may change in the future. + int RM_IsKeysPositionRequest(RedisModuleCtx *ctx); -# Loading modules +Return non-zero if a module command, that was declared with the +flag "getkeys-api", is called in a special way to get the keys positions +and not to get executed. Otherwise zero is returned. -In order to test the module you are developing, you can load the module -using the following `redis.conf` configuration directive: +## `RM_KeyAtPos` - loadmodule /path/to/mymodule.so + void RM_KeyAtPos(RedisModuleCtx *ctx, int pos); -It is also possible to load a module at runtime using the following command: +When a module command is called in order to obtain the position of +keys, since it was flagged as "getkeys-api" during the registration, +the command implementation checks for this special call using the +`RedisModule_IsKeysPositionRequest()` API and uses this function in +order to report keys, like in the following example: - MODULE LOAD /path/to/mymodule.so + if (`RedisModule_IsKeysPositionRequest(ctx))` { + `RedisModule_KeyAtPos(ctx`,1); + `RedisModule_KeyAtPos(ctx`,2); + } -In order to list all loaded modules, use: + Note: in the example below the get keys API would not be needed since + keys are at fixed positions. This interface is only used for commands + with a more complex structure. - MODULE LIST +## `RM_CreateCommand` -Finally, you can unload (and later reload if you wish) a module using the -following command: + int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep); - MODULE UNLOAD mymodule +Register a new command in the Redis server, that will be handled by +calling the function pointer 'func' using the RedisModule calling +convention. The function returns `REDISMODULE_ERR` if the specified command +name is already busy or a set of invalid flags were passed, otherwise +`REDISMODULE_OK` is returned and the new command is registered. -Note that `mymodule` above is not the filename without the `.so` suffix, but -instead, the name the module used to register itself into the Redis core. -The name can be obtained using `MODULE LIST`. However it is good practice -that the filename of the dynamic library is the same as the name the module -uses to register itself into the Redis core. +This function must be called during the initialization of the module +inside the `RedisModule_OnLoad()` function. Calling this function outside +of the initialization function is not defined. -# The simplest module you can write +The command function type is the following: -In order to show the different parts of a module, here we'll show a very -simple module that implements a command that outputs a random number. + int MyCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); - #include "redismodule.h" - #include +And is supposed to always return `REDISMODULE_OK`. - int HelloworldRand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { - RedisModule_ReplyWithLongLong(ctx,rand()); - return REDISMODULE_OK; - } +The set of flags 'strflags' specify the behavior of the command, and should +be passed as a C string compoesd of space separated words, like for +example "write deny-oom". The set of flags are: - int RedisModule_OnLoad(RedisModuleCtx *ctx) { - if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1) - == REDISMODULE_ERR) return REDISMODULE_ERR; +* **"write"**: The command may modify the data set (it may also read + from it). +* **"readonly"**: The command returns data from keys but never writes. +* **"admin"**: The command is an administrative command (may change + replication or perform similar tasks). +* **"deny-oom"**: The command may use additional memory and should be + denied during out of memory conditions. +* **"deny-script"**: Don't allow this command in Lua scripts. +* **"allow-loading"**: Allow this command while the server is loading data. + Only commands not interacting with the data set + should be allowed to run in this mode. If not sure + don't use this flag. +* **"pubsub"**: The command publishes things on Pub/Sub channels. +* **"random"**: The command may have different outputs even starting + from the same input arguments and key values. +* **"allow-stale"**: The command is allowed to run on slaves that don't + serve stale data. Don't use if you don't know what + this means. +* **"no-monitor"**: Don't propoagate the command on monitor. Use this if + the command has sensible data among the arguments. +* **"fast"**: The command time complexity is not greater + than O(log(N)) where N is the size of the collection or + anything else representing the normal scalability + issue with the command. +* **"getkeys-api"**: The command implements the interface to return + the arguments that are keys. Used when start/stop/step + is not enough because of the command syntax. +* **"no-cluster"**: The command should not register in Redis Cluster + since is not designed to work with it because, for + example, is unable to report the position of the + keys, programmatically creates key names, or any + other reason. - if (RedisModule_CreateCommand(ctx,"helloworld.rand", - HelloworldRand_RedisCommand) == REDISMODULE_ERR) - return REDISMODULE_ERR; +## `RM_SetModuleAttribs` - return REDISMODULE_OK; - } + void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int apiver); -The example module has two functions. One implements a command called -HELLOWORLD.RAND. This function is specific of that module. However the -other function called `RedisModule_OnLoad()` must be present in each -Redis module. It is the entry point for the module to be initialized, -register its commands, and potentially other private data structures -it uses. +Called by `RM_Init()` to setup the `ctx->module` structure. -Note that it is a good idea for modules to call commands with the -name of the module followed by a dot, and finally the command name, -like in the case of `HELLOWORLD.RAND`. This way it is less likely to -have collisions. +This is an internal function, Redis modules developers don't need +to use it. -Note that if different modules have colliding commands, they'll not be -able to work in Redis at the same time, since the function -`RedisModule_CreateCommand` will fail in one of the modules, so the module -loading will abort returning an error condition. +## `RM_AutoMemory` -# Module initialization + void RM_AutoMemory(RedisModuleCtx *ctx); -The above example shows the usage of the function `RedisModule_Init()`. -It should be the first function called by the module `OnLoad` function. -The following is the function prototype: +Enable automatic memory management. See API.md for more information. - int RedisModule_Init(RedisModuleCtx *ctx, const char *modulename, - int module_version, int api_version); +The function must be called as the first function of a command implementation +that wants to use automatic memory. -The `Init` function announces the Redis core that the module has a given -name, its version (that is reported by `MODULE LIST`), and that is willing -to use a specific version of the API. +## `RM_CreateString` -If the API version is wrong, the name is already taken, or there are other -similar errors, the function will return `REDISMODULE_ERR`, and the module -`OnLoad` function should return ASAP with an error. + RedisModuleString *RM_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len); -Before the `Init` function is called, no other API function can be called, -otherwise the module will segfault and the Redis instance will crash. +Create a new module string object. The returned string must be freed +with `RedisModule_FreeString()`, unless automatic memory is enabled. -The second function called, `RedisModule_CreateCommand`, is used in order -to register commands into the Redis core. The following is the prototype: +The string is created by copying the `len` bytes starting +at `ptr`. No reference is retained to the passed buffer. - int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *cmdname, - RedisModuleCmdFunc cmdfunc); +## `RM_CreateStringFromLongLong` -As you can see, most Redis modules API calls all take as first argument -the `context` of the module, so that they have a reference to the module -calling it, to the command and client executing a given command, and so forth. + RedisModuleString *RM_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll); -To create a new command, the above function needs the context, the command -name, and the function pointer of the function implementing the command, -which must have the following prototype: +Like `RedisModule_CreatString()`, but creates a string starting from a long long +integer instead of taking a buffer and its length. +The returned string must be released with `RedisModule_FreeString()` or by +enabling automatic memory management. - int mycommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); +## `RM_FreeString` -The command function arguments are just the context, that will be passed -to all the other API calls, the command argument vector, and total number -of arguments, as passed by the user. + void RM_FreeString(RedisModuleCtx *ctx, RedisModuleString *str); -As you can see, the arguments are provided as pointers to a specific data -type, the `RedisModuleString`. This is an opaque data type you have API -functions to access and use, direct access to its fields is never needed. +Free a module string object obtained with one of the Redis modules API calls +that return new string objects. -Zooming into the example command implementation, we can find another call: +It is possible to call this function even when automatic memory management +is enabled. In that case the string will be released ASAP and removed +from the pool of string to release at the end. - int RedisModule_ReplyWithLongLong(RedisModuleCtx *ctx, long long integer); +## `RM_StringPtrLen` -This function returns an integer to the client that invoked the command, -exactly like other Redis commands do, like for example `INCR` or `SCARD`. + const char *RM_StringPtrLen(RedisModuleString *str, size_t *len); -# Setup and dependencies of a Redis module +Given a string module object, this function returns the string pointer +and length of the string. The returned pointer and length should only +be used for read only accesses and never modified. -Redis modules don't depend on Redis or some other library, nor they -need to be compiled with a specific `redismodule.h` file. In order -to create a new module, just copy a recent version of `redismodule.h` -in your source tree, link all the libraries you want, and create -a dynamic library having the `RedisModule_OnLoad()` function symbol -exported. +## `RM_StringToLongLong` -The module will be able to load into different versions of Redis. + int RM_StringToLongLong(RedisModuleString *str, long long *ll); -# Working with RedisModuleString objects +Convert the string into a long long integer, storing it at `*ll`. +Returns `REDISMODULE_OK` on success. If the string can't be parsed +as a valid, strict long long (no spaces before/after), `REDISMODULE_ERR` +is returned. -The command argument vector `argv` passed to module commands, and the -return value of other module APIs functions, are of type `RedisModuleString`. +## `RM_StringToDouble` -Usually you directly pass module strings to other API calls, however sometimes -you may need to directly access the string object. + int RM_StringToDouble(RedisModuleString *str, double *d); -There are a few functions in order to work with string objects: +Convert the string into a double, storing it at `*d`. +Returns `REDISMODULE_OK` on success or `REDISMODULE_ERR` if the string is +not a valid string representation of a double value. - const char *RedisModule_StringPtrLen(RedisModuleString *string, size_t *len); +## `RM_WrongArity` -The above function accesses a string by returning its pointer and setting its -length in `len`. -You should never write to a string object pointer, as you can see from the -`const` pointer qualifier. + int RM_WrongArity(RedisModuleCtx *ctx); -However, if you want, you can create new string objects using the following -API: +Send an error about the number of arguments given to the command, +citing the command name in the error message. - RedisModuleString *RedisModule_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len); +Example: -The string returned by the above command must be freed using a corresponding -call to `RedisModule_FreeString()`: + if (argc != 3) return `RedisModule_WrongArity(ctx)`; - void RedisModule_FreeString(RedisModuleString *str); +## `RM_ReplyWithLongLong` -However if you want to avoid having to free strings, the automatic memory -management, covered later in this document, can be a good alternative, by -doing it for you. + int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll); -Note that the strings provided via the argument vector `argv` never need -to be freed. You only need to free new strings you create, or new strings -returned by other APIs, where it is specified that the returned string must -be freed. +Send an integer reply to the client, with the specified long long value. +The function always returns `REDISMODULE_OK`. -## Creating strings from numbers or parsing strings as numbers +## `RM_ReplyWithError` -Creating a new string from an integer is a very common operation, so there -is a function to do this: + int RM_ReplyWithError(RedisModuleCtx *ctx, const char *err); - RedisModuleString *mystr = RedisModule_CreateStringFromLongLong(ctx,10); +Reply with the error 'err'. -Similarly in order to parse a string as a number: +Note that 'err' must contain all the error, including +the initial error code. The function only provides the initial "-", so +the usage is, for example: - long long myval; - if (RedisModule_StringToLongLong(ctx,argv[1],&myval) == REDISMODULE_OK) { - /* Do something with 'myval' */ - } + `RM_ReplyWithError(ctx`,"ERR Wrong Type"); -## Accessing Redis keys from modules +and not just: -Most Redis modules, in order to be useful, have to interact with the Redis -data space (this is not always true, for example an ID generator may -never touch Redis keys). Redis modules have two different APIs in order to -access the Redis data space, one is a low level API that provides very -fast access and a set of functions to manipulate Redis data structures. -The other API is more high level, and allows to call Redis commands and -fetch the result, similarly to how Lua scripts access Redis. + `RM_ReplyWithError(ctx`,"Wrong Type"); -The high level API is also useful in order to access Redis functionalities -that are not available as APIs. +The function always returns `REDISMODULE_OK`. -In general modules developers should prefer the low level API, because commands -implemented using the low level API run at a speed comparable to the speed -of native Redis commands. However there are definitely use cases for the -higher level API. For example often the bottleneck could be processing the -data and not accessing it. +## `RM_ReplyWithSimpleString` -Also note that sometimes using the low level API is not harder compared to -the higher level one. + int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg); -# Calling Redis commands +Reply with a simple string (+... \r\n in RESP protocol). This replies +are suitable only when sending a small non-binary string with small +overhead, like "OK" or similar replies. -The high level API to access Redis is the sum of the `RedisModule_Call()` -function, together with the functions needed in order to access the -reply object returned by `Call()`. +The function always returns `REDISMODULE_OK`. -`RedisModule_Call` uses a special calling convention, with a format specifier -that is used to specify what kind of objects you are passing as arguments -to the function. +## `RM_ReplyWithArray` -Redis commands are invoked just using a command name and a list of arguments. -However when calling commands, the arguments may originate from different -kind of strings: null-terminated C strings, RedisModuleString objects as -received from the `argv` parameter in the command implementation, binary -safe C buffers with a pointer and a length, and so forth. + int RM_ReplyWithArray(RedisModuleCtx *ctx, long len); -For example if I want to call `INCRBY` using a first argument (the key) -a string received in the argument vector `argv`, which is an array -of RedisModuleString object pointers, and a C string representing the -number "10" as second argument (the increment), I'll use the following -function call: +Reply with an array type of 'len' elements. However 'len' other calls +to `ReplyWith*` style functions must follow in order to emit the elements +of the array. - RedisModuleCallReply *reply; - reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10"); +When producing arrays with a number of element that is not known beforehand +the function can be called with the special count +`REDISMODULE_POSTPONED_ARRAY_LEN`, and the actual number of elements can be +later set with `RedisModule_ReplySetArrayLength()` (which will set the +latest "open" count if there are multiple ones). -The first argument is the context, and the second is always a null terminated -C string with the command name. The third argument is the format specifier -where each character corresponds to the type of the arguments that will follow. -In the above case `"sc"` means a RedisModuleString object, and a null -terminated C string. The other arguments are just the two arguments as -specified. In fact `argv[1]` is a RedisModuleString and `"10"` is a null -terminated C string. +The function always returns `REDISMODULE_OK`. -This is the full list of format specifiers: +## `RM_ReplySetArrayLength` -* **c** -- Null terminated C string pointer. -* **b** -- C buffer, two arguments needed: C string pointer and `size_t` length. -* **s** -- RedisModuleString as received in `argv` or by other Redis module APIs returning a RedisModuleString object. -* **l** -- Long long integer. -* **v** -- Array of RedisModuleString objects. -* **!** -- This modifier just tells the function to replicate the command to slaves and AOF. It is ignored from the point of view of arguments parsing. + void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len); -The function returns a `RedisModuleCallReply` object on success, on -error NULL is returned. +When `RedisModule_ReplyWithArray()` is used with the argument +`REDISMODULE_POSTPONED_ARRAY_LEN`, because we don't know beforehand the number +of items we are going to output as elements of the array, this function +will take care to set the array length. -NULL is returned when the command name is invalid, the format specifier uses -characters that are not recognized, or when the command is called with the -wrong number of arguments. In the above cases the `errno` var is set to `EINVAL`. NULL is also returned when, in an instance with Cluster enabled, the target -keys are about non local hash slots. In this case `errno` is set to `EPERM`. +Since it is possible to have multiple array replies pending with unknown +length, this function guarantees to always set the latest array length +that was created in a postponed way. -## Working with RedisModuleCallReply objects. +For example in order to output an array like [1,[10,20,30]] we +could write: -`RedisModuleCall` returns reply objects that can be accessed using the -`RedisModule_CallReply*` family of functions. + `RedisModule_ReplyWithArray(ctx`,`REDISMODULE_POSTPONED_ARRAY_LEN`); + `RedisModule_ReplyWithLongLong(ctx`,1); + `RedisModule_ReplyWithArray(ctx`,`REDISMODULE_POSTPONED_ARRAY_LEN`); + `RedisModule_ReplyWithLongLong(ctx`,10); + `RedisModule_ReplyWithLongLong(ctx`,20); + `RedisModule_ReplyWithLongLong(ctx`,30); + `RedisModule_ReplySetArrayLength(ctx`,3); // Set len of 10,20,30 array. + `RedisModule_ReplySetArrayLength(ctx`,2); // Set len of top array -In order to obtain the type or reply (corresponding to one of the data types -supported by the Redis protocol), the function `RedisModule_CallReplyType()` -is used: +Note that in the above example there is no reason to postpone the array +length, since we produce a fixed number of elements, but in the practice +the code may use an interator or other ways of creating the output so +that is not easy to calculate in advance the number of elements. - reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10"); - if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) { - long long myval = RedisModule_CallReplyInteger(reply); - /* Do something with myval. */ - } +## `RM_ReplyWithStringBuffer` -Valid reply types are: + int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len); -* `REDISMODULE_REPLY_STRING` Bulk string or status replies. -* `REDISMODULE_REPLY_ERROR` Errors. -* `REDISMODULE_REPLY_INTEGER` Signed 64 bit integers. -* `REDISMODULE_REPLY_ARRAY` Array of replies. -* `REDISMODULE_REPLY_NULL` NULL reply. +Reply with a bulk string, taking in input a C buffer pointer and length. -Strings, errors and arrays have an associated length. For strings and errors -the length corresponds to the length of the string. For arrays the length -is the number of elements. To obtain the reply length the following function -is used: +The function always returns `REDISMODULE_OK`. - size_t reply_len = RedisModule_CallReplyLength(reply); +## `RM_ReplyWithString` -In order to obtain the value of an integer reply, the following function is used, as already shown in the example above: + int RM_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str); - long long reply_integer_val = RedisModule_CallReplyInteger(reply); +Reply with a bulk string, taking in input a RedisModuleString object. -Called with a reply object of the wrong type, the above function always -returns `LLONG_MIN`. +The function always returns `REDISMODULE_OK`. -Sub elements of array replies are accessed this way: +## `RM_ReplyWithNull` - RedisModuleCallReply *subreply; - subreply = RedisModule_CallReplyArrayElement(reply,idx); + int RM_ReplyWithNull(RedisModuleCtx *ctx); -The above function returns NULL if you try to access out of range elements. +Reply to the client with a NULL. In the RESP protocol a NULL is encoded +as the string "$-1\r\n". -Strings and errors (which are like strings but with a different type) can -be accessed using in the following way, making sure to never write to -the resulting pointer (that is returned as as `const` pointer so that -misusing must be pretty explicit): +The function always returns `REDISMODULE_OK`. - size_t len; - char *ptr = RedisModule_CallReplyStringPtr(reply,&len); +## `RM_ReplyWithCallReply` -If the reply type is not a string or an error, NULL is returned. + int RM_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply); -RedisCallReply objects are not the same as module string objects -(RedisModuleString types). However sometimes you may need to pass replies -of type string or integer, to API functions expecting a module string. +Reply exactly what a Redis command returned us with `RedisModule_Call()`. +This function is useful when we use `RedisModule_Call()` in order to +execute some command, as we want to reply to the client exactly the +same reply we obtained by the command. -When this is the case, you may want to evaluate if using the low level -API could be a simpler way to implement your command, or you can use -the following function in order to create a new string object from a -call reply of type string, error or integer: +The function always returns `REDISMODULE_OK`. - RedisModuleString *mystr = RedisModule_CreateStringFromCallReply(myreply); +## `RM_ReplyWithDouble` -If the reply is not of the right type, NULL is returned. -The returned string object should be released with `RedisModule_FreeString()` -as usually, or by enabling automatic memory management (see corresponding -section). + int RM_ReplyWithDouble(RedisModuleCtx *ctx, double d); -# Releasing call reply objects +Send a string reply obtained converting the double 'd' into a bulk string. +This function is basically equivalent to converting a double into +a string into a C buffer, and then calling the function +`RedisModule_ReplyWithStringBuffer()` with the buffer and length. -Reply objects must be freed using `RedisModule_FreeCallRelpy`. For arrays, -you need to free only the top level reply, not the nested replies. -Currently the module implementation provides a protection in order to avoid -crashing if you free a nested reply object for error, however this feature -is not guaranteed to be here forever, so should not be considered part -of the API. +The function always returns `REDISMODULE_OK`. -If you use automatic memory management (explained later in this document) -you don't need to free replies (but you still could if you wish to release -memory ASAP). +## `RM_Replicate` -## Returning values from Redis commands + int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); -Like normal Redis commands, new commands implemented via modules must be -able to return values to the caller. The API exports a set of functions for -this goal, in order to return the usual types of the Redis protocol, and -arrays of such types as elemented. Also errors can be returned with any -error string and code (the error code is the initial uppercase letters in -the error message, like the "BUSY" string in the "BUSY the sever is busy" error -message). +Replicate the specified command and arguments to slaves and AOF, as effect +of execution of the calling command implementation. -All the functions to send a reply to the client are called -`RedisModule_ReplyWith`. +The replicated commands are always wrapped into the MULTI/EXEC that +contains all the commands replicated in a given module command +execution. However the commands replicated with `RedisModule_Call()` +are the first items, the ones replicated with `RedisModule_Replicate()` +will all follow before the EXEC. -To return an error, use: +Modules should try to use one interface or the other. - RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *err); +This command follows exactly the same interface of `RedisModule_Call()`, +so a set of format specifiers must be passed, followed by arguments +matching the provided format specifiers. -There is a predefined error string for key of wrong type errors: +Please refer to `RedisModule_Call()` for more information. - REDISMODULE_ERRORMSG_WRONGTYPE +The command returns `REDISMODULE_ERR` if the format specifiers are invalid +or the command name does not belong to a known command. -Example usage: +## `RM_ReplicateVerbatim` - RedisModule_ReplyWithError(ctx,"ERR invalid arguments"); + int RM_ReplicateVerbatim(RedisModuleCtx *ctx); -We already saw how to reply with a long long in the examples above: +This function will replicate the command exactly as it was invoked +by the client. Note that this function will not wrap the command into +a MULTI/EXEC stanza, so it should not be mixed with other replication +commands. - RedisModule_ReplyWithLongLong(ctx,12345); +Basically this form of replication is useful when you want to propagate +the command to the slaves and AOF file exactly as it was called, since +the command can just be re-executed to deterministically re-create the +new state starting from the old one. -To reply with a simple string, that can't contain binary values or newlines, -(so it's suitable to send small words, like "OK") we use: +The function always returns `REDISMODULE_OK`. - RedisModule_ReplyWithSimpleString(ctx,"OK"); +## `RM_GetClientId` -It's possible to reply with "bulk strings" that are binary safe, using -two different functions: + unsigned long long RM_GetClientId(RedisModuleCtx *ctx); - int RedisModule_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len); +Return the ID of the current client calling the currently active module +command. The returned ID has a few guarantees: - int RedisModule_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str); +1. The ID is different for each different client, so if the same client + executes a module command multiple times, it can be recognized as + having the same ID, otherwise the ID will be different. +2. The ID increases monotonically. Clients connecting to the server later + are guaranteed to get IDs greater than any past ID previously seen. -The first function gets a C pointer and length. The second a RedisMoudleString -object. Use one or the other depending on the source type you have at hand. +Valid IDs are from 1 to 2^64-1. If 0 is returned it means there is no way +to fetch the ID in the context the function was currently called. -In order to reply with an array, you just need to use a function to emit the -array length, followed by as many calls to the above functions as the number -of elements of the array are: +## `RM_GetSelectedDb` - RedisModule_ReplyWithArray(ctx,2); - RedisModule_ReplyWithStringBuffer(ctx,"age",3); - RedisModule_ReplyWithLongLong(ctx,22); + int RM_GetSelectedDb(RedisModuleCtx *ctx); -To return nested arrays is easy, your nested array element just uses another -call to `RedisModule_ReplyWithArray()` followed by the calls to emit the -sub array elements. +Return the currently selected DB. -## Returning arrays with dynamic length +## `RM_SelectDb` -Sometimes it is not possible to know beforehand the number of items of -an array. As an example, think of a Redis module implementing a FACTOR -command that given a number outputs the prime factors. Instead of -factorializing the number, storing the prime factors into an array, and -later produce the command reply, a better solution is to start an array -reply where the length is not known, and set it later. This is accomplished -with a special argument to `RedisModule_ReplyWithArray()`: + int RM_SelectDb(RedisModuleCtx *ctx, int newid); - RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); +Change the currently selected DB. Returns an error if the id +is out of range. -The above call starts an array reply so we can use other `ReplyWith` calls -in order to produce the array items. Finally in order to set the length -se use the following call: +Note that the client will retain the currently selected DB even after +the Redis command implemented by the module calling this function +returns. - RedisModule_ReplySetArrayLength(ctx, number_of_items); +If the module command wishes to change something in a different DB and +returns back to the original one, it should call `RedisModule_GetSelectedDb()` +before in order to restore the old DB number before returning. -In the case of the FACTOR command, this translates to some code similar -to this: +## `RM_OpenKey` - RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); - number_of_factors = 0; - while(still_factors) { - RedisModule_ReplyWithLongLong(ctx, some_factor); - number_of_factors++; - } - RedisModule_ReplySetArrayLength(ctx, number_of_factors); + void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode); -Another common use case for this feature is iterating over the arrays of -some collection and only returning the ones passing some kind of filtering. +Return an handle representing a Redis key, so that it is possible +to call other APIs with the key handle as argument to perform +operations on the key. -It is possible to have multiple nested arrays with postponed reply. -Each call to `SetArray()` will set the length of the latest corresponding -call to `ReplyWithArray()`: +The return value is the handle repesenting the key, that must be +closed with `RM_CloseKey()`. - RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); - ... generate 100 elements ... - RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); - ... generate 10 elements ... - RedisModule_ReplySetArrayLength(ctx, 10); - RedisModule_ReplySetArrayLength(ctx, 100); +If the key does not exist and WRITE mode is requested, the handle +is still returned, since it is possible to perform operations on +a yet not existing key (that will be created, for example, after +a list push operation). If the mode is just READ instead, and the +key does not exist, NULL is returned. However it is still safe to +call `RedisModule_CloseKey()` and `RedisModule_KeyType()` on a NULL +value. -This creates a 100 items array having as last element a 10 items array. +## `RM_CloseKey` -# Arity and type checks + void RM_CloseKey(RedisModuleKey *key); -Often commands need to check that the number of arguments and type of the key -is correct. In order to report a wrong arity, there is a specific function -called `RedisModule_WrongArity()`. The usage is trivial: +Close a key handle. - if (argc != 2) return RedisModule_WrongArity(ctx); +## `RM_KeyType` -Checking for the wrong type involves opening the key and checking the type: + int RM_KeyType(RedisModuleKey *key); - RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], - REDISMODULE_READ|REDISMODULE_WRITE); +Return the type of the key. If the key pointer is NULL then +`REDISMODULE_KEYTYPE_EMPTY` is returned. - int keytype = RedisModule_KeyType(key); - if (keytype != REDISMODULE_KEYTYPE_STRING && - keytype != REDISMODULE_KEYTYPE_EMPTY) - { - RedisModule_CloseKey(key); - return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); - } +## `RM_ValueLength` -Note that you often want to proceed with a command both if the key -is of the expected type, or if it's empty. + size_t RM_ValueLength(RedisModuleKey *key); -## Low level access to keys +Return the length of the value associated with the key. +For strings this is the length of the string. For all the other types +is the number of elements (just counting keys for hashes). -Low level access to keys allow to perform operations on value objects associated -to keys directly, with a speed similar to what Redis uses internally to -implement the built-in commands. +If the key pointer is NULL or the key is empty, zero is returned. -Once a key is opened, a key pointer is returned that will be used with all the -other low level API calls in order to perform operations on the key or its -associated value. +## `RM_DeleteKey` -Because the API is meant to be very fast, it cannot do too many run-time -checks, so the user must be aware of certain rules to follow: + int RM_DeleteKey(RedisModuleKey *key); -* Opening the same key multiple times where at least one instance is opened for writing, is undefined and may lead to crashes. -* While a key is open, it should only be accessed via the low level key API. For example opening a key, then calling DEL on the same key using the `RedisModule_Call()` API will result into a crash. However it is safe to open a key, perform some operation with the low level API, closing it, then using other APIs to manage the same key, and later opening it again to do some more work. +If the key is open for writing, remove it, and setup the key to +accept new writes as an empty key (that will be created on demand). +On success `REDISMODULE_OK` is returned. If the key is not open for +writing `REDISMODULE_ERR` is returned. -In order to open a key the `RedisModule_OpenKey` function is used. It returns -a key pointer, that we'll use with all the next calls to access and modify -the value: +## `RM_GetExpire` - RedisModuleKey *key; - key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ); + mstime_t RM_GetExpire(RedisModuleKey *key); -The second argument is the key name, that must be a `RedisModuleString` object. -The third argument is the mode: `REDISMODULE_READ` or `REDISMODULE_WRITE`. -It is possible to use `|` to bitwise OR the two modes to open the key in -both modes. Currently a key opened for writing can also be accessed for reading -but this is to be considered an implementation detail. The right mode should -be used in sane modules. +Return the key expire value, as milliseconds of remaining TTL. +If no TTL is associated with the key or if the key is empty, +`REDISMODULE_NO_EXPIRE` is returned. -You can open non exisitng keys for writing, since the keys will be created -when an attempt to write to the key is performed. However when opening keys -just for reading, `RedisModule_OpenKey` will return NULL if the key does not -exist. +## `RM_SetExpire` -Once you are done using a key, you can close it with: + int RM_SetExpire(RedisModuleKey *key, mstime_t expire); - RedisModule_CloseKey(key); +Set a new expire for the key. If the special expire +`REDISMODULE_NO_EXPIRE` is set, the expire is cancelled if there was +one (the same as the PERSIST command). -Note that if automatic memory management is enabled, you are not forced to -close keys. When the module function returns, Redis will take care to close -all the keys which are still open. +Note that the expire must be provided as a positive integer representing +the number of milliseconds of TTL the key should have. -## Getting the key type +The function returns `REDISMODULE_OK` on success or `REDISMODULE_ERR` if +the key was not open for writing or is an empty key. -In order to obtain the value of a key, use the `RedisModule_KeyType()` function: +## `RM_StringSet` - int keytype = RedisModule_KeyType(key); + int RM_StringSet(RedisModuleKey *key, RedisModuleString *str); -It returns one of the following values: +If the key is open for writing, set the specified string 'str' as the +value of the key, deleting the old value if any. +On success `REDISMODULE_OK` is returned. If the key is not open for +writing or there is an active iterator, `REDISMODULE_ERR` is returned. - REDISMODULE_KEYTYPE_EMPTY - REDISMODULE_KEYTYPE_STRING - REDISMODULE_KEYTYPE_LIST - REDISMODULE_KEYTYPE_HASH - REDISMODULE_KEYTYPE_SET - REDISMODULE_KEYTYPE_ZSET +## `RM_StringDMA` -The above are just the usual Redis key types, with the addition of an empty -type, that signals the key pointer is associated with an empty key that -does not yet exists. + char *RM_StringDMA(RedisModuleKey *key, size_t *len, int mode); -## Creating new keys +Prepare the key associated string value for DMA access, and returns +a pointer and size (by reference), that the user can use to read or +modify the string in-place accessing it directly via pointer. -To create a new key, open it for writing and then write to it using one -of the key writing functions. Example: +The 'mode' is composed by bitwise OR-ing the following flags: - RedisModuleKey *key; - key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ); - if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) { - RedisModule_StringSet(key,argv[2]); - } +`REDISMODULE_READ` -- Read access +`REDISMODULE_WRITE` -- Write access -## Deleting keys +If the DMA is not requested for writing, the pointer returned should +only be accessed in a read-only fashion. -Just use: +On error (wrong type) NULL is returned. - RedisModule_DeleteKey(key); +DMA access rules: -The function returns `REDISMODULE_ERR` if the key is not open for writing. -Note that after a key gets deleted, it is setup in order to be targeted -by new key commands. For example `RedisModule_KeyType()` will return it is -an empty key, and writing to it will create a new key, possibly of another -type (depending on the API used). +1. No other key writing function should be called since the moment +the pointer is obtained, for all the time we want to use DMA access +to read or modify the string. -## Managing key expires (TTLs) +2. Each time `RM_StringTruncate()` is called, to continue with the DMA +access, `RM_StringDMA()` should be called again to re-obtain +a new pointer and length. -To control key expires two functions are provided, that are able to set, -modify, get, and unset the time to live associated with a key. +3. If the returned pointer is not NULL, but the length is zero, no +byte can be touched (the string is empty, or the key itself is empty) +so a `RM_StringTruncate()` call should be used if there is to enlarge +the string, and later call StringDMA() again to get the pointer. -One function is used in order to query the current expire of an open key: +## `RM_StringTruncate` - mstime_t RedisModule_GetExpire(RedisModuleKey *key); + int RM_StringTruncate(RedisModuleKey *key, size_t newlen); -The function returns the time to live of the key in milliseconds, or -`REDISMODULE_NO_EXPIRE` as a special value to signal the key has no associated -expire or does not exist at all (you can differentiate the two cases checking -if the key type is `REDISMODULE_KEYTYPE_EMPTY`). +If the string is open for writing and is of string type, resize it, padding +with zero bytes if the new length is greater than the old one. -In order to change the expire of a key the following function is used instead: +After this call, `RM_StringDMA()` must be called again to continue +DMA access with the new pointer. - int RedisModule_SetExpire(RedisModuleKey *key, mstime_t expire); +The function returns `REDISMODULE_OK` on success, and `REDISMODULE_ERR` on +error, that is, the key is not open for writing, is not a string +or resizing for more than 512 MB is requested. -When called on a non existing key, `REDISMODULE_ERR` is returned, because -the function can only associate expires to existing open keys (non existing -open keys are only useful in order to create new values with data type -specific write operations). +If the key is empty, a string key is created with the new string value +unless the new length value requested is zero. -Again the `expire` time is specified in milliseconds. If the key has currently -no expire, a new expire is set. If the key already have an expire, it is -replaced with the new value. +## `RM_ListPush` -If the key has an expire, and the special value `REDISMODULE_NO_EXPIRE` is -used as a new expire, the expire is removed, similarly to the Redis -`PERSIST` command. In case the key was already persistent, no operation is -performed. + int RM_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele); -## Obtaining the length of values +Push an element into a list, on head or tail depending on 'where' argumnet. +If the key pointer is about an empty key opened for writing, the key +is created. On error (key opened for read-only operations or of the wrong +type) `REDISMODULE_ERR` is returned, otherwise `REDISMODULE_OK` is returned. -There is a single function in order to retrieve the length of the value -associated to an open key. The returned length is value-specific, and is -the string length for strings, and the number of elements for the aggregated -data types (how many elements there is in a list, set, sorted set, hash). +## `RM_ListPop` - size_t len = RedisModule_ValueLength(key); + RedisModuleString *RM_ListPop(RedisModuleKey *key, int where); -If the key does not exist, 0 is returned by the function: +Pop an element from the list, and returns it as a module string object +that the user should be free with `RM_FreeString()` or by enabling +automatic memory. 'where' specifies if the element should be popped from +head or tail. The command returns NULL if: +1) The list is empty. +2) The key was not open for writing. +3) The key is not a list. -## String type API +## `RM_ZsetAddFlagsToCoreFlags` -Setting a new string value, like the Redis `SET` command does, is performed -using: + int RM_ZsetAddFlagsToCoreFlags(int flags); - int RedisModule_StringSet(RedisModuleKey *key, RedisModuleString *str); +Conversion from/to public flags of the Modules API and our private flags, +so that we have everything decoupled. -The function works exactly like the Redis `SET` command itself, that is, if -there is a prior value (of any type) it will be deleted. +## `RM_ZsetAddFlagsFromCoreFlags` -Accessing existing string values is performed using DMA (direct memory -access) for speed. The API will return a pointer and a length, so that's -possible to access and, if needed, modify the string directly. + int RM_ZsetAddFlagsFromCoreFlags(int flags); - size_t len, j; - char *myptr = RedisModule_StringDMA(key,REDISMODULE_WRITE,&len); - for (j = 0; j < len; j++) myptr[j] = 'A'; +See previous function comment. -In the above example we write directly on the string. Note that if you want -to write, you must be sure to ask for `WRITE` mode. +## `RM_ZsetAdd` -DMA pointers are only valid if no other operations are performed with the key -before using the pointer, after the DMA call. + int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr); -Sometimes when we want to manipulate strings directly, we need to change -their size as well. For this scope, the `RedisModule_StringTruncate` function -is used. Example: +Add a new element into a sorted set, with the specified 'score'. +If the element already exists, the score is updated. - RedisModule_StringTruncate(mykey,1024); +A new sorted set is created at value if the key is an empty open key +setup for writing. -The function truncates, or enlarges the string as needed, padding it with -zero bytes if the previos length is smaller than the new length we request. -If the string does not exist since `key` is associated to an open empty key, -a string value is created and associated to the key. +Additional flags can be passed to the function via a pointer, the flags +are both used to receive input and to communicate state when the function +returns. 'flagsptr' can be NULL if no special flags are used. -Note that every time `StringTruncate()` is called, we need to re-obtain -the DMA pointer again, since the old may be invalid. +The input flags are: -## List type API +`REDISMODULE_ZADD_XX`: Element must already exist. Do nothing otherwise. +`REDISMODULE_ZADD_NX`: Element must not exist. Do nothing otherwise. -It's possible to push and pop values from list values: +The output flags are: - int RedisModule_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele); - RedisModuleString *RedisModule_ListPop(RedisModuleKey *key, int where); +`REDISMODULE_ZADD_ADDED`: The new element was added to the sorted set. +`REDISMODULE_ZADD_UPDATED`: The score of the element was updated. +`REDISMODULE_ZADD_NOP`: No operation was performed because XX or NX flags. -In both the APIs the `where` argument specifies if to push or pop from tail -or head, using the following macros: +On success the function returns `REDISMODULE_OK`. On the following errors +`REDISMODULE_ERR` is returned: - REDISMODULE_LIST_HEAD - REDISMODULE_LIST_TAIL +- The key was not opened for writing. +- The key is of the wrong type. +- 'score' double value is not a number (NaN). -Elements returned by `RedisModule_ListPop()` are like strings craeted with -`RedisModule_CreateString()`, they must be released with -`RedisModule_FreeString()` or by enabling automatic memory management. +## `RM_ZsetIncrby` -## Set type API + int RM_ZsetIncrby(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore); -Work in progress. +This function works exactly like `RM_ZsetAdd()`, but instead of setting +a new score, the score of the existing element is incremented, or if the +element does not already exist, it is added assuming the old score was +zero. -## Sorted set type API +The input and output flags, and the return value, have the same exact +meaning, with the only difference that this function will return +`REDISMODULE_ERR` even when 'score' is a valid double number, but adding it +to the existing score resuts into a NaN (not a number) condition. -Documentation missing, please refer to the top comments inside `module.c` -for the following functions: +This function has an additional field 'newscore', if not NULL is filled +with the new score of the element after the increment, if no error +is returned. -* `RedisModule_ZsetAdd` -* `RedisModule_ZsetIncrby` -* `RedisModule_ZsetScore` -* `RedisModule_ZsetRem` +## `RM_ZsetRem` -And for the sorted set iterator: + int RM_ZsetRem(RedisModuleKey *key, RedisModuleString *ele, int *deleted); -* `RedisModule_ZsetRangeStop` -* `RedisModule_ZsetFirstInScoreRange` -* `RedisModule_ZsetLastInScoreRange` -* `RedisModule_ZsetFirstInLexRange` -* `RedisModule_ZsetLastInLexRange` -* `RedisModule_ZsetRangeCurrentElement` -* `RedisModule_ZsetRangeNext` -* `RedisModule_ZsetRangePrev` -* `RedisModule_ZsetRangeEndReached` +Remove the specified element from the sorted set. +The function returns `REDISMODULE_OK` on success, and `REDISMODULE_ERR` +on one of the following conditions: -## Hash type API +- The key was not opened for writing. +- The key is of the wrong type. -Documentation missing, please refer to the top comments inside `module.c` -for the following functions: +The return value does NOT indicate the fact the element was really +removed (since it existed) or not, just if the function was executed +with success. -* `RedisModule_HashSet` -* `RedisModule_HashGet` +In order to know if the element was removed, the additional argument +'deleted' must be passed, that populates the integer by reference +setting it to 1 or 0 depending on the outcome of the operation. +The 'deleted' argument can be NULL if the caller is not interested +to know if the element was really removed. -## Iterating aggregated values +Empty keys will be handled correctly by doing nothing. -Work in progress. +## `RM_ZsetScore` -# Replicating commands + int RM_ZsetScore(RedisModuleKey *key, RedisModuleString *ele, double *score); -If you want to use module commands exactly like normal Redis commands, in the -context of replicated Redis instances, or using the AOF file for persistence, -it is important for module commands to handle their replication in a consistent -way. +On success retrieve the double score associated at the sorted set element +'ele' and returns `REDISMODULE_OK`. Otherwise `REDISMODULE_ERR` is returned +to signal one of the following conditions: -When using the higher level APIs to invoke commands, replication happens -automatically if you use the "!" modifier in the format string of -`RedisModule_Call()` as in the following example: +- There is no such element 'ele' in the sorted set. +- The key is not a sorted set. +- The key is an open empty key. - reply = RedisModule_Call(ctx,"INCR","!sc",argv[1],"10"); +## `RM_ZsetRangeStop` -As you can see the format specifier is `"!sc"`. The bang is not parsed as a -format specifier, but it internally flags the command as "must replicate". + void RM_ZsetRangeStop(RedisModuleKey *key); -If you use the above programming style, there are no problems. -However sometimes things are more complex than that, and you use the low level -API. In this case, if there are no side effects in the command execution, and -it consistently always performs the same work, what is possible to do is to -replicate the command verbatim as the user executed it. To do that, you just -need to call the following function: +Stop a sorted set iteration. - RedisModule_ReplicateVerbatim(ctx); +## `RM_ZsetRangeEndReached` -When you use the above API, you should not use any other replication function -since they are not guaranteed to mix well. + int RM_ZsetRangeEndReached(RedisModuleKey *key); -However this is not the only option. It's also possible to exactly tell -Redis what commands to replicate as the effect of the command execution, using -an API similar to `RedisModule_Call()` but that instead of calling the command -sends it to the AOF / slaves stream. Example: +Return the "End of range" flag value to signal the end of the iteration. - RedisModule_Replicate(ctx,"INCRBY","cl","foo",my_increment); +## `RM_ZsetFirstInScoreRange` -It's possible to call `RedisModule_Replicate` multiple times, and each -will emit a command. All the sequence emitted is wrapped between a -`MULTI/EXEC` transaction, so that the AOF and replication effects are the -same as executing a single command. + int RM_ZsetFirstInScoreRange(RedisModuleKey *key, double min, double max, int minex, int maxex); -Note that `Call()` replication and `Replicate()` replication have a rule, -in case you want to mix both forms of replication (not necessarily a good -idea if there are simpler approaches). Commands replicated with `Call()` -are always the first emitted in the final `MULTI/EXEC` block, while all -the commands emitted with `Replicate()` will follow. +Setup a sorted set iterator seeking the first element in the specified +range. Returns `REDISMODULE_OK` if the iterator was correctly initialized +otherwise `REDISMODULE_ERR` is returned in the following conditions: -# Automatic memory management +1. The value stored at key is not a sorted set or the key is empty. -Normally when writing programs in the C language, programmers need to manage -memory manually. This is why the Redis modules API has functions to release -strings, close open keys, free replies, and so forth. +The range is specified according to the two double values 'min' and 'max'. +Both can be infinite using the following two macros: -However given that commands are executed in a contained environment and -with a set of strict APIs, Redis is able to provide automatic memory management -to modules, at the cost of some performance (most of the time, a very low -cost). +`REDISMODULE_POSITIVE_INFINITE` for positive infinite value +`REDISMODULE_NEGATIVE_INFINITE` for negative infinite value -When automatic memory management is enabled: +'minex' and 'maxex' parameters, if true, respectively setup a range +where the min and max value are exclusive (not included) instead of +inclusive. -1. You don't need to close open keys. -2. You don't need to free replies. -3. You don't need to free RedisModuleString objects. +## `RM_ZsetLastInScoreRange` -However you can still do it, if you want. For example, automatic memory -management may be active, but inside a loop allocating a lot of strings, -you may still want to free strings no longer used. + int RM_ZsetLastInScoreRange(RedisModuleKey *key, double min, double max, int minex, int maxex); -In order to enable automatic memory management, just call the following -function at the start of the command implementation: +Exactly like `RedisModule_ZsetFirstInScoreRange()` but the last element of +the range is selected for the start of the iteration instead. - RedisModule_AutoMemory(ctx); +## `RM_ZsetFirstInLexRange` -Automatic memory management is usually the way to go, however experienced -C programmers may not use it in order to gain some speed and memory usage -benefit. + int RM_ZsetFirstInLexRange(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); -# Writing commands compatible with Redis Cluster +Setup a sorted set iterator seeking the first element in the specified +lexicographical range. Returns `REDISMODULE_OK` if the iterator was correctly +initialized otherwise `REDISMODULE_ERR` is returned in the +following conditions: -Documentation missing, please check the following functions inside `module.c`: +1. The value stored at key is not a sorted set or the key is empty. +2. The lexicographical range 'min' and 'max' format is invalid. - RedisModule_IsKeysPositionRequest(ctx); - RedisModule_KeyAtPos(ctx,pos); +'min' and 'max' should be provided as two RedisModuleString objects +in the same format as the parameters passed to the ZRANGEBYLEX command. +The function does not take ownership of the objects, so they can be released +ASAP after the iterator is setup. + +## `RM_ZsetLastInLexRange` + + int RM_ZsetLastInLexRange(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); + +Exactly like `RedisModule_ZsetFirstInLexRange()` but the last element of +the range is selected for the start of the iteration instead. + +## `RM_ZsetRangeCurrentElement` + + RedisModuleString *RM_ZsetRangeCurrentElement(RedisModuleKey *key, double *score); + +Return the current sorted set element of an active sorted set iterator +or NULL if the range specified in the iterator does not include any +element. + +## `RM_ZsetRangeNext` + + int RM_ZsetRangeNext(RedisModuleKey *key); + +Go to the next element of the sorted set iterator. Returns 1 if there was +a next element, 0 if we are already at the latest element or the range +does not include any item at all. + +## `RM_ZsetRangePrev` + + int RM_ZsetRangePrev(RedisModuleKey *key); + +Go to the previous element of the sorted set iterator. Returns 1 if there was +a previous element, 0 if we are already at the first element or the range +does not include any item at all. + +## `RM_HashSet` + + int RM_HashSet(RedisModuleKey *key, int flags, ...); + +Set the field of the specified hash field to the specified value. +If the key is an empty key open for writing, it is created with an empty +hash value, in order to set the specified field. + +The function is variadic and the user must specify pairs of field +names and values, both as RedisModuleString pointers (unless the +CFIELD option is set, see later). + +Example to set the hash argv[1] to the value argv[2]: + + `RedisModule_HashSet(key`,`REDISMODULE_HASH_NONE`,argv[1],argv[2],NULL); + +The function can also be used in order to delete fields (if they exist) +by setting them to the specified value of `REDISMODULE_HASH_DELETE`: + + `RedisModule_HashSet(key`,`REDISMODULE_HASH_NONE`,argv[1], + `REDISMODULE_HASH_DELETE`,NULL); + +The behavior of the command changes with the specified flags, that can be +set to `REDISMODULE_HASH_NONE` if no special behavior is needed. + +`REDISMODULE_HASH_NX`: The operation is performed only if the field was not + already existing in the hash. +`REDISMODULE_HASH_XX`: The operation is performed only if the field was + already existing, so that a new value could be + associated to an existing filed, but no new fields + are created. +`REDISMODULE_HASH_CFIELDS`: The field names passed are null terminated C + strings instead of RedisModuleString objects. + +Unless NX is specified, the command overwrites the old field value with +the new one. + +When using `REDISMODULE_HASH_CFIELDS`, field names are reported using +normal C strings, so for example to delete the field "foo" the following +code can be used: + + `RedisModule_HashSet(key`,`REDISMODULE_HASH_CFIELDS`,"foo", + `REDISMODULE_HASH_DELETE`,NULL); + +Return value: + +The number of fields updated (that may be less than the number of fields +specified because of the XX or NX options). + +In the following case the return value is always zero: + +- The key was not open for writing. +- The key was associated with a non Hash value. + +## `RM_HashGet` + + int RM_HashGet(RedisModuleKey *key, int flags, ...); + +Get fields from an hash value. This function is called using a variable +number of arguments, alternating a field name (as a StringRedisModule +pointer) with a pointer to a StringRedisModule pointer, that is set to the +value of the field if the field exist, or NULL if the field did not exist. +At the end of the field/value-ptr pairs, NULL must be specified as last +argument to signal the end of the arguments in the variadic function. + +This is an example usage: + + RedisModuleString *first, *second; + `RedisModule_HashGet(mykey`,`REDISMODULE_HASH_NONE`,argv[1],&first, + argv[2],&second,NULL); + +As with `RedisModule_HashSet()` the behavior of the command can be specified +passing flags different than `REDISMODULE_HASH_NONE`: + +`REDISMODULE_HASH_CFIELD`: field names as null terminated C strings. + +`REDISMODULE_HASH_EXISTS`: instead of setting the value of the field +expecting a RedisModuleString pointer to pointer, the function just +reports if the field esists or not and expects an integer pointer +as the second element of each pair. + +Example of `REDISMODULE_HASH_CFIELD`: + + RedisModuleString *username, *hashedpass; + `RedisModule_HashGet(mykey`,"username",&username,"hp",&hashedpass, NULL); + +Example of `REDISMODULE_HASH_EXISTS`: + + int exists; + `RedisModule_HashGet(mykey`,argv[1],&exists,NULL); + +The function returns `REDISMODULE_OK` on success and `REDISMODULE_ERR` if +the key is not an hash value. + +Memory management: + +The returned RedisModuleString objects should be released with +`RedisModule_FreeString()`, or by enabling automatic memory management. + +## `RM_FreeCallReply_Rec` + + void RM_FreeCallReply_Rec(RedisModuleCallReply *reply, int freenested); + +Free a Call reply and all the nested replies it contains if it's an +array. + +## `RM_FreeCallReply` + + void RM_FreeCallReply(RedisModuleCallReply *reply); + +Wrapper for the recursive free reply function. This is needed in order +to have the first level function to return on nested replies, but only +if called by the module API. + +## `RM_CallReplyType` + + int RM_CallReplyType(RedisModuleCallReply *reply); + +Return the reply type. + +## `RM_CallReplyLength` + + size_t RM_CallReplyLength(RedisModuleCallReply *reply); + +Return the reply type length, where applicable. + +## `RM_CallReplyArrayElement` + + RedisModuleCallReply *RM_CallReplyArrayElement(RedisModuleCallReply *reply, size_t idx); + +Return the 'idx'-th nested call reply element of an array reply, or NULL +if the reply type is wrong or the index is out of range. + +## `RM_CallReplyInteger` + + long long RM_CallReplyInteger(RedisModuleCallReply *reply); + +Return the long long of an integer reply. + +## `RM_CallReplyStringPtr` + + const char *RM_CallReplyStringPtr(RedisModuleCallReply *reply, size_t *len); + +Return the pointer and length of a string or error reply. + +## `RM_CreateStringFromCallReply` + + RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply); + +Return a new string object from a call reply of type string, error or +integer. Otherwise (wrong reply type) return NULL. + +## `RM_Call` + + RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); + +Exported API to call any Redis command from modules. +On success a RedisModuleCallReply object is returned, otherwise +NULL is returned and errno is set to the following values: + +EINVAL: command non existing, wrong arity, wrong format specifier. +EPERM: operation in Cluster instance with key in non local slot. + +## `RM_CallReplyProto` + + const char *RM_CallReplyProto(RedisModuleCallReply *reply, size_t *len); + +Return a pointer, and a length, to the protocol returned by the command +that returned the reply object. diff --git a/src/modules/INTRO.md b/src/modules/INTRO.md new file mode 100644 index 00000000..c64a5007 --- /dev/null +++ b/src/modules/INTRO.md @@ -0,0 +1,786 @@ +Redis Modules API reference manual +=== + +Redis modules make possible to extend Redis functionality using external +modules, implementing new Redis commands at a speed and with features +similar to what can be done inside the core itself. + +Redis modules are dynamic libraries, that can be loaded into Redis at +startup or using the `MODULE LOAD` command. Redis exports a C API, in the +form of a single C header file called `redismodule.h`. Modules are meant +to be written in C, however it will be possible to use C++ or other languages +that have C binding functionalities. + +Modules are designed in order to be loaded into different versions of Redis, +so a given module does not need to be designed, or recompiled, in order to +run with a specific version of Redis. For this reason, the module will +register to the Redis core using a specific API version. The current API +version is "1". + +This document is about an alpha version of Redis modules. API, functionalities +and other details may change in the future. + +# Loading modules + +In order to test the module you are developing, you can load the module +using the following `redis.conf` configuration directive: + + loadmodule /path/to/mymodule.so + +It is also possible to load a module at runtime using the following command: + + MODULE LOAD /path/to/mymodule.so + +In order to list all loaded modules, use: + + MODULE LIST + +Finally, you can unload (and later reload if you wish) a module using the +following command: + + MODULE UNLOAD mymodule + +Note that `mymodule` above is not the filename without the `.so` suffix, but +instead, the name the module used to register itself into the Redis core. +The name can be obtained using `MODULE LIST`. However it is good practice +that the filename of the dynamic library is the same as the name the module +uses to register itself into the Redis core. + +# The simplest module you can write + +In order to show the different parts of a module, here we'll show a very +simple module that implements a command that outputs a random number. + + #include "redismodule.h" + #include + + int HelloworldRand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + RedisModule_ReplyWithLongLong(ctx,rand()); + return REDISMODULE_OK; + } + + int RedisModule_OnLoad(RedisModuleCtx *ctx) { + if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1) + == REDISMODULE_ERR) return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"helloworld.rand", + HelloworldRand_RedisCommand) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; + } + +The example module has two functions. One implements a command called +HELLOWORLD.RAND. This function is specific of that module. However the +other function called `RedisModule_OnLoad()` must be present in each +Redis module. It is the entry point for the module to be initialized, +register its commands, and potentially other private data structures +it uses. + +Note that it is a good idea for modules to call commands with the +name of the module followed by a dot, and finally the command name, +like in the case of `HELLOWORLD.RAND`. This way it is less likely to +have collisions. + +Note that if different modules have colliding commands, they'll not be +able to work in Redis at the same time, since the function +`RedisModule_CreateCommand` will fail in one of the modules, so the module +loading will abort returning an error condition. + +# Module initialization + +The above example shows the usage of the function `RedisModule_Init()`. +It should be the first function called by the module `OnLoad` function. +The following is the function prototype: + + int RedisModule_Init(RedisModuleCtx *ctx, const char *modulename, + int module_version, int api_version); + +The `Init` function announces the Redis core that the module has a given +name, its version (that is reported by `MODULE LIST`), and that is willing +to use a specific version of the API. + +If the API version is wrong, the name is already taken, or there are other +similar errors, the function will return `REDISMODULE_ERR`, and the module +`OnLoad` function should return ASAP with an error. + +Before the `Init` function is called, no other API function can be called, +otherwise the module will segfault and the Redis instance will crash. + +The second function called, `RedisModule_CreateCommand`, is used in order +to register commands into the Redis core. The following is the prototype: + + int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *cmdname, + RedisModuleCmdFunc cmdfunc); + +As you can see, most Redis modules API calls all take as first argument +the `context` of the module, so that they have a reference to the module +calling it, to the command and client executing a given command, and so forth. + +To create a new command, the above function needs the context, the command +name, and the function pointer of the function implementing the command, +which must have the following prototype: + + + int mycommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); + +The command function arguments are just the context, that will be passed +to all the other API calls, the command argument vector, and total number +of arguments, as passed by the user. + +As you can see, the arguments are provided as pointers to a specific data +type, the `RedisModuleString`. This is an opaque data type you have API +functions to access and use, direct access to its fields is never needed. + +Zooming into the example command implementation, we can find another call: + + int RedisModule_ReplyWithLongLong(RedisModuleCtx *ctx, long long integer); + +This function returns an integer to the client that invoked the command, +exactly like other Redis commands do, like for example `INCR` or `SCARD`. + +# Setup and dependencies of a Redis module + +Redis modules don't depend on Redis or some other library, nor they +need to be compiled with a specific `redismodule.h` file. In order +to create a new module, just copy a recent version of `redismodule.h` +in your source tree, link all the libraries you want, and create +a dynamic library having the `RedisModule_OnLoad()` function symbol +exported. + +The module will be able to load into different versions of Redis. + +# Working with RedisModuleString objects + +The command argument vector `argv` passed to module commands, and the +return value of other module APIs functions, are of type `RedisModuleString`. + +Usually you directly pass module strings to other API calls, however sometimes +you may need to directly access the string object. + +There are a few functions in order to work with string objects: + + const char *RedisModule_StringPtrLen(RedisModuleString *string, size_t *len); + +The above function accesses a string by returning its pointer and setting its +length in `len`. +You should never write to a string object pointer, as you can see from the +`const` pointer qualifier. + +However, if you want, you can create new string objects using the following +API: + + RedisModuleString *RedisModule_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len); + +The string returned by the above command must be freed using a corresponding +call to `RedisModule_FreeString()`: + + void RedisModule_FreeString(RedisModuleString *str); + +However if you want to avoid having to free strings, the automatic memory +management, covered later in this document, can be a good alternative, by +doing it for you. + +Note that the strings provided via the argument vector `argv` never need +to be freed. You only need to free new strings you create, or new strings +returned by other APIs, where it is specified that the returned string must +be freed. + +## Creating strings from numbers or parsing strings as numbers + +Creating a new string from an integer is a very common operation, so there +is a function to do this: + + RedisModuleString *mystr = RedisModule_CreateStringFromLongLong(ctx,10); + +Similarly in order to parse a string as a number: + + long long myval; + if (RedisModule_StringToLongLong(ctx,argv[1],&myval) == REDISMODULE_OK) { + /* Do something with 'myval' */ + } + +## Accessing Redis keys from modules + +Most Redis modules, in order to be useful, have to interact with the Redis +data space (this is not always true, for example an ID generator may +never touch Redis keys). Redis modules have two different APIs in order to +access the Redis data space, one is a low level API that provides very +fast access and a set of functions to manipulate Redis data structures. +The other API is more high level, and allows to call Redis commands and +fetch the result, similarly to how Lua scripts access Redis. + +The high level API is also useful in order to access Redis functionalities +that are not available as APIs. + +In general modules developers should prefer the low level API, because commands +implemented using the low level API run at a speed comparable to the speed +of native Redis commands. However there are definitely use cases for the +higher level API. For example often the bottleneck could be processing the +data and not accessing it. + +Also note that sometimes using the low level API is not harder compared to +the higher level one. + +# Calling Redis commands + +The high level API to access Redis is the sum of the `RedisModule_Call()` +function, together with the functions needed in order to access the +reply object returned by `Call()`. + +`RedisModule_Call` uses a special calling convention, with a format specifier +that is used to specify what kind of objects you are passing as arguments +to the function. + +Redis commands are invoked just using a command name and a list of arguments. +However when calling commands, the arguments may originate from different +kind of strings: null-terminated C strings, RedisModuleString objects as +received from the `argv` parameter in the command implementation, binary +safe C buffers with a pointer and a length, and so forth. + +For example if I want to call `INCRBY` using a first argument (the key) +a string received in the argument vector `argv`, which is an array +of RedisModuleString object pointers, and a C string representing the +number "10" as second argument (the increment), I'll use the following +function call: + + RedisModuleCallReply *reply; + reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10"); + +The first argument is the context, and the second is always a null terminated +C string with the command name. The third argument is the format specifier +where each character corresponds to the type of the arguments that will follow. +In the above case `"sc"` means a RedisModuleString object, and a null +terminated C string. The other arguments are just the two arguments as +specified. In fact `argv[1]` is a RedisModuleString and `"10"` is a null +terminated C string. + +This is the full list of format specifiers: + +* **c** -- Null terminated C string pointer. +* **b** -- C buffer, two arguments needed: C string pointer and `size_t` length. +* **s** -- RedisModuleString as received in `argv` or by other Redis module APIs returning a RedisModuleString object. +* **l** -- Long long integer. +* **v** -- Array of RedisModuleString objects. +* **!** -- This modifier just tells the function to replicate the command to slaves and AOF. It is ignored from the point of view of arguments parsing. + +The function returns a `RedisModuleCallReply` object on success, on +error NULL is returned. + +NULL is returned when the command name is invalid, the format specifier uses +characters that are not recognized, or when the command is called with the +wrong number of arguments. In the above cases the `errno` var is set to `EINVAL`. NULL is also returned when, in an instance with Cluster enabled, the target +keys are about non local hash slots. In this case `errno` is set to `EPERM`. + +## Working with RedisModuleCallReply objects. + +`RedisModuleCall` returns reply objects that can be accessed using the +`RedisModule_CallReply*` family of functions. + +In order to obtain the type or reply (corresponding to one of the data types +supported by the Redis protocol), the function `RedisModule_CallReplyType()` +is used: + + reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10"); + if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) { + long long myval = RedisModule_CallReplyInteger(reply); + /* Do something with myval. */ + } + +Valid reply types are: + +* `REDISMODULE_REPLY_STRING` Bulk string or status replies. +* `REDISMODULE_REPLY_ERROR` Errors. +* `REDISMODULE_REPLY_INTEGER` Signed 64 bit integers. +* `REDISMODULE_REPLY_ARRAY` Array of replies. +* `REDISMODULE_REPLY_NULL` NULL reply. + +Strings, errors and arrays have an associated length. For strings and errors +the length corresponds to the length of the string. For arrays the length +is the number of elements. To obtain the reply length the following function +is used: + + size_t reply_len = RedisModule_CallReplyLength(reply); + +In order to obtain the value of an integer reply, the following function is used, as already shown in the example above: + + long long reply_integer_val = RedisModule_CallReplyInteger(reply); + +Called with a reply object of the wrong type, the above function always +returns `LLONG_MIN`. + +Sub elements of array replies are accessed this way: + + RedisModuleCallReply *subreply; + subreply = RedisModule_CallReplyArrayElement(reply,idx); + +The above function returns NULL if you try to access out of range elements. + +Strings and errors (which are like strings but with a different type) can +be accessed using in the following way, making sure to never write to +the resulting pointer (that is returned as as `const` pointer so that +misusing must be pretty explicit): + + size_t len; + char *ptr = RedisModule_CallReplyStringPtr(reply,&len); + +If the reply type is not a string or an error, NULL is returned. + +RedisCallReply objects are not the same as module string objects +(RedisModuleString types). However sometimes you may need to pass replies +of type string or integer, to API functions expecting a module string. + +When this is the case, you may want to evaluate if using the low level +API could be a simpler way to implement your command, or you can use +the following function in order to create a new string object from a +call reply of type string, error or integer: + + RedisModuleString *mystr = RedisModule_CreateStringFromCallReply(myreply); + +If the reply is not of the right type, NULL is returned. +The returned string object should be released with `RedisModule_FreeString()` +as usually, or by enabling automatic memory management (see corresponding +section). + +# Releasing call reply objects + +Reply objects must be freed using `RedisModule_FreeCallRelpy`. For arrays, +you need to free only the top level reply, not the nested replies. +Currently the module implementation provides a protection in order to avoid +crashing if you free a nested reply object for error, however this feature +is not guaranteed to be here forever, so should not be considered part +of the API. + +If you use automatic memory management (explained later in this document) +you don't need to free replies (but you still could if you wish to release +memory ASAP). + +## Returning values from Redis commands + +Like normal Redis commands, new commands implemented via modules must be +able to return values to the caller. The API exports a set of functions for +this goal, in order to return the usual types of the Redis protocol, and +arrays of such types as elemented. Also errors can be returned with any +error string and code (the error code is the initial uppercase letters in +the error message, like the "BUSY" string in the "BUSY the sever is busy" error +message). + +All the functions to send a reply to the client are called +`RedisModule_ReplyWith`. + +To return an error, use: + + RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *err); + +There is a predefined error string for key of wrong type errors: + + REDISMODULE_ERRORMSG_WRONGTYPE + +Example usage: + + RedisModule_ReplyWithError(ctx,"ERR invalid arguments"); + +We already saw how to reply with a long long in the examples above: + + RedisModule_ReplyWithLongLong(ctx,12345); + +To reply with a simple string, that can't contain binary values or newlines, +(so it's suitable to send small words, like "OK") we use: + + RedisModule_ReplyWithSimpleString(ctx,"OK"); + +It's possible to reply with "bulk strings" that are binary safe, using +two different functions: + + int RedisModule_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len); + + int RedisModule_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str); + +The first function gets a C pointer and length. The second a RedisMoudleString +object. Use one or the other depending on the source type you have at hand. + +In order to reply with an array, you just need to use a function to emit the +array length, followed by as many calls to the above functions as the number +of elements of the array are: + + RedisModule_ReplyWithArray(ctx,2); + RedisModule_ReplyWithStringBuffer(ctx,"age",3); + RedisModule_ReplyWithLongLong(ctx,22); + +To return nested arrays is easy, your nested array element just uses another +call to `RedisModule_ReplyWithArray()` followed by the calls to emit the +sub array elements. + +## Returning arrays with dynamic length + +Sometimes it is not possible to know beforehand the number of items of +an array. As an example, think of a Redis module implementing a FACTOR +command that given a number outputs the prime factors. Instead of +factorializing the number, storing the prime factors into an array, and +later produce the command reply, a better solution is to start an array +reply where the length is not known, and set it later. This is accomplished +with a special argument to `RedisModule_ReplyWithArray()`: + + RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); + +The above call starts an array reply so we can use other `ReplyWith` calls +in order to produce the array items. Finally in order to set the length +se use the following call: + + RedisModule_ReplySetArrayLength(ctx, number_of_items); + +In the case of the FACTOR command, this translates to some code similar +to this: + + RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); + number_of_factors = 0; + while(still_factors) { + RedisModule_ReplyWithLongLong(ctx, some_factor); + number_of_factors++; + } + RedisModule_ReplySetArrayLength(ctx, number_of_factors); + +Another common use case for this feature is iterating over the arrays of +some collection and only returning the ones passing some kind of filtering. + +It is possible to have multiple nested arrays with postponed reply. +Each call to `SetArray()` will set the length of the latest corresponding +call to `ReplyWithArray()`: + + RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); + ... generate 100 elements ... + RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); + ... generate 10 elements ... + RedisModule_ReplySetArrayLength(ctx, 10); + RedisModule_ReplySetArrayLength(ctx, 100); + +This creates a 100 items array having as last element a 10 items array. + +# Arity and type checks + +Often commands need to check that the number of arguments and type of the key +is correct. In order to report a wrong arity, there is a specific function +called `RedisModule_WrongArity()`. The usage is trivial: + + if (argc != 2) return RedisModule_WrongArity(ctx); + +Checking for the wrong type involves opening the key and checking the type: + + RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], + REDISMODULE_READ|REDISMODULE_WRITE); + + int keytype = RedisModule_KeyType(key); + if (keytype != REDISMODULE_KEYTYPE_STRING && + keytype != REDISMODULE_KEYTYPE_EMPTY) + { + RedisModule_CloseKey(key); + return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); + } + +Note that you often want to proceed with a command both if the key +is of the expected type, or if it's empty. + +## Low level access to keys + +Low level access to keys allow to perform operations on value objects associated +to keys directly, with a speed similar to what Redis uses internally to +implement the built-in commands. + +Once a key is opened, a key pointer is returned that will be used with all the +other low level API calls in order to perform operations on the key or its +associated value. + +Because the API is meant to be very fast, it cannot do too many run-time +checks, so the user must be aware of certain rules to follow: + +* Opening the same key multiple times where at least one instance is opened for writing, is undefined and may lead to crashes. +* While a key is open, it should only be accessed via the low level key API. For example opening a key, then calling DEL on the same key using the `RedisModule_Call()` API will result into a crash. However it is safe to open a key, perform some operation with the low level API, closing it, then using other APIs to manage the same key, and later opening it again to do some more work. + +In order to open a key the `RedisModule_OpenKey` function is used. It returns +a key pointer, that we'll use with all the next calls to access and modify +the value: + + RedisModuleKey *key; + key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ); + +The second argument is the key name, that must be a `RedisModuleString` object. +The third argument is the mode: `REDISMODULE_READ` or `REDISMODULE_WRITE`. +It is possible to use `|` to bitwise OR the two modes to open the key in +both modes. Currently a key opened for writing can also be accessed for reading +but this is to be considered an implementation detail. The right mode should +be used in sane modules. + +You can open non exisitng keys for writing, since the keys will be created +when an attempt to write to the key is performed. However when opening keys +just for reading, `RedisModule_OpenKey` will return NULL if the key does not +exist. + +Once you are done using a key, you can close it with: + + RedisModule_CloseKey(key); + +Note that if automatic memory management is enabled, you are not forced to +close keys. When the module function returns, Redis will take care to close +all the keys which are still open. + +## Getting the key type + +In order to obtain the value of a key, use the `RedisModule_KeyType()` function: + + int keytype = RedisModule_KeyType(key); + +It returns one of the following values: + + REDISMODULE_KEYTYPE_EMPTY + REDISMODULE_KEYTYPE_STRING + REDISMODULE_KEYTYPE_LIST + REDISMODULE_KEYTYPE_HASH + REDISMODULE_KEYTYPE_SET + REDISMODULE_KEYTYPE_ZSET + +The above are just the usual Redis key types, with the addition of an empty +type, that signals the key pointer is associated with an empty key that +does not yet exists. + +## Creating new keys + +To create a new key, open it for writing and then write to it using one +of the key writing functions. Example: + + RedisModuleKey *key; + key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ); + if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) { + RedisModule_StringSet(key,argv[2]); + } + +## Deleting keys + +Just use: + + RedisModule_DeleteKey(key); + +The function returns `REDISMODULE_ERR` if the key is not open for writing. +Note that after a key gets deleted, it is setup in order to be targeted +by new key commands. For example `RedisModule_KeyType()` will return it is +an empty key, and writing to it will create a new key, possibly of another +type (depending on the API used). + +## Managing key expires (TTLs) + +To control key expires two functions are provided, that are able to set, +modify, get, and unset the time to live associated with a key. + +One function is used in order to query the current expire of an open key: + + mstime_t RedisModule_GetExpire(RedisModuleKey *key); + +The function returns the time to live of the key in milliseconds, or +`REDISMODULE_NO_EXPIRE` as a special value to signal the key has no associated +expire or does not exist at all (you can differentiate the two cases checking +if the key type is `REDISMODULE_KEYTYPE_EMPTY`). + +In order to change the expire of a key the following function is used instead: + + int RedisModule_SetExpire(RedisModuleKey *key, mstime_t expire); + +When called on a non existing key, `REDISMODULE_ERR` is returned, because +the function can only associate expires to existing open keys (non existing +open keys are only useful in order to create new values with data type +specific write operations). + +Again the `expire` time is specified in milliseconds. If the key has currently +no expire, a new expire is set. If the key already have an expire, it is +replaced with the new value. + +If the key has an expire, and the special value `REDISMODULE_NO_EXPIRE` is +used as a new expire, the expire is removed, similarly to the Redis +`PERSIST` command. In case the key was already persistent, no operation is +performed. + +## Obtaining the length of values + +There is a single function in order to retrieve the length of the value +associated to an open key. The returned length is value-specific, and is +the string length for strings, and the number of elements for the aggregated +data types (how many elements there is in a list, set, sorted set, hash). + + size_t len = RedisModule_ValueLength(key); + +If the key does not exist, 0 is returned by the function: + +## String type API + +Setting a new string value, like the Redis `SET` command does, is performed +using: + + int RedisModule_StringSet(RedisModuleKey *key, RedisModuleString *str); + +The function works exactly like the Redis `SET` command itself, that is, if +there is a prior value (of any type) it will be deleted. + +Accessing existing string values is performed using DMA (direct memory +access) for speed. The API will return a pointer and a length, so that's +possible to access and, if needed, modify the string directly. + + size_t len, j; + char *myptr = RedisModule_StringDMA(key,REDISMODULE_WRITE,&len); + for (j = 0; j < len; j++) myptr[j] = 'A'; + +In the above example we write directly on the string. Note that if you want +to write, you must be sure to ask for `WRITE` mode. + +DMA pointers are only valid if no other operations are performed with the key +before using the pointer, after the DMA call. + +Sometimes when we want to manipulate strings directly, we need to change +their size as well. For this scope, the `RedisModule_StringTruncate` function +is used. Example: + + RedisModule_StringTruncate(mykey,1024); + +The function truncates, or enlarges the string as needed, padding it with +zero bytes if the previos length is smaller than the new length we request. +If the string does not exist since `key` is associated to an open empty key, +a string value is created and associated to the key. + +Note that every time `StringTruncate()` is called, we need to re-obtain +the DMA pointer again, since the old may be invalid. + +## List type API + +It's possible to push and pop values from list values: + + int RedisModule_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele); + RedisModuleString *RedisModule_ListPop(RedisModuleKey *key, int where); + +In both the APIs the `where` argument specifies if to push or pop from tail +or head, using the following macros: + + REDISMODULE_LIST_HEAD + REDISMODULE_LIST_TAIL + +Elements returned by `RedisModule_ListPop()` are like strings craeted with +`RedisModule_CreateString()`, they must be released with +`RedisModule_FreeString()` or by enabling automatic memory management. + +## Set type API + +Work in progress. + +## Sorted set type API + +Documentation missing, please refer to the top comments inside `module.c` +for the following functions: + +* `RedisModule_ZsetAdd` +* `RedisModule_ZsetIncrby` +* `RedisModule_ZsetScore` +* `RedisModule_ZsetRem` + +And for the sorted set iterator: + +* `RedisModule_ZsetRangeStop` +* `RedisModule_ZsetFirstInScoreRange` +* `RedisModule_ZsetLastInScoreRange` +* `RedisModule_ZsetFirstInLexRange` +* `RedisModule_ZsetLastInLexRange` +* `RedisModule_ZsetRangeCurrentElement` +* `RedisModule_ZsetRangeNext` +* `RedisModule_ZsetRangePrev` +* `RedisModule_ZsetRangeEndReached` + +## Hash type API + +Documentation missing, please refer to the top comments inside `module.c` +for the following functions: + +* `RedisModule_HashSet` +* `RedisModule_HashGet` + +## Iterating aggregated values + +Work in progress. + +# Replicating commands + +If you want to use module commands exactly like normal Redis commands, in the +context of replicated Redis instances, or using the AOF file for persistence, +it is important for module commands to handle their replication in a consistent +way. + +When using the higher level APIs to invoke commands, replication happens +automatically if you use the "!" modifier in the format string of +`RedisModule_Call()` as in the following example: + + reply = RedisModule_Call(ctx,"INCR","!sc",argv[1],"10"); + +As you can see the format specifier is `"!sc"`. The bang is not parsed as a +format specifier, but it internally flags the command as "must replicate". + +If you use the above programming style, there are no problems. +However sometimes things are more complex than that, and you use the low level +API. In this case, if there are no side effects in the command execution, and +it consistently always performs the same work, what is possible to do is to +replicate the command verbatim as the user executed it. To do that, you just +need to call the following function: + + RedisModule_ReplicateVerbatim(ctx); + +When you use the above API, you should not use any other replication function +since they are not guaranteed to mix well. + +However this is not the only option. It's also possible to exactly tell +Redis what commands to replicate as the effect of the command execution, using +an API similar to `RedisModule_Call()` but that instead of calling the command +sends it to the AOF / slaves stream. Example: + + RedisModule_Replicate(ctx,"INCRBY","cl","foo",my_increment); + +It's possible to call `RedisModule_Replicate` multiple times, and each +will emit a command. All the sequence emitted is wrapped between a +`MULTI/EXEC` transaction, so that the AOF and replication effects are the +same as executing a single command. + +Note that `Call()` replication and `Replicate()` replication have a rule, +in case you want to mix both forms of replication (not necessarily a good +idea if there are simpler approaches). Commands replicated with `Call()` +are always the first emitted in the final `MULTI/EXEC` block, while all +the commands emitted with `Replicate()` will follow. + +# Automatic memory management + +Normally when writing programs in the C language, programmers need to manage +memory manually. This is why the Redis modules API has functions to release +strings, close open keys, free replies, and so forth. + +However given that commands are executed in a contained environment and +with a set of strict APIs, Redis is able to provide automatic memory management +to modules, at the cost of some performance (most of the time, a very low +cost). + +When automatic memory management is enabled: + +1. You don't need to close open keys. +2. You don't need to free replies. +3. You don't need to free RedisModuleString objects. + +However you can still do it, if you want. For example, automatic memory +management may be active, but inside a loop allocating a lot of strings, +you may still want to free strings no longer used. + +In order to enable automatic memory management, just call the following +function at the start of the command implementation: + + RedisModule_AutoMemory(ctx); + +Automatic memory management is usually the way to go, however experienced +C programmers may not use it in order to gain some speed and memory usage +benefit. + +# Writing commands compatible with Redis Cluster + +Documentation missing, please check the following functions inside `module.c`: + + RedisModule_IsKeysPositionRequest(ctx); + RedisModule_KeyAtPos(ctx,pos); + diff --git a/src/modules/gendoc.rb b/src/modules/gendoc.rb index 6fd687c3..b3dbf1ca 100644 --- a/src/modules/gendoc.rb +++ b/src/modules/gendoc.rb @@ -6,6 +6,11 @@ def markdown(s) s = s.gsub(/\*\/$/,"") s = s.gsub(/^ \* {0,1}/,"") s = s.gsub(/^\/\* /,"") + if s[0] != ' ' + s = s.gsub(/RM_[A-z()]+/){|x| "`#{x}`"} + s = s.gsub(/RedisModule_[A-z()]+/){|x| "`#{x}`"} + s = s.gsub(/REDISMODULE_[A-z]+/){|x| "`#{x}`"} + end s.chop! while s[-1] == "\n" || s[-1] == " " return s end @@ -15,7 +20,7 @@ end def docufy(src,i) m = /RM_[A-z0-9]+/.match(src[i]) proto = src[i].sub("{","").strip+";\n" - puts "## #{m[0]}\n\n" + puts "## `#{m[0]}`\n\n" puts " #{proto}\n" comment = "" while true