├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── REDIS_LICENSE ├── bench_multi_map.go ├── build.rs ├── include ├── redismodule.c └── redismodule.h └── src ├── ffi.rs ├── lib.rs └── multi_map.rs /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | script: 10 | - cargo build 11 | - cargo test 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redis-multi-map" 3 | version = "0.1.0" 4 | edition = "2018" 5 | authors = ["Garrett Squire "] 6 | 7 | [dependencies] 8 | libc = "0.2" 9 | 10 | [build-dependencies] 11 | bindgen = "0.47" 12 | cc = "1.0" 13 | 14 | [lib] 15 | crate-type = ["cdylib"] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Garrett Squire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redis-multi-map 2 | [![Build Status](https://travis-ci.org/gsquire/redis-multi-map.svg?branch=master)](https://travis-ci.org/gsquire/redis-multi-map) 3 | 4 | This crate provides a custom [Redis module](https://redis.io/topics/modules-intro) type using Rust's FFI support. 5 | By leveraging the ability to make [custom types](https://redis.io/topics/modules-native-types) that Redis can utilize we can 6 | use Rust to provide a new data structure. The `MultiMap` type is akin to Redis' existing hash type with the added 7 | benefit of storing more than one value. 8 | 9 | ## Production Readiness 10 | I would not consider this module to be production ready at the moment. There should be more extensive unit 11 | test coverage. 12 | 13 | Some benchmark numbers achieved by running the provided script locally: 14 | 15 | ```sh 16 | MULTIMAP.INSERT averaged over 5 runs: 66,760 requests/s 17 | SET averaged over 5 runs: 78,672 requests/s 18 | ``` 19 | 20 | ## Install 21 | You must have Rust and LLVM installed in order to compile this project. The preferred way to install Rust is using the 22 | [rustup](https://rustup.rs/) tool. If you are wary of shell script installs, you can download it through brew. 23 | 24 | ```sh 25 | brew install llvm # And `brew install rust` if you don't already have it installed. 26 | 27 | git clone https://github.com/gsquire/redis-multi-map 28 | cd redis-multi-map 29 | cargo build --release # The dynamic library will be under the target/release folder. 30 | ``` 31 | 32 | ## Running Redis 33 | You can load the module in a few ways. 34 | 35 | ### Directly from the command line. 36 | ```sh 37 | redis-server --loadmodule /path/to/module.dylib # Or /path/to/module.so on Unix systems. 38 | ``` 39 | 40 | ### Using the Redis configuration file 41 | ```sh 42 | loadmodule /path/to/module.dylib 43 | ``` 44 | 45 | ## API 46 | The API is open to extending if other functionality is desired. The currently supported commands are as follows: 47 | 48 | ### Insert 49 | ```sh 50 | MULTIMAP.INSERT KEY MAP_KEY MAP_VALUE... 51 | ``` 52 | This command returns a simple string of "OK". 53 | 54 | ### Values 55 | ```sh 56 | MULTIMAP.VALUES KEY MAP_KEY 57 | ``` 58 | This command lists all values associated with `MAP_KEY`. It is an array of strings. 59 | 60 | ### Length 61 | ```sh 62 | MULTIMAP.LEN KEY MAP_KEY 63 | ``` 64 | This command returns the length of values for `MAP_KEY` and `0` if it doesn't exist. This is an integer response. 65 | 66 | ### Delete 67 | ```sh 68 | MULTIMAP.DEL KEY MAP_KEY 69 | ``` 70 | This command deletes `MAP_KEY` from `KEY`. It is an integer response of `0` if `MAP_KEY` did not exist and `1` if it did. 71 | 72 | ## Thanks 73 | Thanks to [redis-cell](https://github.com/brandur/redis-cell) for providing some motivation and guidance in making another 74 | module using Rust. 75 | ## License 76 | This code is release under an MIT license and the Redis license stored under REDIS_LICENSE. 77 | -------------------------------------------------------------------------------- /REDIS_LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2015, Salvatore Sanfilippo 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | * Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /bench_multi_map.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/tidwall/redbench" 8 | ) 9 | 10 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 11 | 12 | func init() { 13 | rand.Seed(time.Now().UnixNano()) 14 | } 15 | 16 | func randomString(length int) string { 17 | b := make([]rune, length) 18 | for i := range b { 19 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 20 | } 21 | 22 | return string(b) 23 | } 24 | 25 | func main() { 26 | redbench.Bench("MULTIMAP.INSERT", "127.0.0.1:6379", nil, nil, func(buf []byte) []byte { 27 | return redbench.AppendCommand(buf, "MULTIMAP.INSERT", "map", randomString(5), randomString(10)) 28 | }) 29 | 30 | redbench.Bench("SET", "127.0.0.1:6379", nil, nil, func(buf []byte) []byte { 31 | return redbench.AppendCommand(buf, "SET", randomString(5), randomString(10)) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate bindgen; 2 | extern crate cc; 3 | 4 | use std::env; 5 | use std::path::PathBuf; 6 | 7 | fn main() { 8 | cc::Build::new() 9 | .file("include/redismodule.c") 10 | .include("include/") 11 | .compile("libredismodule.a"); 12 | 13 | let bindings = bindgen::Builder::default() 14 | .header("include/redismodule.h") 15 | .generate() 16 | .expect("error generating bindings"); 17 | let out = PathBuf::from(env::var("OUT_DIR").unwrap()); 18 | bindings 19 | .write_to_file(out.join("bindings.rs")) 20 | .expect("failed to write bindings to file"); 21 | } 22 | -------------------------------------------------------------------------------- /include/redismodule.c: -------------------------------------------------------------------------------- 1 | #include "redismodule.h" 2 | 3 | int ExportedRedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { 4 | return RedisModule_Init(ctx, name, ver, apiver); 5 | } 6 | -------------------------------------------------------------------------------- /include/redismodule.h: -------------------------------------------------------------------------------- 1 | #ifndef REDISMODULE_H 2 | #define REDISMODULE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* ---------------- Defines common between core and modules --------------- */ 9 | 10 | /* Error status return values. */ 11 | #define REDISMODULE_OK 0 12 | #define REDISMODULE_ERR 1 13 | 14 | /* API versions. */ 15 | #define REDISMODULE_APIVER_1 1 16 | 17 | /* API flags and constants */ 18 | #define REDISMODULE_READ (1<<0) 19 | #define REDISMODULE_WRITE (1<<1) 20 | 21 | #define REDISMODULE_LIST_HEAD 0 22 | #define REDISMODULE_LIST_TAIL 1 23 | 24 | /* Key types. */ 25 | #define REDISMODULE_KEYTYPE_EMPTY 0 26 | #define REDISMODULE_KEYTYPE_STRING 1 27 | #define REDISMODULE_KEYTYPE_LIST 2 28 | #define REDISMODULE_KEYTYPE_HASH 3 29 | #define REDISMODULE_KEYTYPE_SET 4 30 | #define REDISMODULE_KEYTYPE_ZSET 5 31 | #define REDISMODULE_KEYTYPE_MODULE 6 32 | 33 | /* Reply types. */ 34 | #define REDISMODULE_REPLY_UNKNOWN -1 35 | #define REDISMODULE_REPLY_STRING 0 36 | #define REDISMODULE_REPLY_ERROR 1 37 | #define REDISMODULE_REPLY_INTEGER 2 38 | #define REDISMODULE_REPLY_ARRAY 3 39 | #define REDISMODULE_REPLY_NULL 4 40 | 41 | /* Postponed array length. */ 42 | #define REDISMODULE_POSTPONED_ARRAY_LEN -1 43 | 44 | /* Expire */ 45 | #define REDISMODULE_NO_EXPIRE -1 46 | 47 | /* Sorted set API flags. */ 48 | #define REDISMODULE_ZADD_XX (1<<0) 49 | #define REDISMODULE_ZADD_NX (1<<1) 50 | #define REDISMODULE_ZADD_ADDED (1<<2) 51 | #define REDISMODULE_ZADD_UPDATED (1<<3) 52 | #define REDISMODULE_ZADD_NOP (1<<4) 53 | 54 | /* Hash API flags. */ 55 | #define REDISMODULE_HASH_NONE 0 56 | #define REDISMODULE_HASH_NX (1<<0) 57 | #define REDISMODULE_HASH_XX (1<<1) 58 | #define REDISMODULE_HASH_CFIELDS (1<<2) 59 | #define REDISMODULE_HASH_EXISTS (1<<3) 60 | 61 | /* Context Flags: Info about the current context returned by 62 | * RM_GetContextFlags(). */ 63 | 64 | /* The command is running in the context of a Lua script */ 65 | #define REDISMODULE_CTX_FLAGS_LUA (1<<0) 66 | /* The command is running inside a Redis transaction */ 67 | #define REDISMODULE_CTX_FLAGS_MULTI (1<<1) 68 | /* The instance is a master */ 69 | #define REDISMODULE_CTX_FLAGS_MASTER (1<<2) 70 | /* The instance is a slave */ 71 | #define REDISMODULE_CTX_FLAGS_SLAVE (1<<3) 72 | /* The instance is read-only (usually meaning it's a slave as well) */ 73 | #define REDISMODULE_CTX_FLAGS_READONLY (1<<4) 74 | /* The instance is running in cluster mode */ 75 | #define REDISMODULE_CTX_FLAGS_CLUSTER (1<<5) 76 | /* The instance has AOF enabled */ 77 | #define REDISMODULE_CTX_FLAGS_AOF (1<<6) 78 | /* The instance has RDB enabled */ 79 | #define REDISMODULE_CTX_FLAGS_RDB (1<<7) 80 | /* The instance has Maxmemory set */ 81 | #define REDISMODULE_CTX_FLAGS_MAXMEMORY (1<<8) 82 | /* Maxmemory is set and has an eviction policy that may delete keys */ 83 | #define REDISMODULE_CTX_FLAGS_EVICT (1<<9) 84 | /* Redis is out of memory according to the maxmemory flag. */ 85 | #define REDISMODULE_CTX_FLAGS_OOM (1<<10) 86 | /* Less than 25% of memory available according to maxmemory. */ 87 | #define REDISMODULE_CTX_FLAGS_OOM_WARNING (1<<11) 88 | 89 | #define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */ 90 | #define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */ 91 | #define REDISMODULE_NOTIFY_LIST (1<<4) /* l */ 92 | #define REDISMODULE_NOTIFY_SET (1<<5) /* s */ 93 | #define REDISMODULE_NOTIFY_HASH (1<<6) /* h */ 94 | #define REDISMODULE_NOTIFY_ZSET (1<<7) /* z */ 95 | #define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */ 96 | #define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */ 97 | #define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */ 98 | #define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */ 99 | 100 | 101 | /* A special pointer that we can use between the core and the module to signal 102 | * field deletion, and that is impossible to be a valid pointer. */ 103 | #define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1) 104 | 105 | /* Error messages. */ 106 | #define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value" 107 | 108 | #define REDISMODULE_POSITIVE_INFINITE (1.0/0.0) 109 | #define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0) 110 | 111 | /* Cluster API defines. */ 112 | #define REDISMODULE_NODE_ID_LEN 40 113 | #define REDISMODULE_NODE_MYSELF (1<<0) 114 | #define REDISMODULE_NODE_MASTER (1<<1) 115 | #define REDISMODULE_NODE_SLAVE (1<<2) 116 | #define REDISMODULE_NODE_PFAIL (1<<3) 117 | #define REDISMODULE_NODE_FAIL (1<<4) 118 | #define REDISMODULE_NODE_NOFAILOVER (1<<5) 119 | 120 | #define REDISMODULE_CLUSTER_FLAG_NONE 0 121 | #define REDISMODULE_CLUSTER_FLAG_NO_FAILOVER (1<<1) 122 | #define REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION (1<<2) 123 | 124 | #define REDISMODULE_NOT_USED(V) ((void) V) 125 | 126 | /* This type represents a timer handle, and is returned when a timer is 127 | * registered and used in order to invalidate a timer. It's just a 64 bit 128 | * number, because this is how each timer is represented inside the radix tree 129 | * of timers that are going to expire, sorted by expire time. */ 130 | typedef uint64_t RedisModuleTimerID; 131 | 132 | /* ------------------------- End of common defines ------------------------ */ 133 | 134 | #ifndef REDISMODULE_CORE 135 | 136 | typedef long long mstime_t; 137 | 138 | /* Incomplete structures for compiler checks but opaque access. */ 139 | typedef struct RedisModuleCtx RedisModuleCtx; 140 | typedef struct RedisModuleKey RedisModuleKey; 141 | typedef struct RedisModuleString RedisModuleString; 142 | typedef struct RedisModuleCallReply RedisModuleCallReply; 143 | typedef struct RedisModuleIO RedisModuleIO; 144 | typedef struct RedisModuleType RedisModuleType; 145 | typedef struct RedisModuleDigest RedisModuleDigest; 146 | typedef struct RedisModuleBlockedClient RedisModuleBlockedClient; 147 | typedef struct RedisModuleClusterInfo RedisModuleClusterInfo; 148 | typedef struct RedisModuleDict RedisModuleDict; 149 | typedef struct RedisModuleDictIter RedisModuleDictIter; 150 | 151 | typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); 152 | typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc); 153 | typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key); 154 | typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver); 155 | typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value); 156 | typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value); 157 | typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value); 158 | typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value); 159 | typedef void (*RedisModuleTypeFreeFunc)(void *value); 160 | typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len); 161 | typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data); 162 | 163 | #define REDISMODULE_TYPE_METHOD_VERSION 1 164 | typedef struct RedisModuleTypeMethods { 165 | uint64_t version; 166 | RedisModuleTypeLoadFunc rdb_load; 167 | RedisModuleTypeSaveFunc rdb_save; 168 | RedisModuleTypeRewriteFunc aof_rewrite; 169 | RedisModuleTypeMemUsageFunc mem_usage; 170 | RedisModuleTypeDigestFunc digest; 171 | RedisModuleTypeFreeFunc free; 172 | } RedisModuleTypeMethods; 173 | 174 | #define REDISMODULE_GET_API(name) \ 175 | RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name)) 176 | 177 | #define REDISMODULE_API_FUNC(x) (*x) 178 | 179 | 180 | void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes); 181 | void *REDISMODULE_API_FUNC(RedisModule_Realloc)(void *ptr, size_t bytes); 182 | void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr); 183 | void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size); 184 | char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str); 185 | int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *); 186 | int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep); 187 | void REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver); 188 | int REDISMODULE_API_FUNC(RedisModule_IsModuleNameBusy)(const char *name); 189 | int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx); 190 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll); 191 | int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx); 192 | int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid); 193 | void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode); 194 | void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp); 195 | int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp); 196 | size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp); 197 | int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele); 198 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where); 199 | RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); 200 | const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len); 201 | void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply); 202 | int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply); 203 | long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply); 204 | size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply); 205 | RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx); 206 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len); 207 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll); 208 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str); 209 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...); 210 | void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str); 211 | const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len); 212 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err); 213 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg); 214 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len); 215 | void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len); 216 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len); 217 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str); 218 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx); 219 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d); 220 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply); 221 | int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll); 222 | int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d); 223 | void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx); 224 | int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); 225 | int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx); 226 | const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len); 227 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply); 228 | int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key); 229 | int REDISMODULE_API_FUNC(RedisModule_UnlinkKey)(RedisModuleKey *key); 230 | int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str); 231 | char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode); 232 | int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen); 233 | mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key); 234 | int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire); 235 | int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr); 236 | int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore); 237 | int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score); 238 | int REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted); 239 | void REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key); 240 | int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); 241 | int REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); 242 | int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); 243 | int REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); 244 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score); 245 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key); 246 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key); 247 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key); 248 | int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...); 249 | int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...); 250 | int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx); 251 | void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos); 252 | unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx); 253 | int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx); 254 | void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes); 255 | RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods); 256 | int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value); 257 | RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key); 258 | void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key); 259 | void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value); 260 | uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io); 261 | void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value); 262 | int64_t REDISMODULE_API_FUNC(RedisModule_LoadSigned)(RedisModuleIO *io); 263 | void REDISMODULE_API_FUNC(RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...); 264 | void REDISMODULE_API_FUNC(RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s); 265 | void REDISMODULE_API_FUNC(RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len); 266 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_LoadString)(RedisModuleIO *io); 267 | char *REDISMODULE_API_FUNC(RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr); 268 | void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double value); 269 | double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io); 270 | void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value); 271 | float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io); 272 | void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...); 273 | void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...); 274 | int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len); 275 | void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str); 276 | int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b); 277 | RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io); 278 | long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void); 279 | void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len); 280 | void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele); 281 | void REDISMODULE_API_FUNC(RedisModule_DigestEndSequence)(RedisModuleDigest *md); 282 | RedisModuleDict *REDISMODULE_API_FUNC(RedisModule_CreateDict)(RedisModuleCtx *ctx); 283 | void REDISMODULE_API_FUNC(RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d); 284 | uint64_t REDISMODULE_API_FUNC(RedisModule_DictSize)(RedisModuleDict *d); 285 | int REDISMODULE_API_FUNC(RedisModule_DictSetC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr); 286 | int REDISMODULE_API_FUNC(RedisModule_DictReplaceC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr); 287 | int REDISMODULE_API_FUNC(RedisModule_DictSet)(RedisModuleDict *d, RedisModuleString *key, void *ptr); 288 | int REDISMODULE_API_FUNC(RedisModule_DictReplace)(RedisModuleDict *d, RedisModuleString *key, void *ptr); 289 | void *REDISMODULE_API_FUNC(RedisModule_DictGetC)(RedisModuleDict *d, void *key, size_t keylen, int *nokey); 290 | void *REDISMODULE_API_FUNC(RedisModule_DictGet)(RedisModuleDict *d, RedisModuleString *key, int *nokey); 291 | int REDISMODULE_API_FUNC(RedisModule_DictDelC)(RedisModuleDict *d, void *key, size_t keylen, void *oldval); 292 | int REDISMODULE_API_FUNC(RedisModule_DictDel)(RedisModuleDict *d, RedisModuleString *key, void *oldval); 293 | RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStartC)(RedisModuleDict *d, const char *op, void *key, size_t keylen); 294 | RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStart)(RedisModuleDict *d, const char *op, RedisModuleString *key); 295 | void REDISMODULE_API_FUNC(RedisModule_DictIteratorStop)(RedisModuleDictIter *di); 296 | int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseekC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen); 297 | int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseek)(RedisModuleDictIter *di, const char *op, RedisModuleString *key); 298 | void *REDISMODULE_API_FUNC(RedisModule_DictNextC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr); 299 | void *REDISMODULE_API_FUNC(RedisModule_DictPrevC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr); 300 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr); 301 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr); 302 | int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen); 303 | int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key); 304 | 305 | /* Experimental APIs */ 306 | #ifdef REDISMODULE_EXPERIMENTAL_API 307 | #define REDISMODULE_EXPERIMENTAL_API_VERSION 3 308 | RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms); 309 | int REDISMODULE_API_FUNC(RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata); 310 | int REDISMODULE_API_FUNC(RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx); 311 | int REDISMODULE_API_FUNC(RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx); 312 | void *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx); 313 | RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientHandle)(RedisModuleCtx *ctx); 314 | int REDISMODULE_API_FUNC(RedisModule_AbortBlock)(RedisModuleBlockedClient *bc); 315 | RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc); 316 | void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx); 317 | void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx); 318 | void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx); 319 | int REDISMODULE_API_FUNC(RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb); 320 | int REDISMODULE_API_FUNC(RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx); 321 | void REDISMODULE_API_FUNC(RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback); 322 | int REDISMODULE_API_FUNC(RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len); 323 | int REDISMODULE_API_FUNC(RedisModule_GetClusterNodeInfo)(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags); 324 | char **REDISMODULE_API_FUNC(RedisModule_GetClusterNodesList)(RedisModuleCtx *ctx, size_t *numnodes); 325 | void REDISMODULE_API_FUNC(RedisModule_FreeClusterNodesList)(char **ids); 326 | RedisModuleTimerID REDISMODULE_API_FUNC(RedisModule_CreateTimer)(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data); 327 | int REDISMODULE_API_FUNC(RedisModule_StopTimer)(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data); 328 | int REDISMODULE_API_FUNC(RedisModule_GetTimerInfo)(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data); 329 | const char *REDISMODULE_API_FUNC(RedisModule_GetMyClusterID)(void); 330 | size_t REDISMODULE_API_FUNC(RedisModule_GetClusterSize)(void); 331 | void REDISMODULE_API_FUNC(RedisModule_GetRandomBytes)(unsigned char *dst, size_t len); 332 | void REDISMODULE_API_FUNC(RedisModule_GetRandomHexChars)(char *dst, size_t len); 333 | void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback); 334 | void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags); 335 | #endif 336 | 337 | /* This is included inline inside each Redis module. */ 338 | static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused)); 339 | static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { 340 | void *getapifuncptr = ((void**)ctx)[0]; 341 | RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr; 342 | REDISMODULE_GET_API(Alloc); 343 | REDISMODULE_GET_API(Calloc); 344 | REDISMODULE_GET_API(Free); 345 | REDISMODULE_GET_API(Realloc); 346 | REDISMODULE_GET_API(Strdup); 347 | REDISMODULE_GET_API(CreateCommand); 348 | REDISMODULE_GET_API(SetModuleAttribs); 349 | REDISMODULE_GET_API(IsModuleNameBusy); 350 | REDISMODULE_GET_API(WrongArity); 351 | REDISMODULE_GET_API(ReplyWithLongLong); 352 | REDISMODULE_GET_API(ReplyWithError); 353 | REDISMODULE_GET_API(ReplyWithSimpleString); 354 | REDISMODULE_GET_API(ReplyWithArray); 355 | REDISMODULE_GET_API(ReplySetArrayLength); 356 | REDISMODULE_GET_API(ReplyWithStringBuffer); 357 | REDISMODULE_GET_API(ReplyWithString); 358 | REDISMODULE_GET_API(ReplyWithNull); 359 | REDISMODULE_GET_API(ReplyWithCallReply); 360 | REDISMODULE_GET_API(ReplyWithDouble); 361 | REDISMODULE_GET_API(ReplySetArrayLength); 362 | REDISMODULE_GET_API(GetSelectedDb); 363 | REDISMODULE_GET_API(SelectDb); 364 | REDISMODULE_GET_API(OpenKey); 365 | REDISMODULE_GET_API(CloseKey); 366 | REDISMODULE_GET_API(KeyType); 367 | REDISMODULE_GET_API(ValueLength); 368 | REDISMODULE_GET_API(ListPush); 369 | REDISMODULE_GET_API(ListPop); 370 | REDISMODULE_GET_API(StringToLongLong); 371 | REDISMODULE_GET_API(StringToDouble); 372 | REDISMODULE_GET_API(Call); 373 | REDISMODULE_GET_API(CallReplyProto); 374 | REDISMODULE_GET_API(FreeCallReply); 375 | REDISMODULE_GET_API(CallReplyInteger); 376 | REDISMODULE_GET_API(CallReplyType); 377 | REDISMODULE_GET_API(CallReplyLength); 378 | REDISMODULE_GET_API(CallReplyArrayElement); 379 | REDISMODULE_GET_API(CallReplyStringPtr); 380 | REDISMODULE_GET_API(CreateStringFromCallReply); 381 | REDISMODULE_GET_API(CreateString); 382 | REDISMODULE_GET_API(CreateStringFromLongLong); 383 | REDISMODULE_GET_API(CreateStringFromString); 384 | REDISMODULE_GET_API(CreateStringPrintf); 385 | REDISMODULE_GET_API(FreeString); 386 | REDISMODULE_GET_API(StringPtrLen); 387 | REDISMODULE_GET_API(AutoMemory); 388 | REDISMODULE_GET_API(Replicate); 389 | REDISMODULE_GET_API(ReplicateVerbatim); 390 | REDISMODULE_GET_API(DeleteKey); 391 | REDISMODULE_GET_API(UnlinkKey); 392 | REDISMODULE_GET_API(StringSet); 393 | REDISMODULE_GET_API(StringDMA); 394 | REDISMODULE_GET_API(StringTruncate); 395 | REDISMODULE_GET_API(GetExpire); 396 | REDISMODULE_GET_API(SetExpire); 397 | REDISMODULE_GET_API(ZsetAdd); 398 | REDISMODULE_GET_API(ZsetIncrby); 399 | REDISMODULE_GET_API(ZsetScore); 400 | REDISMODULE_GET_API(ZsetRem); 401 | REDISMODULE_GET_API(ZsetRangeStop); 402 | REDISMODULE_GET_API(ZsetFirstInScoreRange); 403 | REDISMODULE_GET_API(ZsetLastInScoreRange); 404 | REDISMODULE_GET_API(ZsetFirstInLexRange); 405 | REDISMODULE_GET_API(ZsetLastInLexRange); 406 | REDISMODULE_GET_API(ZsetRangeCurrentElement); 407 | REDISMODULE_GET_API(ZsetRangeNext); 408 | REDISMODULE_GET_API(ZsetRangePrev); 409 | REDISMODULE_GET_API(ZsetRangeEndReached); 410 | REDISMODULE_GET_API(HashSet); 411 | REDISMODULE_GET_API(HashGet); 412 | REDISMODULE_GET_API(IsKeysPositionRequest); 413 | REDISMODULE_GET_API(KeyAtPos); 414 | REDISMODULE_GET_API(GetClientId); 415 | REDISMODULE_GET_API(GetContextFlags); 416 | REDISMODULE_GET_API(PoolAlloc); 417 | REDISMODULE_GET_API(CreateDataType); 418 | REDISMODULE_GET_API(ModuleTypeSetValue); 419 | REDISMODULE_GET_API(ModuleTypeGetType); 420 | REDISMODULE_GET_API(ModuleTypeGetValue); 421 | REDISMODULE_GET_API(SaveUnsigned); 422 | REDISMODULE_GET_API(LoadUnsigned); 423 | REDISMODULE_GET_API(SaveSigned); 424 | REDISMODULE_GET_API(LoadSigned); 425 | REDISMODULE_GET_API(SaveString); 426 | REDISMODULE_GET_API(SaveStringBuffer); 427 | REDISMODULE_GET_API(LoadString); 428 | REDISMODULE_GET_API(LoadStringBuffer); 429 | REDISMODULE_GET_API(SaveDouble); 430 | REDISMODULE_GET_API(LoadDouble); 431 | REDISMODULE_GET_API(SaveFloat); 432 | REDISMODULE_GET_API(LoadFloat); 433 | REDISMODULE_GET_API(EmitAOF); 434 | REDISMODULE_GET_API(Log); 435 | REDISMODULE_GET_API(LogIOError); 436 | REDISMODULE_GET_API(StringAppendBuffer); 437 | REDISMODULE_GET_API(RetainString); 438 | REDISMODULE_GET_API(StringCompare); 439 | REDISMODULE_GET_API(GetContextFromIO); 440 | REDISMODULE_GET_API(Milliseconds); 441 | REDISMODULE_GET_API(DigestAddStringBuffer); 442 | REDISMODULE_GET_API(DigestAddLongLong); 443 | REDISMODULE_GET_API(DigestEndSequence); 444 | REDISMODULE_GET_API(CreateDict); 445 | REDISMODULE_GET_API(FreeDict); 446 | REDISMODULE_GET_API(DictSize); 447 | REDISMODULE_GET_API(DictSetC); 448 | REDISMODULE_GET_API(DictReplaceC); 449 | REDISMODULE_GET_API(DictSet); 450 | REDISMODULE_GET_API(DictReplace); 451 | REDISMODULE_GET_API(DictGetC); 452 | REDISMODULE_GET_API(DictGet); 453 | REDISMODULE_GET_API(DictDelC); 454 | REDISMODULE_GET_API(DictDel); 455 | REDISMODULE_GET_API(DictIteratorStartC); 456 | REDISMODULE_GET_API(DictIteratorStart); 457 | REDISMODULE_GET_API(DictIteratorStop); 458 | REDISMODULE_GET_API(DictIteratorReseekC); 459 | REDISMODULE_GET_API(DictIteratorReseek); 460 | REDISMODULE_GET_API(DictNextC); 461 | REDISMODULE_GET_API(DictPrevC); 462 | REDISMODULE_GET_API(DictNext); 463 | REDISMODULE_GET_API(DictPrev); 464 | REDISMODULE_GET_API(DictCompare); 465 | REDISMODULE_GET_API(DictCompareC); 466 | 467 | #ifdef REDISMODULE_EXPERIMENTAL_API 468 | REDISMODULE_GET_API(GetThreadSafeContext); 469 | REDISMODULE_GET_API(FreeThreadSafeContext); 470 | REDISMODULE_GET_API(ThreadSafeContextLock); 471 | REDISMODULE_GET_API(ThreadSafeContextUnlock); 472 | REDISMODULE_GET_API(BlockClient); 473 | REDISMODULE_GET_API(UnblockClient); 474 | REDISMODULE_GET_API(IsBlockedReplyRequest); 475 | REDISMODULE_GET_API(IsBlockedTimeoutRequest); 476 | REDISMODULE_GET_API(GetBlockedClientPrivateData); 477 | REDISMODULE_GET_API(GetBlockedClientHandle); 478 | REDISMODULE_GET_API(AbortBlock); 479 | REDISMODULE_GET_API(SetDisconnectCallback); 480 | REDISMODULE_GET_API(SubscribeToKeyspaceEvents); 481 | REDISMODULE_GET_API(BlockedClientDisconnected); 482 | REDISMODULE_GET_API(RegisterClusterMessageReceiver); 483 | REDISMODULE_GET_API(SendClusterMessage); 484 | REDISMODULE_GET_API(GetClusterNodeInfo); 485 | REDISMODULE_GET_API(GetClusterNodesList); 486 | REDISMODULE_GET_API(FreeClusterNodesList); 487 | REDISMODULE_GET_API(CreateTimer); 488 | REDISMODULE_GET_API(StopTimer); 489 | REDISMODULE_GET_API(GetTimerInfo); 490 | REDISMODULE_GET_API(GetMyClusterID); 491 | REDISMODULE_GET_API(GetClusterSize); 492 | REDISMODULE_GET_API(GetRandomBytes); 493 | REDISMODULE_GET_API(GetRandomHexChars); 494 | REDISMODULE_GET_API(SetClusterFlags); 495 | #endif 496 | 497 | if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR; 498 | RedisModule_SetModuleAttribs(ctx,name,ver,apiver); 499 | return REDISMODULE_OK; 500 | } 501 | 502 | #else 503 | 504 | /* Things only defined for the modules core, not exported to modules 505 | * including this file. */ 506 | #define RedisModuleString robj 507 | 508 | #endif /* REDISMODULE_CORE */ 509 | #endif /* REDISMOUDLE_H */ 510 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 6 | 7 | // We can't use C macros safely with FFI so redefine this message here. 8 | pub const ERRORMSG_WRONGTYPE: &str = 9 | "WRONGTYPE Operation against a key holding the wrong kind of value"; 10 | pub const SIMPLE_OK: &str = "OK"; 11 | pub const APIVER_1: libc::c_int = 1; 12 | pub const REDIS_OK: libc::c_int = 0; 13 | pub const REDIS_ERR: libc::c_int = 1; 14 | pub const POSTPONED_ARRAY_LEN: libc::c_long = -1; 15 | 16 | // This is the one static function we need to initialize a module. 17 | #[allow(improper_ctypes)] 18 | extern "C" { 19 | pub fn ExportedRedisModule_Init( 20 | ctx: *mut RedisModuleCtx, 21 | module_name: *const u8, 22 | module_version: libc::c_int, 23 | api_version: libc::c_int, 24 | ) -> libc::c_int; 25 | } 26 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::ffi::{CStr, CString}; 3 | use std::os::raw; 4 | use std::slice; 5 | 6 | #[allow(dead_code)] 7 | mod ffi; 8 | mod multi_map; 9 | 10 | use crate::multi_map::MultiMap; 11 | 12 | // `MULTI_MAP_TYPE` is a value used to define what data type this module uses. 13 | static mut MULTI_MAP_TYPE: *mut ffi::RedisModuleType = 0 as *mut ffi::RedisModuleType; 14 | 15 | #[no_mangle] 16 | /// Return a raw pointer to a `MultiMap` instance. 17 | pub extern "C" fn new_multi_map() -> *mut MultiMap { 18 | Box::into_raw(Box::new(MultiMap::new())) 19 | } 20 | 21 | #[no_mangle] 22 | /// Given a pointer to a `MultiMap`, make sure that it isn't null and then re-`Box` it so 23 | /// it can be freed automatically. 24 | pub unsafe extern "C" fn free_multi_map(map: *mut MultiMap) { 25 | if !map.is_null() { 26 | Box::from_raw(map); 27 | } 28 | } 29 | 30 | #[no_mangle] 31 | /// Free a `MultiMap` type. 32 | pub unsafe extern "C" fn MultiMapFree(value: *mut raw::c_void) { 33 | free_multi_map(value as *mut MultiMap) 34 | } 35 | 36 | #[no_mangle] 37 | /// Free a `CString` object. 38 | pub unsafe extern "C" fn free_ffi_string(s: *mut libc::c_char) { 39 | if !s.is_null() { 40 | CString::from_raw(s); 41 | } 42 | } 43 | 44 | #[no_mangle] 45 | /// If the supplied key is not a `MultiMap` then return true. 46 | pub unsafe extern "C" fn invalid_key_type(key: *mut ffi::RedisModuleKey) -> bool { 47 | let key_type = ffi::RedisModule_KeyType.unwrap()(key); 48 | key_type != (ffi::REDISMODULE_KEYTYPE_EMPTY as i32) 49 | && ffi::RedisModule_ModuleTypeGetType.unwrap()(key) != MULTI_MAP_TYPE 50 | } 51 | 52 | #[no_mangle] 53 | /// Return early with the supplied error message. 54 | pub unsafe extern "C" fn reply_with_error( 55 | ctx: *mut ffi::RedisModuleCtx, 56 | message: &str, 57 | ) -> libc::c_int { 58 | let m = CString::new(message).unwrap(); 59 | ffi::RedisModule_ReplyWithError.unwrap()(ctx, m.as_ptr()) 60 | } 61 | 62 | #[no_mangle] 63 | /// Perform a lossy conversion of a module string into a `Cow`. 64 | pub unsafe extern "C" fn string_from_module_string( 65 | s: *const ffi::RedisModuleString, 66 | ) -> Cow<'static, str> { 67 | let mut len = 0; 68 | let c_str = ffi::RedisModule_StringPtrLen.unwrap()(s, &mut len); 69 | CStr::from_ptr(c_str).to_string_lossy() 70 | } 71 | 72 | #[no_mangle] 73 | // The command to insert one or more elements into the multi-map. 74 | pub unsafe extern "C" fn MultiMapInsert_RedisCommand( 75 | ctx: *mut ffi::RedisModuleCtx, 76 | argv: *mut *mut ffi::RedisModuleString, 77 | argc: libc::c_int, 78 | ) -> libc::c_int { 79 | ffi::RedisModule_AutoMemory.unwrap()(ctx); 80 | 81 | if argc < 4 { 82 | return ffi::RedisModule_WrongArity.unwrap()(ctx); 83 | } 84 | 85 | // Unpack the arguments into a slice and then open and validate the key. 86 | let args = slice::from_raw_parts(argv, argc as usize); 87 | let key = ffi::RedisModule_OpenKey.unwrap()( 88 | ctx, 89 | args[1], 90 | (ffi::REDISMODULE_READ as i32) | (ffi::REDISMODULE_WRITE as i32), 91 | ) as *mut ffi::RedisModuleKey; 92 | 93 | if invalid_key_type(key) { 94 | return reply_with_error(ctx, ffi::ERRORMSG_WRONGTYPE); 95 | } 96 | 97 | // Turn the rest of the arguments into strings to insert into the map. 98 | let map: *mut MultiMap; 99 | let key_type = ffi::RedisModule_KeyType.unwrap()(key); 100 | if key_type == (ffi::REDISMODULE_KEYTYPE_EMPTY as i32) { 101 | map = new_multi_map(); 102 | ffi::RedisModule_ModuleTypeSetValue.unwrap()( 103 | key, 104 | MULTI_MAP_TYPE, 105 | map as *mut ::std::os::raw::c_void, 106 | ); 107 | } else { 108 | map = ffi::RedisModule_ModuleTypeGetValue.unwrap()(key) as *mut MultiMap; 109 | } 110 | 111 | // We have to make some more casts to insert into our map. 112 | let map_key = string_from_module_string(args[2]); 113 | let map_values = args 114 | .iter() 115 | .skip(3) 116 | .map(|v| string_from_module_string(*v as *const ffi::RedisModuleString).into_owned()); 117 | let m = &mut *map; 118 | m.insert(map_key, map_values); 119 | 120 | let resp = CString::new(ffi::SIMPLE_OK).unwrap(); 121 | ffi::RedisModule_ReplyWithSimpleString.unwrap()(ctx, resp.as_ptr()); 122 | ffi::RedisModule_ReplicateVerbatim.unwrap()(ctx); 123 | 124 | ffi::REDIS_OK 125 | } 126 | 127 | #[no_mangle] 128 | // Return the length of a key in a `MultiMap`. 129 | pub unsafe extern "C" fn MultiMapLength_RedisCommand( 130 | ctx: *mut ffi::RedisModuleCtx, 131 | argv: *mut *mut ffi::RedisModuleString, 132 | argc: libc::c_int, 133 | ) -> libc::c_int { 134 | ffi::RedisModule_AutoMemory.unwrap()(ctx); 135 | 136 | if argc != 3 { 137 | return ffi::RedisModule_WrongArity.unwrap()(ctx); 138 | } 139 | 140 | // Unpack the arguments into a slice and then open and validate the key. 141 | let args = slice::from_raw_parts(argv, argc as usize); 142 | let key = ffi::RedisModule_OpenKey.unwrap()( 143 | ctx, 144 | args[1], 145 | (ffi::REDISMODULE_READ as i32) | (ffi::REDISMODULE_WRITE as i32), 146 | ) as *mut ffi::RedisModuleKey; 147 | 148 | if invalid_key_type(key) { 149 | return reply_with_error(ctx, ffi::ERRORMSG_WRONGTYPE); 150 | } 151 | 152 | let map = ffi::RedisModule_ModuleTypeGetValue.unwrap()(key) as *mut MultiMap; 153 | if map.is_null() { 154 | ffi::RedisModule_ReplyWithLongLong.unwrap()(ctx, 0); 155 | } else { 156 | let m = &mut *map; 157 | let map_key = string_from_module_string(args[2]); 158 | ffi::RedisModule_ReplyWithLongLong.unwrap()(ctx, m.key_len(map_key) as i64); 159 | } 160 | 161 | ffi::REDIS_OK 162 | } 163 | 164 | #[no_mangle] 165 | // Return a list of the values for a given key. 166 | pub unsafe extern "C" fn MultiMapValues_RedisCommand( 167 | ctx: *mut ffi::RedisModuleCtx, 168 | argv: *mut *mut ffi::RedisModuleString, 169 | argc: libc::c_int, 170 | ) -> libc::c_int { 171 | ffi::RedisModule_AutoMemory.unwrap()(ctx); 172 | 173 | if argc != 3 { 174 | return ffi::RedisModule_WrongArity.unwrap()(ctx); 175 | } 176 | 177 | let args = slice::from_raw_parts(argv, argc as usize); 178 | let key = ffi::RedisModule_OpenKey.unwrap()( 179 | ctx, 180 | args[1], 181 | (ffi::REDISMODULE_READ as i32) | (ffi::REDISMODULE_WRITE as i32), 182 | ) as *mut ffi::RedisModuleKey; 183 | 184 | if invalid_key_type(key) { 185 | return reply_with_error(ctx, ffi::ERRORMSG_WRONGTYPE); 186 | } 187 | 188 | let map = ffi::RedisModule_ModuleTypeGetValue.unwrap()(key) as *mut MultiMap; 189 | if map.is_null() { 190 | ffi::RedisModule_ReplyWithArray.unwrap()(ctx, 0); 191 | } else { 192 | let m = &mut *map; 193 | ffi::RedisModule_ReplyWithArray.unwrap()(ctx, ffi::POSTPONED_ARRAY_LEN); 194 | let map_key = string_from_module_string(args[2]); 195 | let values = m.values(map_key); 196 | 197 | match values { 198 | Some(values) => { 199 | ffi::RedisModule_ReplySetArrayLength.unwrap()(ctx, values.len() as i64); 200 | for v in values { 201 | let s_len = v.len(); 202 | let s = CString::new(v.as_bytes()).unwrap(); 203 | let s_ptr = s.into_raw(); 204 | ffi::RedisModule_ReplyWithStringBuffer.unwrap()(ctx, s_ptr, s_len); 205 | 206 | // Even with automatic memory mangement enabled, we must free these according 207 | // to the Rust documentation. 208 | let _ = CString::from_raw(s_ptr); 209 | } 210 | } 211 | None => { 212 | ffi::RedisModule_ReplySetArrayLength.unwrap()(ctx, 0); 213 | } 214 | } 215 | } 216 | 217 | ffi::REDIS_OK 218 | } 219 | 220 | #[no_mangle] 221 | // Delete a key from a `MultiMap`. 222 | pub unsafe extern "C" fn MultiMapDelete_RedisCommand( 223 | ctx: *mut ffi::RedisModuleCtx, 224 | argv: *mut *mut ffi::RedisModuleString, 225 | argc: libc::c_int, 226 | ) -> libc::c_int { 227 | ffi::RedisModule_AutoMemory.unwrap()(ctx); 228 | 229 | if argc != 3 { 230 | return ffi::RedisModule_WrongArity.unwrap()(ctx); 231 | } 232 | 233 | let args = slice::from_raw_parts(argv, argc as usize); 234 | let key = ffi::RedisModule_OpenKey.unwrap()( 235 | ctx, 236 | args[1], 237 | (ffi::REDISMODULE_READ as i32) | (ffi::REDISMODULE_WRITE as i32), 238 | ) as *mut ffi::RedisModuleKey; 239 | 240 | if invalid_key_type(key) { 241 | return reply_with_error(ctx, ffi::ERRORMSG_WRONGTYPE); 242 | } 243 | 244 | let map = ffi::RedisModule_ModuleTypeGetValue.unwrap()(key) as *mut MultiMap; 245 | if map.is_null() { 246 | ffi::RedisModule_ReplyWithLongLong.unwrap()(ctx, 0); 247 | } else { 248 | let m = &mut *map; 249 | let map_key = string_from_module_string(args[2]); 250 | let deleted = m.delete_key(map_key); 251 | ffi::RedisModule_ReplyWithLongLong.unwrap()(ctx, deleted); 252 | } 253 | 254 | ffi::REDIS_OK 255 | } 256 | 257 | #[allow(unused_variables)] 258 | #[no_mangle] 259 | pub unsafe extern "C" fn MultiMapRdbLoad( 260 | rdb: *mut ffi::RedisModuleIO, 261 | encver: libc::c_int, 262 | ) -> *mut raw::c_void { 263 | let m = new_multi_map(); 264 | let map = &mut *m; 265 | let count = ffi::RedisModule_LoadUnsigned.unwrap()(rdb); 266 | 267 | // We have to load `count` keys and then do a load of how many values it had after. 268 | for _ in 0..count { 269 | let mut key_len = 0; 270 | let loaded_key = ffi::RedisModule_LoadStringBuffer.unwrap()(rdb, &mut key_len); 271 | let key = CStr::from_ptr(loaded_key).to_string_lossy(); 272 | let num_values = ffi::RedisModule_LoadUnsigned.unwrap()(rdb); 273 | 274 | for _ in 0..num_values { 275 | let mut val_len = 0; 276 | let loaded_value = ffi::RedisModule_LoadStringBuffer.unwrap()(rdb, &mut val_len); 277 | let value = CStr::from_ptr(loaded_value).to_string_lossy().into_owned(); 278 | map.insert(key.clone(), vec![value]); 279 | ffi::RedisModule_Free.unwrap()(loaded_value as *mut raw::c_void); 280 | } 281 | 282 | ffi::RedisModule_Free.unwrap()(loaded_key as *mut raw::c_void); 283 | } 284 | 285 | m as *mut raw::c_void 286 | } 287 | 288 | #[no_mangle] 289 | pub unsafe extern "C" fn MultiMapRdbSave(rdb: *mut ffi::RedisModuleIO, value: *mut raw::c_void) { 290 | if value.is_null() { 291 | return; 292 | } 293 | 294 | let m = &*(value as *mut MultiMap); 295 | 296 | // We will save our map in this order: 297 | // 1. Number of items in the map 2. Key name 3. Value length 4. Values 298 | ffi::RedisModule_SaveUnsigned.unwrap()(rdb, m.len() as u64); 299 | 300 | for (k, v) in m { 301 | let k_len = k.len(); 302 | let key = CString::new(k.as_bytes()).unwrap(); 303 | ffi::RedisModule_SaveStringBuffer.unwrap()(rdb, key.as_ptr(), k_len + 1); 304 | 305 | ffi::RedisModule_SaveUnsigned.unwrap()(rdb, v.len() as u64); 306 | for value in v { 307 | let v_len = value.len(); 308 | let value_str = CString::new(value.as_bytes()).unwrap(); 309 | ffi::RedisModule_SaveStringBuffer.unwrap()(rdb, value_str.as_ptr(), v_len + 1); 310 | } 311 | } 312 | } 313 | 314 | #[no_mangle] 315 | pub unsafe extern "C" fn MultiMapAofRewrite( 316 | aof: *mut ffi::RedisModuleIO, 317 | key: *mut ffi::RedisModuleString, 318 | value: *mut raw::c_void, 319 | ) { 320 | let map = &*(value as *mut MultiMap); 321 | let insert_cmd = CString::new("multimap.insert").unwrap(); 322 | let flags = CString::new("scc").unwrap(); 323 | 324 | for (k, v) in map { 325 | let actual_key = CString::new(k.as_bytes()).unwrap(); 326 | 327 | for value in v { 328 | let actual_value = CString::new(value.as_bytes()).unwrap(); 329 | 330 | ffi::RedisModule_EmitAOF.unwrap()( 331 | aof, 332 | insert_cmd.as_ptr(), 333 | flags.as_ptr(), 334 | key, 335 | actual_key.as_ptr(), 336 | actual_value.as_ptr(), 337 | ); 338 | } 339 | } 340 | } 341 | 342 | #[no_mangle] 343 | pub unsafe extern "C" fn RedisModule_OnLoad( 344 | ctx: *mut ffi::RedisModuleCtx, 345 | _argv: *mut *mut ffi::RedisModuleString, 346 | _argc: libc::c_int, 347 | ) -> libc::c_int { 348 | if ffi::ExportedRedisModule_Init(ctx, "rmultimap\0".as_ptr(), 1, ffi::APIVER_1) == 1 { 349 | return ffi::REDIS_ERR; 350 | } 351 | 352 | let mut type_functions = ffi::RedisModuleTypeMethods { 353 | version: 1, 354 | rdb_load: Some(MultiMapRdbLoad), 355 | rdb_save: Some(MultiMapRdbSave), 356 | aof_rewrite: Some(MultiMapAofRewrite), 357 | free: Some(MultiMapFree), 358 | mem_usage: None, 359 | digest: None, 360 | }; 361 | // We reuse this flag so declare it at the beginning of the function. 362 | let write_flag = CString::new("write").unwrap(); 363 | 364 | let type_name = CString::new("rmultimap").unwrap(); 365 | let out_type = 366 | ffi::RedisModule_CreateDataType.unwrap()(ctx, type_name.as_ptr(), 0, &mut type_functions); 367 | if out_type.is_null() { 368 | return ffi::REDIS_ERR; 369 | } else { 370 | MULTI_MAP_TYPE = out_type; 371 | } 372 | 373 | let insert_cmd = CString::new("multimap.insert").unwrap(); 374 | if ffi::RedisModule_CreateCommand.unwrap()( 375 | ctx, 376 | insert_cmd.as_ptr(), 377 | Some(MultiMapInsert_RedisCommand), 378 | write_flag.as_ptr(), 379 | 1, 380 | 1, 381 | 1, 382 | ) == ffi::REDIS_ERR 383 | { 384 | return ffi::REDIS_ERR; 385 | } 386 | 387 | let len_cmd = CString::new("multimap.len").unwrap(); 388 | let read_fast_flag = CString::new("readonly fast").unwrap(); 389 | if ffi::RedisModule_CreateCommand.unwrap()( 390 | ctx, 391 | len_cmd.as_ptr(), 392 | Some(MultiMapLength_RedisCommand), 393 | read_fast_flag.as_ptr(), 394 | 1, 395 | 1, 396 | 1, 397 | ) == ffi::REDIS_ERR 398 | { 399 | return ffi::REDIS_ERR; 400 | } 401 | 402 | let values_cmd = CString::new("multimap.values").unwrap(); 403 | let read_flag = CString::new("readonly").unwrap(); 404 | if ffi::RedisModule_CreateCommand.unwrap()( 405 | ctx, 406 | values_cmd.as_ptr(), 407 | Some(MultiMapValues_RedisCommand), 408 | read_flag.as_ptr(), 409 | 1, 410 | 1, 411 | 1, 412 | ) == ffi::REDIS_ERR 413 | { 414 | return ffi::REDIS_ERR; 415 | } 416 | 417 | let del_cmd = CString::new("multimap.del").unwrap(); 418 | if ffi::RedisModule_CreateCommand.unwrap()( 419 | ctx, 420 | del_cmd.as_ptr(), 421 | Some(MultiMapDelete_RedisCommand), 422 | write_flag.as_ptr(), 423 | 1, 424 | 1, 425 | 1, 426 | ) == ffi::REDIS_ERR 427 | { 428 | return ffi::REDIS_ERR; 429 | } 430 | 431 | ffi::REDIS_OK 432 | } 433 | -------------------------------------------------------------------------------- /src/multi_map.rs: -------------------------------------------------------------------------------- 1 | //! The `multi_map` module represents an implementation of a `MultiMap` to be used by the 2 | //! Redis module system. 3 | use std::collections::hash_map::Iter; 4 | use std::collections::HashMap; 5 | use std::iter::IntoIterator; 6 | 7 | /// `MultiMap` represents a map of `String` keys to a `Vec` of `String` values. 8 | #[derive(Clone, Debug, Default)] 9 | pub struct MultiMap { 10 | inner: HashMap>, 11 | } 12 | 13 | impl MultiMap { 14 | /// New returns an initialized `MultiMap`. 15 | pub fn new() -> MultiMap { 16 | MultiMap { 17 | inner: HashMap::new(), 18 | } 19 | } 20 | 21 | /// Insert will add all values passed as an argument to the specified key. 22 | pub fn insert(&mut self, key: K, values: I) 23 | where 24 | K: Into, 25 | I: IntoIterator, 26 | { 27 | let entry = self.inner.entry(key.into()).or_insert_with(|| vec![]); 28 | values.into_iter().for_each(|item| entry.push(item)); 29 | } 30 | 31 | /// Given a key, return the length of the values. 32 | pub fn key_len>(&self, key: K) -> usize { 33 | let values = self.inner.get(&key.into()); 34 | match values { 35 | Some(v) => v.len(), 36 | None => 0, 37 | } 38 | } 39 | 40 | /// Given a key, return a list of the values. 41 | pub fn values>(&self, key: K) -> Option<&Vec> { 42 | self.inner.get(&key.into()) 43 | } 44 | 45 | /// Given a key, remove it from the map. A return value of 0 indicates that the key didn't 46 | /// priorly exist while 1 means it was successfully removed. 47 | pub fn delete_key>(&mut self, key: K) -> i64 { 48 | const ONE: i64 = 1; 49 | const ZERO: i64 = 0; 50 | let del_k = &key.into(); 51 | 52 | match self.inner.get(del_k) { 53 | Some(_) => { 54 | self.inner.remove(del_k); 55 | ONE 56 | } 57 | None => ZERO, 58 | } 59 | } 60 | 61 | /// Return the number of pairs in the map. 62 | pub fn len(&self) -> usize { 63 | self.inner.len() 64 | } 65 | } 66 | 67 | impl<'a> IntoIterator for &'a MultiMap { 68 | type Item = (&'a String, &'a Vec); 69 | type IntoIter = Iter<'a, String, Vec>; 70 | 71 | fn into_iter(self) -> Self::IntoIter { 72 | self.inner.iter() 73 | } 74 | } 75 | --------------------------------------------------------------------------------