mirror of
http://github.com/valkey-io/valkey
synced 2024-11-22 18:54:58 +00:00
Modules: first preview 31 March 2016.
This commit is contained in:
parent
3b644e82b0
commit
6dead2cff5
@ -35,6 +35,14 @@
|
||||
# include /path/to/local.conf
|
||||
# include /path/to/other.conf
|
||||
|
||||
################################## MODULES #####################################
|
||||
|
||||
# Load modules at startup. If the server is not able to load modules
|
||||
# it will abort. It is possible to use multiple loadmodule directives.
|
||||
#
|
||||
# loadmodule /path/to/my_module.so
|
||||
# loadmodule /path/to/other_module.so
|
||||
|
||||
################################## NETWORK #####################################
|
||||
|
||||
# By default, if no "bind" configuration directive is specified, Redis listens
|
||||
|
@ -117,7 +117,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o geo.o lazyfree.o
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o geo.o lazyfree.o module.o
|
||||
REDIS_GEOHASH_OBJ=../deps/geohash-int/geohash.o ../deps/geohash-int/geohash_helper.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
|
||||
|
@ -632,6 +632,8 @@ void loadServerConfigFromString(char *config) {
|
||||
"Allowed values: 'upstart', 'systemd', 'auto', or 'no'";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"loadmodule") && argc == 2) {
|
||||
listAddNodeTail(server.loadmodule_queue,sdsnew(argv[1]));
|
||||
} else if (!strcasecmp(argv[0],"sentinel")) {
|
||||
/* argc == 1 is handled by main() as we need to enter the sentinel
|
||||
* mode ASAP. */
|
||||
|
1384
src/module.c
Normal file
1384
src/module.c
Normal file
File diff suppressed because it is too large
Load Diff
2
src/modules/.gitignore
vendored
Normal file
2
src/modules/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.so
|
||||
*.xo
|
695
src/modules/API.md
Normal file
695
src/modules/API.md
Normal file
@ -0,0 +1,695 @@
|
||||
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 <stdlib.h>
|
||||
|
||||
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_StringPtr(RedisModuleString *string, size_t *len);
|
||||
|
||||
The above function accesses a string by returning its pointer and length.
|
||||
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** -- NOT YET IMPLEMENTED: 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<something>`.
|
||||
|
||||
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.
|
||||
|
||||
# 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).
|
||||
|
||||
## 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
|
||||
|
||||
Work in progress.
|
||||
|
||||
## Hash type API
|
||||
|
||||
Work in progress.
|
||||
|
||||
## Iterating aggregated values
|
||||
|
||||
Work in progress.
|
||||
|
||||
## Accessing keys TTL and setting expires
|
||||
|
||||
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
|
||||
|
||||
Work in progress. Implement and document the following API:
|
||||
|
||||
RedisModule_IsKeysPositionRequest(ctx);
|
||||
RedisModule_KeyAtPos(ctx,pos);
|
||||
RedisModule_KeyAtRange(ctx,start,stop,step);
|
||||
|
||||
Command implementations, on keys position request, must reply with
|
||||
`REDISMODULE_KEYPOS_OK` to signal the request was processed, otherwise
|
||||
Cluster returns an error for those module commands that are not able to
|
||||
describe the position of keys.
|
17
src/modules/Makefile
Normal file
17
src/modules/Makefile
Normal file
@ -0,0 +1,17 @@
|
||||
SHOBJ_CFLAGS ?= -dynamic -fno-common -g -ggdb
|
||||
SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: helloworld.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
|
||||
helloworld.xo: ../redismodule.h
|
||||
|
||||
helloworld.so: helloworld.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
clean:
|
||||
rm -rf *.xo *.so
|
334
src/modules/helloworld.c
Normal file
334
src/modules/helloworld.c
Normal file
@ -0,0 +1,334 @@
|
||||
#include "../redismodule.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* HELLO.SIMPLE is among the simplest commands you can implement.
|
||||
* It just returns the currently selected DB id, a functionality which is
|
||||
* missing in Redis. The command uses two important API calls: one to
|
||||
* fetch the currently selected DB, the other in order to send the client
|
||||
* an integer reply as response. */
|
||||
int HelloSimple_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_ReplyWithLongLong(ctx,RedisModule_GetSelectedDb(ctx));
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* HELLO.PUSH.NATIVE re-implements RPUSH, and shows the low level modules API
|
||||
* where you can "open" keys, make low level operations, create new keys by
|
||||
* pushing elements into non-existing keys, and so forth.
|
||||
*
|
||||
* You'll find this command to be roughly as fast as the actual RPUSH
|
||||
* command. */
|
||||
int HelloPushNative_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 3) return RedisModule_WrongArity(ctx);
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
|
||||
REDISMODULE_READ|REDISMODULE_WRITE);
|
||||
|
||||
RedisModule_ListPush(key,REDISMODULE_LIST_TAIL,argv[2]);
|
||||
size_t newlen = RedisModule_ValueLength(key);
|
||||
RedisModule_CloseKey(key);
|
||||
RedisModule_ReplyWithLongLong(ctx,newlen);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* HELLO.PUSH.CALL implements RPUSH using an higher level approach, calling
|
||||
* a Redis command instead of working with the key in a low level way. This
|
||||
* approach is useful when you need to call Redis commands that are not
|
||||
* available as low level APIs, or when you don't need the maximum speed
|
||||
* possible but instead prefer implementation simplicity. */
|
||||
int HelloPushCall_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 3) return RedisModule_WrongArity(ctx);
|
||||
|
||||
RedisModuleCallReply *reply;
|
||||
|
||||
reply = RedisModule_Call(ctx,"RPUSH","ss",argv[1],argv[2]);
|
||||
long long len = RedisModule_CallReplyInteger(reply);
|
||||
RedisModule_FreeCallReply(reply);
|
||||
RedisModule_ReplyWithLongLong(ctx,len);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* HELLO.LIST.SUM.LEN returns the total length of all the items inside
|
||||
* a Redis list, by using the high level Call() API.
|
||||
* This command is an example of the array reply access. */
|
||||
int HelloListSumLen_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
|
||||
RedisModuleCallReply *reply;
|
||||
|
||||
reply = RedisModule_Call(ctx,"LRANGE","sll",argv[1],(long long)0,(long long)-1);
|
||||
size_t strlen = 0;
|
||||
size_t items = RedisModule_CallReplyLength(reply);
|
||||
size_t j;
|
||||
for (j = 0; j < items; j++) {
|
||||
RedisModuleCallReply *ele = RedisModule_CallReplyArrayElement(reply,j);
|
||||
strlen += RedisModule_CallReplyLength(ele);
|
||||
}
|
||||
RedisModule_FreeCallReply(reply);
|
||||
RedisModule_ReplyWithLongLong(ctx,strlen);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* HELLO.LIST.SPLICE srclist dstlist count
|
||||
* Moves 'count' elements from the tail of 'srclist' to the head of
|
||||
* 'dstlist'. If less than count elements are available, it moves as much
|
||||
* elements as possible. */
|
||||
int HelloListSplice_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 4) return RedisModule_WrongArity(ctx);
|
||||
|
||||
RedisModuleKey *srckey = RedisModule_OpenKey(ctx,argv[1],
|
||||
REDISMODULE_READ|REDISMODULE_WRITE);
|
||||
RedisModuleKey *dstkey = RedisModule_OpenKey(ctx,argv[2],
|
||||
REDISMODULE_READ|REDISMODULE_WRITE);
|
||||
|
||||
/* Src and dst key must be empty or lists. */
|
||||
if ((RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_LIST &&
|
||||
RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_EMPTY) ||
|
||||
(RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_LIST &&
|
||||
RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_EMPTY))
|
||||
{
|
||||
RedisModule_CloseKey(srckey);
|
||||
RedisModule_CloseKey(dstkey);
|
||||
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||
}
|
||||
|
||||
long long count;
|
||||
if (RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK) {
|
||||
RedisModule_CloseKey(srckey);
|
||||
RedisModule_CloseKey(dstkey);
|
||||
return RedisModule_ReplyWithError(ctx,"ERR invalid count");
|
||||
}
|
||||
|
||||
while(count-- > 0) {
|
||||
RedisModuleString *ele;
|
||||
|
||||
ele = RedisModule_ListPop(srckey,REDISMODULE_LIST_TAIL);
|
||||
if (ele == NULL) break;
|
||||
RedisModule_ListPush(dstkey,REDISMODULE_LIST_HEAD,ele);
|
||||
RedisModule_FreeString(ctx,ele);
|
||||
}
|
||||
|
||||
size_t len = RedisModule_ValueLength(srckey);
|
||||
RedisModule_CloseKey(srckey);
|
||||
RedisModule_CloseKey(dstkey);
|
||||
RedisModule_ReplyWithLongLong(ctx,len);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Like the HELLO.LIST.SPLICE above, but uses automatic memory management
|
||||
* in order to avoid freeing stuff. */
|
||||
int HelloListSpliceAuto_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 4) return RedisModule_WrongArity(ctx);
|
||||
|
||||
RedisModule_AutoMemory(ctx);
|
||||
|
||||
RedisModuleKey *srckey = RedisModule_OpenKey(ctx,argv[1],
|
||||
REDISMODULE_READ|REDISMODULE_WRITE);
|
||||
RedisModuleKey *dstkey = RedisModule_OpenKey(ctx,argv[2],
|
||||
REDISMODULE_READ|REDISMODULE_WRITE);
|
||||
|
||||
/* Src and dst key must be empty or lists. */
|
||||
if ((RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_LIST &&
|
||||
RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_EMPTY) ||
|
||||
(RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_LIST &&
|
||||
RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_EMPTY))
|
||||
{
|
||||
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||
}
|
||||
|
||||
long long count;
|
||||
if (RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK)
|
||||
return RedisModule_ReplyWithError(ctx,"ERR invalid count");
|
||||
|
||||
while(count-- > 0) {
|
||||
RedisModuleString *ele;
|
||||
|
||||
ele = RedisModule_ListPop(srckey,REDISMODULE_LIST_TAIL);
|
||||
if (ele == NULL) break;
|
||||
RedisModule_ListPush(dstkey,REDISMODULE_LIST_HEAD,ele);
|
||||
}
|
||||
|
||||
size_t len = RedisModule_ValueLength(srckey);
|
||||
RedisModule_ReplyWithLongLong(ctx,len);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* HELLO.RAND.ARRAY <count>
|
||||
* Shows how to generate arrays as commands replies.
|
||||
* It just outputs <count> random numbers. */
|
||||
int HelloRandArray_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
long long count;
|
||||
if (RedisModule_StringToLongLong(argv[1],&count) != REDISMODULE_OK ||
|
||||
count < 0)
|
||||
return RedisModule_ReplyWithError(ctx,"ERR invalid count");
|
||||
|
||||
/* To reply with an array, we call RedisModule_ReplyWithArray() followed
|
||||
* by other "count" calls to other reply functions in order to generate
|
||||
* the elements of the array. */
|
||||
RedisModule_ReplyWithArray(ctx,count);
|
||||
while(count--) RedisModule_ReplyWithLongLong(ctx,rand());
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* This is a simple command to test replication. Because of the "!" modified
|
||||
* in the RedisModule_Call() call, the two INCRs get replicated.
|
||||
* Also note how the ECHO is replicated in an unexpected position (check
|
||||
* comments the function implementation). */
|
||||
int HelloRepl1_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
RedisModuleCallReply *reply;
|
||||
RedisModule_AutoMemory(ctx);
|
||||
|
||||
/* This will be replicated *after* the two INCR statements, since
|
||||
* the Call() replication has precedence, so the actual replication
|
||||
* stream will be:
|
||||
*
|
||||
* MULTI
|
||||
* INCR foo
|
||||
* INCR bar
|
||||
* ECHO c foo
|
||||
* EXEC
|
||||
*/
|
||||
RedisModule_Replicate(ctx,"ECHO","c","foo");
|
||||
|
||||
/* Using the "!" modifier we replicate the command if it
|
||||
* modified the dataset in some way. */
|
||||
reply = RedisModule_Call(ctx,"INCR","c!","foo");
|
||||
reply = RedisModule_Call(ctx,"INCR","c!","bar");
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx,0);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Another command to show replication. In this case, we call
|
||||
* RedisModule_ReplicateVerbatim() to mean we want just the command to be
|
||||
* propagated to slaves / AOF exactly as it was called by the user.
|
||||
*
|
||||
* This command also shows how to work with string objects.
|
||||
* It takes a list, and increments all the elements (that must have
|
||||
* a numerical value) by 1, returning the sum of all the elements
|
||||
* as reply.
|
||||
*
|
||||
* Usage: HELLO.REPL2 <list-key> */
|
||||
int HelloRepl2_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
|
||||
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
|
||||
REDISMODULE_READ|REDISMODULE_WRITE);
|
||||
|
||||
if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_LIST)
|
||||
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||
|
||||
size_t listlen = RedisModule_ValueLength(key);
|
||||
long long sum = 0;
|
||||
|
||||
/* Rotate and increment. */
|
||||
while(listlen--) {
|
||||
RedisModuleString *ele = RedisModule_ListPop(key,REDISMODULE_LIST_TAIL);
|
||||
long long val;
|
||||
if (RedisModule_StringToLongLong(ele,&val) != REDISMODULE_OK) val = 0;
|
||||
val++;
|
||||
sum += val;
|
||||
RedisModuleString *newele = RedisModule_CreateStringFromLongLong(ctx,val);
|
||||
RedisModule_ListPush(key,REDISMODULE_LIST_HEAD,newele);
|
||||
}
|
||||
RedisModule_ReplyWithLongLong(ctx,sum);
|
||||
RedisModule_ReplicateVerbatim(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* This is an example of strings DMA access. Given a key containing a string
|
||||
* it toggles the case of each character from lower to upper case or the
|
||||
* other way around.
|
||||
*
|
||||
* No automatic memory management is used in this example (for the sake
|
||||
* of variety).
|
||||
*
|
||||
* HELLO.TOGGLE.CASE key */
|
||||
int HelloToggleCase_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (keytype == REDISMODULE_KEYTYPE_STRING) {
|
||||
size_t len, j;
|
||||
char *s = RedisModule_StringDMA(key,&len,REDISMODULE_WRITE);
|
||||
for (j = 0; j < len; j++) {
|
||||
if (isupper(s[j])) {
|
||||
s[j] = tolower(s[j]);
|
||||
} else {
|
||||
s[j] = toupper(s[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RedisModule_CloseKey(key);
|
||||
RedisModule_ReplyWithSimpleString(ctx,"OK");
|
||||
RedisModule_ReplicateVerbatim(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* This function must be present on each Redis module. It is used in order to
|
||||
* register the commands into the Redis server. */
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx) {
|
||||
if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.simple",
|
||||
HelloSimple_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.push.native",
|
||||
HelloPushNative_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.push.call",
|
||||
HelloPushCall_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.list.sum.len",
|
||||
HelloListSumLen_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.list.splice",
|
||||
HelloListSplice_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.list.splice.auto",
|
||||
HelloListSpliceAuto_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.rand.array",
|
||||
HelloRandArray_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.repl1",
|
||||
HelloRepl1_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.repl2",
|
||||
HelloRepl2_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.toggle.case",
|
||||
HelloToggleCase_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
@ -158,7 +158,7 @@ client *createClient(int fd) {
|
||||
int prepareClientToWrite(client *c) {
|
||||
/* If it's the Lua client we always return ok without installing any
|
||||
* handler since there is no socket at all. */
|
||||
if (c->flags & CLIENT_LUA) return C_OK;
|
||||
if (c->flags & (CLIENT_LUA|CLIENT_MODULE)) return C_OK;
|
||||
|
||||
/* CLIENT REPLY OFF / SKIP handling: don't send replies. */
|
||||
if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;
|
||||
|
152
src/redismodule.h
Normal file
152
src/redismodule.h
Normal file
@ -0,0 +1,152 @@
|
||||
#ifndef REDISMODULE_H
|
||||
#define REDISMODULE_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
/* ---------------- Defines common between core and modules --------------- */
|
||||
|
||||
/* Error status return values. */
|
||||
#define REDISMODULE_OK 0
|
||||
#define REDISMODULE_ERR 1
|
||||
|
||||
/* API versions. */
|
||||
#define REDISMODULE_APIVER_1 1
|
||||
|
||||
/* API flags and constants */
|
||||
#define REDISMODULE_READ (1<<0)
|
||||
#define REDISMODULE_WRITE (1<<1)
|
||||
|
||||
#define REDISMODULE_LIST_HEAD 0
|
||||
#define REDISMODULE_LIST_TAIL 1
|
||||
|
||||
/* Key types. */
|
||||
#define REDISMODULE_KEYTYPE_EMPTY 0
|
||||
#define REDISMODULE_KEYTYPE_STRING 1
|
||||
#define REDISMODULE_KEYTYPE_LIST 2
|
||||
#define REDISMODULE_KEYTYPE_HASH 3
|
||||
#define REDISMODULE_KEYTYPE_SET 4
|
||||
#define REDISMODULE_KEYTYPE_ZSET 5
|
||||
|
||||
/* Reply types. */
|
||||
#define REDISMODULE_REPLY_UNKNOWN -1
|
||||
#define REDISMODULE_REPLY_STRING 0
|
||||
#define REDISMODULE_REPLY_ERROR 1
|
||||
#define REDISMODULE_REPLY_INTEGER 2
|
||||
#define REDISMODULE_REPLY_ARRAY 3
|
||||
#define REDISMODULE_REPLY_NULL 4
|
||||
|
||||
/* Error messages. */
|
||||
#define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value"
|
||||
|
||||
/* ------------------------- End of common defines ------------------------ */
|
||||
|
||||
#ifndef REDISMODULE_CORE
|
||||
|
||||
/* Incomplete structures for compiler checks but opaque access. */
|
||||
typedef struct RedisModuleCtx RedisModuleCtx;
|
||||
typedef struct RedisModuleKey RedisModuleKey;
|
||||
typedef struct RedisModuleString RedisModuleString;
|
||||
typedef struct RedisModuleCallReply RedisModuleCallReply;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
|
||||
#define REDISMODULE_GET_API(name) \
|
||||
RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name))
|
||||
|
||||
#define REDISMODULE_API_FUNC(x) (*x)
|
||||
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
|
||||
int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode);
|
||||
void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp);
|
||||
int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp);
|
||||
size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where);
|
||||
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply);
|
||||
long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply);
|
||||
size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply);
|
||||
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(RedisModuleString *str, size_t *len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, int len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(RedisModuleString *str, long long *ll);
|
||||
void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
|
||||
void *getapifuncptr = ((void**)ctx)[0];
|
||||
RedisModule_GetApi = (int (*)(const char *, void *)) getapifuncptr;
|
||||
REDISMODULE_GET_API(CreateCommand);
|
||||
REDISMODULE_GET_API(SetModuleAttribs);
|
||||
REDISMODULE_GET_API(WrongArity);
|
||||
REDISMODULE_GET_API(ReplyWithLongLong);
|
||||
REDISMODULE_GET_API(ReplyWithError);
|
||||
REDISMODULE_GET_API(ReplyWithSimpleString);
|
||||
REDISMODULE_GET_API(ReplyWithArray);
|
||||
REDISMODULE_GET_API(ReplyWithStringBuffer);
|
||||
REDISMODULE_GET_API(ReplyWithString);
|
||||
REDISMODULE_GET_API(GetSelectedDb);
|
||||
REDISMODULE_GET_API(SelectDb);
|
||||
REDISMODULE_GET_API(OpenKey);
|
||||
REDISMODULE_GET_API(CloseKey);
|
||||
REDISMODULE_GET_API(KeyType);
|
||||
REDISMODULE_GET_API(ValueLength);
|
||||
REDISMODULE_GET_API(ListPush);
|
||||
REDISMODULE_GET_API(ListPop);
|
||||
REDISMODULE_GET_API(StringToLongLong);
|
||||
REDISMODULE_GET_API(Call);
|
||||
REDISMODULE_GET_API(CallReplyProto);
|
||||
REDISMODULE_GET_API(FreeCallReply);
|
||||
REDISMODULE_GET_API(CallReplyInteger);
|
||||
REDISMODULE_GET_API(CallReplyType);
|
||||
REDISMODULE_GET_API(CallReplyLength);
|
||||
REDISMODULE_GET_API(CallReplyArrayElement);
|
||||
REDISMODULE_GET_API(CallReplyStringPtr);
|
||||
REDISMODULE_GET_API(CreateStringFromCallReply);
|
||||
REDISMODULE_GET_API(CreateString);
|
||||
REDISMODULE_GET_API(CreateStringFromLongLong);
|
||||
REDISMODULE_GET_API(FreeString);
|
||||
REDISMODULE_GET_API(StringPtrLen);
|
||||
REDISMODULE_GET_API(AutoMemory);
|
||||
REDISMODULE_GET_API(Replicate);
|
||||
REDISMODULE_GET_API(ReplicateVerbatim);
|
||||
REDISMODULE_GET_API(DeleteKey);
|
||||
REDISMODULE_GET_API(StringSet);
|
||||
REDISMODULE_GET_API(StringDMA);
|
||||
REDISMODULE_GET_API(StringTruncate);
|
||||
|
||||
RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* Things only defined for the modules core, not exported to modules
|
||||
* including this file. */
|
||||
#define RedisModuleString robj
|
||||
|
||||
#endif /* REDISMODULE_CORE */
|
||||
#endif /* REDISMOUDLE_H */
|
17
src/server.c
17
src/server.c
@ -123,6 +123,7 @@ struct redisServer server; /* server global state */
|
||||
* are not fast commands.
|
||||
*/
|
||||
struct redisCommand redisCommandTable[] = {
|
||||
{"module",moduleCommand,-2,"as",0,NULL,1,1,1,0,0},
|
||||
{"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
@ -648,6 +649,18 @@ dictType clusterNodesBlackListDictType = {
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* Cluster re-addition blacklist. This maps node IDs to the time
|
||||
* we can re-add this node. The goal is to avoid readding a removed
|
||||
* node for some time. */
|
||||
dictType modulesDictType = {
|
||||
dictSdsCaseHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCaseCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* Migrate cache dict type. */
|
||||
dictType migrateCacheDictType = {
|
||||
dictSdsHash, /* hash function */
|
||||
@ -2238,6 +2251,7 @@ void call(client *c, int flags) {
|
||||
/* Initialization: clear the flags that must be set by the command on
|
||||
* demand, and initialize the array for additional commands propagation. */
|
||||
c->flags &= ~(CLIENT_FORCE_AOF|CLIENT_FORCE_REPL|CLIENT_PREVENT_PROP);
|
||||
redisOpArray prev_also_propagate = server.also_propagate;
|
||||
redisOpArrayInit(&server.also_propagate);
|
||||
|
||||
/* Call the command. */
|
||||
@ -2333,6 +2347,7 @@ void call(client *c, int flags) {
|
||||
}
|
||||
redisOpArrayFree(&server.also_propagate);
|
||||
}
|
||||
server.also_propagate = prev_also_propagate;
|
||||
server.stat_numcommands++;
|
||||
}
|
||||
|
||||
@ -3993,6 +4008,7 @@ int main(int argc, char **argv) {
|
||||
dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
|
||||
server.sentinel_mode = checkForSentinelMode(argc,argv);
|
||||
initServerConfig();
|
||||
moduleInitModulesSystem();
|
||||
|
||||
/* Store the executable path and arguments in a safe place in order
|
||||
* to be able to restart the server later. */
|
||||
@ -4099,6 +4115,7 @@ int main(int argc, char **argv) {
|
||||
#ifdef __linux__
|
||||
linuxMemoryWarnings();
|
||||
#endif
|
||||
moduleLoadFromQueue();
|
||||
loadDataFromDisk();
|
||||
if (server.cluster_enabled) {
|
||||
if (verifyClusterConfigWithData() == C_ERR) {
|
||||
|
12
src/server.h
12
src/server.h
@ -256,6 +256,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CLIENT_REPLY_SKIP (1<<24) /* Don't send just this reply. */
|
||||
#define CLIENT_LUA_DEBUG (1<<25) /* Run EVAL in debug mode. */
|
||||
#define CLIENT_LUA_DEBUG_SYNC (1<<26) /* EVAL debugging without fork() */
|
||||
#define CLIENT_MODULE (1<<27) /* Non connected client used by some module. */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
@ -570,7 +571,6 @@ typedef struct client {
|
||||
uint64_t id; /* Client incremental unique ID. */
|
||||
int fd; /* Client socket. */
|
||||
redisDb *db; /* Pointer to currently SELECTed DB. */
|
||||
int dictid; /* ID of the currently SELECTed DB. */
|
||||
robj *name; /* As set by CLIENT SETNAME. */
|
||||
sds querybuf; /* Buffer we use to accumulate client queries. */
|
||||
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
|
||||
@ -725,6 +725,9 @@ struct redisServer {
|
||||
int cronloops; /* Number of times the cron function run */
|
||||
char runid[CONFIG_RUN_ID_SIZE+1]; /* ID always different at every exec. */
|
||||
int sentinel_mode; /* True if this instance is a Sentinel. */
|
||||
/* Modules */
|
||||
dict *moduleapi; /* Exported APIs dictionary for modules. */
|
||||
list *loadmodule_queue; /* List of modules to load at startup. */
|
||||
/* Networking */
|
||||
int port; /* TCP listening port */
|
||||
int tcp_backlog; /* TCP listen() backlog */
|
||||
@ -1085,11 +1088,17 @@ extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
extern dictType hashDictType;
|
||||
extern dictType replScriptCacheDictType;
|
||||
extern dictType keyptrDictType;
|
||||
extern dictType modulesDictType;
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Functions prototypes
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Modules */
|
||||
void moduleInitModulesSystem(void);
|
||||
int moduleLoad(const char *path);
|
||||
void moduleLoadFromQueue(void);
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
long long mstime(void);
|
||||
@ -1686,6 +1695,7 @@ void pfcountCommand(client *c);
|
||||
void pfmergeCommand(client *c);
|
||||
void pfdebugCommand(client *c);
|
||||
void latencyCommand(client *c);
|
||||
void moduleCommand(client *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
|
@ -274,7 +274,7 @@ uint32_t sdigits10(int64_t v) {
|
||||
*
|
||||
* Modified in order to handle signed integers since the original code was
|
||||
* designed for unsigned integers. */
|
||||
int ll2string(char* dst, size_t dstlen, long long svalue) {
|
||||
int ll2string(char *dst, size_t dstlen, long long svalue) {
|
||||
static const char digits[201] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
|
Loading…
Reference in New Issue
Block a user