added more utils and hgetset to module

master
Dvir Volk 9 years ago
parent d17147747c
commit 603f390756

@ -1,6 +1,7 @@
#include "../redismodule.h" #include "../redismodule.h"
#include "../rmutil/util.h" #include "../rmutil/util.h"
#include "../rmutil/strings.h" #include "../rmutil/strings.h"
#include "../rmutil/test_util.h"
/* EXAMPLE.PARSE [SUM <x> <y>] | [PROD <x> <y>] /* EXAMPLE.PARSE [SUM <x> <y>] | [PROD <x> <y>]
* Demonstrates the automatic arg parsing utility. * Demonstrates the automatic arg parsing utility.
@ -9,47 +10,136 @@
*/ */
int ParseCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int ParseCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// we must have at least 4 args // we must have at least 4 args
if (argc < 4) { if (argc < 4) {
return RedisModule_WrongArity(ctx); return RedisModule_WrongArity(ctx);
} }
// init auto memory for created strings
RedisModule_AutoMemory(ctx);
long long x, y;
// init auto memory for created strings // If we got SUM - return the sum of 2 consecutive arguments
RedisModule_AutoMemory(ctx); if (RMUtil_ParseArgsAfter("SUM", argv, argc, "ll", &x, &y) ==
long long x,y; REDISMODULE_OK) {
RedisModule_ReplyWithLongLong(ctx, x + y);
return REDISMODULE_OK;
}
// If we got PROD - return the product of 2 consecutive arguments
if (RMUtil_ParseArgsAfter("PROD", argv, argc, "ll", &x, &y) ==
REDISMODULE_OK) {
RedisModule_ReplyWithLongLong(ctx, x * y);
return REDISMODULE_ERR;
}
// something is fishy...
RedisModule_ReplyWithError(ctx, "Invalid arguments");
// If we got SUM - return the sum of 2 consecutive arguments return REDISMODULE_OK;
if (RMUtil_ParseArgsAfter("SUM", argv, argc, "ll", &x, &y) == REDISMODULE_OK) { }
RedisModule_ReplyWithLongLong(ctx, x+y);
return REDISMODULE_OK;
}
// If we got PROD - return the product of 2 consecutive arguments
if (RMUtil_ParseArgsAfter("PROD", argv, argc, "ll", &x, &y) == REDISMODULE_OK) {
RedisModule_ReplyWithLongLong(ctx, x*y);
return REDISMODULE_OK;
}
// something is fishy... /*
RedisModule_ReplyWithError(ctx, "Invalid arguments"); * 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
*/
int HGetSetCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// we need EXACTLY 4 arguments
if (argc != 4) {
return RedisModule_WrongArity(ctx);
}
RedisModule_AutoMemory(ctx);
// open the key and make sure it's indeed a HASH and not empty
RedisModuleKey *key =
RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_HASH &&
RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_EMPTY) {
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
}
// get the current value of the hash element
RedisModuleCallReply *rep =
RedisModule_Call(ctx, "HGET", "ss", argv[1], argv[2]);
RMUTIL_ASSERT_NOERROR(rep);
// set the new value of the element
RedisModuleCallReply *srep =
RedisModule_Call(ctx, "HSET", "sss", argv[1], argv[2], argv[3]);
RMUTIL_ASSERT_NOERROR(srep);
// if the value was null before - we just return null
if (RedisModule_CallReplyType(rep) == REDISMODULE_REPLY_NULL) {
RedisModule_ReplyWithNull(ctx);
return REDISMODULE_OK; return REDISMODULE_OK;
}
// forward the HGET reply to the client
RedisModule_ReplyWithCallReply(ctx, rep);
return REDISMODULE_OK;
} }
// Test the the PARSE command
int testParse(RedisModuleCtx *ctx) {
RedisModuleCallReply *r =
RedisModule_Call(ctx, "example.parse", "ccc", "SUM", "5", "2");
RMUtil_Assert(RedisModule_CallReplyType(r) == REDISMODULE_REPLY_INTEGER);
RMUtil_AssertReplyEquals(r, "7");
r = RedisModule_Call(ctx, "example.parse", "ccc", "PROD", "5", "2");
RMUtil_Assert(RedisModule_CallReplyType(r) == REDISMODULE_REPLY_INTEGER);
RMUtil_AssertReplyEquals(r, "10");
return 0;
}
// test the HGETSET command
int testHgetSet(RedisModuleCtx *ctx) {
RedisModuleCallReply *r = RedisModule_Call(ctx, "example.hgetset", "ccc", "foo", "bar", "baz");
RMUtil_Assert(RedisModule_CallReplyType(r) != REDISMODULE_REPLY_ERROR);
r = RedisModule_Call(ctx, "example.hgetset", "ccc", "foo", "bar", "bag");
RMUtil_Assert(RedisModule_CallReplyType(r) == REDISMODULE_REPLY_STRING);
RMUtil_AssertReplyEquals(r, "baz");
r = RedisModule_Call(ctx, "example.hgetset", "ccc", "foo", "bar", "bang");
RMUtil_AssertReplyEquals(r, "bag");
return 0;
}
// Unit test entry point for the module
int TestModule(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
RedisModule_AutoMemory(ctx);
RMUtil_Test(testParse);
RMUtil_Test(testHgetSet);
RedisModule_ReplyWithSimpleString(ctx, "PASS");
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx) { int RedisModule_OnLoad(RedisModuleCtx *ctx) {
if (RedisModule_Init(ctx,"example",1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) { // Register the module itself
if (RedisModule_Init(ctx, "example", 1, REDISMODULE_APIVER_1) ==
REDISMODULE_ERR) {
return 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) {
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "example.parse", ParseCommand, "readonly", 1,1,1) // register example.hgetset - using the shortened utility registration macro
== REDISMODULE_ERR) { RMUtil_RegisterWriteCmd(ctx, "example.hgetset", HGetSetCommand);
return REDISMODULE_ERR;
}
return REDISMODULE_OK; // register the unit test
RMUtil_RegisterWriteCmd(ctx, "example.test", TestModule);
return REDISMODULE_OK;
} }

@ -0,0 +1,67 @@
#ifndef __TEST_UTIL_H__
#define __TEST_UTIL_H__
#include "util.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#define RMUtil_Test(f) \
if (argc < 2 || RMUtil_ArgExists(__STRING(f), argv, argc, 1)) { \
int rc = f(ctx); \
if (rc != REDISMODULE_OK) { \
RedisModule_ReplyWithError(ctx, "Test " __STRING(f) " FAILED"); \
return REDISMODULE_ERR;\
}\
}
#define RMUtil_Assert(expr) if (!(expr)) { fprintf (stderr, "Assertion '%s' Failed\n", __STRING(expr)); return REDISMODULE_ERR; }
#define RMUtil_AssertReplyEquals(rep, cstr) RMUtil_Assert( \
RMUtil_StringEquals(RedisModule_CreateStringFromCallReply(rep), RedisModule_CreateString(ctx, cstr, strlen(cstr))) \
)
#
/**
* Create an arg list to pass to a redis command handler manually, based on the format in fmt.
* The accepted format specifiers are:
* c - for null terminated c strings
* s - for RedisModuleString* objects
* l - for longs
*
* Example: RMUtil_MakeArgs(ctx, &argc, "clc", "hello", 1337, "world");
*
* Returns an array of RedisModuleString pointers. The size of the array is store in argcp
*/
RedisModuleString **RMUtil_MakeArgs(RedisModuleCtx *ctx, int *argcp, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
RedisModuleString **argv = calloc(strlen(fmt), sizeof(RedisModuleString*));
int argc = 0;
const char *p = fmt;
while(*p) {
if (*p == 'c') {
char *cstr = va_arg(ap,char*);
argv[argc++] = RedisModule_CreateString(ctx, cstr, strlen(cstr));
} else if (*p == 's') {
argv[argc++] = va_arg(ap,void*);;
} else if (*p == 'l') {
long ll = va_arg(ap,long long);
argv[argc++] = RedisModule_CreateStringFromLongLong(ctx, ll);
} else {
goto fmterr;
}
p++;
}
*argcp = argc;
return argv;
fmterr:
free(argv);
return NULL;
}
#endif

