Skip to content

C++ RedisModule API #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 31 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6acb933
C++ RedisModule API
rafie Aug 2, 2022
25c4591
4/8/22
ephraimfeldblum Aug 4, 2022
18c9585
morning 7/8/22
ephraimfeldblum Aug 7, 2022
894e416
7/8/22
ephraimfeldblum Aug 7, 2022
7f70211
morning 8/8/22
ephraimfeldblum Aug 8, 2022
09c4569
lunch 8/8/22
ephraimfeldblum Aug 8, 2022
d2d8cf0
interrogating exceptions
ephraimfeldblum Aug 9, 2022
156f14b
Added 7.0 & unstable APIs
rafie Aug 9, 2022
6996758
Fix to 7.0 API
rafie Aug 9, 2022
c728eb4
EoD 10/8/22 pain and despair
ephraimfeldblum Aug 10, 2022
d624e8b
noon 11/8/22
ephraimfeldblum Aug 11, 2022
83383b0
11/8/22 nearly EoD. frustrated with forwarding
ephraimfeldblum Aug 11, 2022
8bb52de
why are there so many opaque structs that are unconstructable? how ar…
ephraimfeldblum Aug 14, 2022
bda37fb
need to rethink string::operator=
ephraimfeldblum Aug 15, 2022
9554c98
CallReply blood magic needs to be thought through thoroughly
ephraimfeldblum Aug 15, 2022
e1131de
cxx module compiles
ephraimfeldblum Aug 16, 2022
27c696a
but fails to load. out to lunch.
ephraimfeldblum Aug 16, 2022
1febd88
#ifdef __cplusplus extern C. as and where relevant
ephraimfeldblum Aug 16, 2022
d20e8e5
minor
ephraimfeldblum Aug 16, 2022
ccf8ba1
example passed. still far from complete though
ephraimfeldblum Aug 16, 2022
f055505
unwrapping cppclasses to cstruct*s using cpp20 concepts
ephraimfeldblum Aug 17, 2022
7e7c349
unwrapping cppclasses to cstruct*s using cpp20 concepts
ephraimfeldblum Aug 17, 2022
b9d077b
slightly more readable: less declval, more auto
ephraimfeldblum Aug 17, 2022
19d0ae5
no owned strings, only retained refs
ephraimfeldblum Aug 17, 2022
60f2f9f
passing responsibility to std::unique_ptr where appropriate
ephraimfeldblum Aug 21, 2022
880f6d1
simplifying ownership
ephraimfeldblum Aug 22, 2022
87b9d89
encapsulating deleters
ephraimfeldblum Aug 22, 2022
13d8695
added some missing functionality. added class Context. moved BlockCli…
ephraimfeldblum Aug 23, 2022
8bbb1a5
callbacks working. just needed to use template parameters.
ephraimfeldblum Aug 25, 2022
93a21f4
CRTP should be CRTP. No virtual functions, that way lies madness. Arg…
ephraimfeldblum Aug 25, 2022
5f1f146
slight changes
ephraimfeldblum Aug 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
39 changes: 23 additions & 16 deletions example/Makefile
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
#set environment variable RM_INCLUDE_DIR to the location of redismodule.h
ifndef RM_INCLUDE_DIR
RM_INCLUDE_DIR=../
endif

ifndef RMUTIL_LIBDIR
RMUTIL_LIBDIR=../rmutil
endif
RMUTIL_LIBDIR ?= ../rmutil

# find the OS
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')

# Compile flags for linux / osx
ifeq ($(uname_S),Linux)
SHOBJ_CFLAGS ?= -fno-common -g -ggdb
SHOBJ_LDFLAGS ?= -shared -Bsymbolic
SHOBJ_CFLAGS ?= -fno-common -g -ggdb
SHOBJ_LDFLAGS ?= -shared -Bsymbolic
else
SHOBJ_CFLAGS ?= -dynamic -fno-common -g -ggdb
SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
SHOBJ_CFLAGS ?= -dynamic -fno-common -g -ggdb
SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
endif
CFLAGS = -I$(RM_INCLUDE_DIR) -Wall -g -fPIC -lc -lm -std=gnu99
CC=gcc

all: rmutil module.so
GCC_FLAGS = -I.. -I../include -Wall -g -fPIC -lc -lm -std=gnu99
GCC=gcc

GXX_FLAGS = -I.. -I../include -Wall -g -fPIC -lc -lm -std=c++20
GXX=g++

all: rmutil module.so module-cxx.so

rmutil: FORCE
rmutil: $(RMUTIL_LIBDIR)/librmutil.a
$(MAKE) -C $(RMUTIL_LIBDIR)

%.o : %.c
$(GCC) -c $(GCC_FLAGS) -Wall -o $@ $^

%.o : %.cc
$(GXX) -c $(GXX_FLAGS) -Wall -o $@ $^

module.so: module.o
$(LD) -o $@ module.o $(SHOBJ_LDFLAGS) $(LIBS) -L$(RMUTIL_LIBDIR) -lrmutil -lc
$(GCC) -o $@ $^ $(GCC_FLAGS) $(SHOBJ_LDFLAGS) $(LIBS) -L$(RMUTIL_LIBDIR) -lrmutil -lc

module-cxx.so: module-cxx.o
$(GXX) -o $@ $^ $(GXX_FLAGS) $(SHOBJ_LDFLAGS) $(LIBS) -L$(RMUTIL_LIBDIR) -lrmutil -lc

clean:
rm -rf *.xo *.so *.o
Expand Down
Binary file added example/dump.rdb
Binary file not shown.
200 changes: 200 additions & 0 deletions example/module-cxx.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,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);
19 changes: 10 additions & 9 deletions example/module.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#include "../redismodule.h"
#include "../rmutil/util.h"
#include "../rmutil/strings.h"
#include "../rmutil/test_util.h"

#define REDISMODULE_MAIN
#define REDISMODULE_EXPERIMENTAL_API
#include "redismodule.h"

#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.
Expand Down Expand Up @@ -123,16 +127,13 @@ int TestModule(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
}

int RedisModule_OnLoad(RedisModuleCtx *ctx) {

// Register the module itself
if (RedisModule_Init(ctx, "example", 1, REDISMODULE_APIVER_1) ==
REDISMODULE_ERR) {
if (RedisModule_Init(ctx, "example", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}

// register example.parse - the default registration syntax
if (RedisModule_CreateCommand(ctx, "example.parse", ParseCommand, "readonly",
1, 1, 1) == REDISMODULE_ERR) {
if (RedisModule_CreateCommand(ctx, "example.parse", ParseCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions 6.2/redismodule.h → include/6.2/redismodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ct
REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR;
REDISMODULE_API const char *(*RedisModule_GetCurrentCommandName)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterDefragFunc)(RedisModuleCtx *ctx, RedisModuleDefragFunc func) REDISMODULE_ATTR;
REDISMODULE_API void *(*RedisModule_DefragAlloc)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString *(*RedisModule_DefragRedisModuleString)(RedisModuleDefragCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
Expand Down Expand Up @@ -1118,6 +1119,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(AuthenticateClientWithUser);
REDISMODULE_GET_API(GetClientCertificate);
REDISMODULE_GET_API(GetCommandKeys);
REDISMODULE_GET_API(GetCurrentCommandName);
REDISMODULE_GET_API(RegisterDefragFunc);
REDISMODULE_GET_API(DefragAlloc);
REDISMODULE_GET_API(DefragRedisModuleString);
Expand Down
Loading