-
Notifications
You must be signed in to change notification settings - Fork 206
Expand file tree
/
Copy pathmodule-cxx.cc
More file actions
200 lines (161 loc) · 5.77 KB
/
module-cxx.cc
File metadata and controls
200 lines (161 loc) · 5.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#define REDISMODULE_MAIN
#define REDISMODULE_EXPERIMENTAL_API
#include "cxx/moduleapi.hxx"
#include "rmutil/util.h"
#include "rmutil/strings.h"
#include "rmutil/test_util.h"
/* EXAMPLE.PARSE [SUM <x> <y>] | [PROD <x> <y>]
* Demonstrates the automatic arg parsing utility.
* If the command receives "SUM <x> <y>" it returns their sum
* If it receives "PROD <x> <y>" it returns their product
*/
using namespace Redis;
int ArgExists(const char *arg, const Args& args, int offset) {
size_t larg = strlen(arg);
for (; offset < args.Size(); offset++) {
size_t l;
const char *carg = RedisModule_StringPtrLen(args[offset], &l);
if (l != larg) continue;
if (carg != NULL && strncasecmp(carg, arg, larg) == 0) {
return offset;
}
}
return 0;
}
#define ASSERT_NOERROR(ctx, r) \
if (r == nullptr) { \
return ctx.ReplyWithError("ERR reply is NULL"); \
} else if (r.Type() == REDISMODULE_REPLY_ERROR) { \
ctx.ReplyWithCallReply(r); \
return REDISMODULE_ERR; \
}
#define AssertReplyEquals(rep, cstr) \
RMUtil_Assert( \
RMUtil_StringEquals( \
rep.CreateString(), \
String(cstr, strlen(cstr)) \
) \
)
#define TEST(f) \
if (args.Size() < 2 || \
ArgExists(__STRING(f), args, 1)) { \
int rc = f(ctx); \
if (rc != REDISMODULE_OK) { \
ctx.ReplyWithError("Test " __STRING(f) " FAILED");\
return REDISMODULE_ERR; \
} \
}
#define RegisterWriteCmd(ctx, cmd, f) \
if (Command::Create<f>(ctx, cmd, "write", 1, 1, 1) == REDISMODULE_ERR) \
return REDISMODULE_ERR;
struct Parse : CmdCRTP<Parse> {
Parse(Context ctx, const Args& args) : CmdCRTP(ctx, args) {
// we must have at least 4 args
if (_args.Size() < 4) {
throw _ctx.WrongArity();
}
}
int operator()() {
// init auto memory for created strings
// RedisModule_AutoMemory(ctx);
long long x, y;
// If we got SUM - return the sum of 2 consecutive arguments
if (RMUtil_ParseArgsAfter("SUM", _args, _args.Size(), "ll", &x, &y) == REDISMODULE_OK) {
_ctx.ReplyWithLongLong(x + y);
return REDISMODULE_OK;
}
// If we got PROD - return the product of 2 consecutive arguments
if (RMUtil_ParseArgsAfter("PROD", _args, _args.Size(), "ll", &x, &y) == REDISMODULE_OK) {
_ctx.ReplyWithLongLong(x * y);
return REDISMODULE_OK;
}
// something is fishy...
_ctx.ReplyWithError("Invalid arguments");
return REDISMODULE_ERR;
}
};
/*
* example.HGETSET <key> <element> <value>
* Atomically set a value in a HASH key to <value> and return its value before
* the HSET.
*
* Basically atomic HGET + HSET
*/
struct HGetSet : CmdCRTP<HGetSet> {
HGetSet(Context ctx, const Args& args) : CmdCRTP(ctx, args) {
// we need EXACTLY 4 arguments
if (_args.Size() != 4) {
throw _ctx.WrongArity();
}
}
int operator()() {
// RedisModule_AutoMemory(ctx);
// open the key and make sure it's indeed a HASH and not empty
Key key(_ctx, _args[1], REDISMODULE_READ | REDISMODULE_WRITE);
if (key.Type() != REDISMODULE_KEYTYPE_HASH &&
key.Type() != REDISMODULE_KEYTYPE_EMPTY) {
return _ctx.ReplyWithError(REDISMODULE_ERRORMSG_WRONGTYPE);
}
// get the current value of the hash element
auto rep = _ctx.Call("HGET", "ss", _args[1], _args[2]);
ASSERT_NOERROR(_ctx, rep);
// if (!rep) { _ctx.Reply(rep); }
// set the new value of the element
auto srep = _ctx.Call("HSET", "sss", _args[1], _args[2], _args[3]);
ASSERT_NOERROR(_ctx, srep);
// if the value was null before - we just return null
if (rep.Type() == REDISMODULE_REPLY_NULL) {
return _ctx.ReplyWithNull();
}
// forward the HGET reply to the client
_ctx.ReplyWithCallReply(rep);
return REDISMODULE_OK;
}
};
// Test the the PARSE command
int testParse(Context ctx) {
auto r = ctx.Call("example.parse", "ccc", "SUM", "5", "2");
RMUtil_Assert(r.Type() == REDISMODULE_REPLY_INTEGER);
AssertReplyEquals(r, "7");
r = ctx.Call("example.parse", "ccc", "PROD", "5", "2");
RMUtil_Assert(r.Type() == REDISMODULE_REPLY_INTEGER);
AssertReplyEquals(r, "10");
return REDISMODULE_OK;
}
// test the HGETSET command
int testHgetSet(Context ctx) {
auto r = ctx.Call("example.hgetset", "ccc", "foo", "bar", "baz");
RMUtil_Assert(r.Type() != REDISMODULE_REPLY_ERROR);
r = ctx.Call("example.hgetset", "ccc", "foo", "bar", "bag");
RMUtil_Assert(r.Type() == REDISMODULE_REPLY_STRING);
AssertReplyEquals(r, "baz");
r = ctx.Call("example.hgetset", "ccc", "foo", "bar", "bang");
AssertReplyEquals(r, "bag");
return REDISMODULE_OK;
}
// Unit test entry point for the module
int TestModule(Context ctx, const Args& args) {
// RedisModule_AutoMemory(ctx);
TEST(testParse);
TEST(testHgetSet);
ctx.ReplyWithSimpleString("PASS");
return REDISMODULE_OK;
}
extern "C" {
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **, int) {
// Register the module itself
if (RedisModule_Init(ctx, "example", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
// register example.parse - the default registration syntax
if (Command::Create<Parse::cmdfunc>(ctx, "example.parse", "readonly", 1, 1, 1) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
// register example.hgetset - using the shortened utility registration macro
RegisterWriteCmd(ctx, "example.hgetset", HGetSet::cmdfunc);
// register the unit test
RegisterWriteCmd(ctx, "example.test", TestModule);
return REDISMODULE_OK;
}
}
// REDIS_MODULE(MyModule);