@ -5,7 +5,7 @@
#include <stdarg.h> #include <stdarg.h>
// make sure the response is not NULL or an error, and if it is sends the error to the client and exit the current function // make sure the response is not NULL or an error, and if it is sends the error to the client and exit the current function
#define REDIS_ASSERT_NOERROR(r) \ #define RMUTIL_ASSERT_NOERROR(r) \
if (r == NULL) { \ if (r == NULL) { \
return RedisModule_ReplyWithError(ctx,"ERR reply is NULL"); \ return RedisModule_ReplyWithError(ctx,"ERR reply is NULL"); \
} else if (RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) { \ } else if (RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) { \
@ -14,6 +14,13 @@
} }
#define __rmutil_register_cmd(ctx, cmd, f, mode) if (RedisModule_CreateCommand(ctx, cmd, f, mode, \
1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR;
#define RMUtil_RegisterReadCmd(ctx, cmd, f) __rmutil_register_cmd(ctx, cmd, f, "readonly") }
#define RMUtil_RegisterWriteCmd(ctx, cmd, f) __rmutil_register_cmd(ctx, cmd, f, "write")
/* RedisModule utilities. */ /* RedisModule utilities. */
/* Return the offset of an arg if it exists in the arg list, or 0 if it's not there */ /* Return the offset of an arg if it exists in the arg list, or 0 if it's not there */

Loading…
Cancel
Save