Unverified Commit bdbf5eed authored by Ariel Shtul's avatar Ariel Shtul Committed by GitHub
Browse files

Module api support for RESP3 (#8521)



Add new Module APS for RESP3 responses:
- RM_ReplyWithMap
- RM_ReplyWithSet
- RM_ReplyWithAttribute
- RM_ReplySetMapLength
- RM_ReplySetSetLength
- RM_ReplySetAttributeLength
- RM_ReplyWithBool

Deprecate REDISMODULE_POSTPONED_ARRAY_LEN in favor of a generic REDISMODULE_POSTPONED_LEN

Improve documentation
Add tests
Co-authored-by: default avatarGuy Benoish <guy.benoish@redislabs.com>
Co-authored-by: default avatarOran Agra <oran@redislabs.com>
parent 4bd77483
...@@ -660,8 +660,8 @@ void moduleFreeContext(RedisModuleCtx *ctx) { ...@@ -660,8 +660,8 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
ctx->postponed_arrays_count = 0; ctx->postponed_arrays_count = 0;
serverLog(LL_WARNING, serverLog(LL_WARNING,
"API misuse detected in module %s: " "API misuse detected in module %s: "
"RedisModule_ReplyWithArray(REDISMODULE_POSTPONED_ARRAY_LEN) " "RedisModule_ReplyWith*(REDISMODULE_POSTPONED_LEN) "
"not matched by the same number of RedisModule_SetReplyArrayLen() " "not matched by the same number of RedisModule_SetReply*Len() "
"calls.", "calls.",
ctx->module->name); ctx->module->name);
} }
...@@ -1448,6 +1448,18 @@ int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const cha ...@@ -1448,6 +1448,18 @@ int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const cha
* *
* if (... some condition ...) * if (... some condition ...)
* return RedisModule_ReplyWithLongLong(ctx,mycount); * return RedisModule_ReplyWithLongLong(ctx,mycount);
*
* ### Reply with collection functions
*
* After starting a collection reply, the module must make calls to other
* `ReplyWith*` style functions in order to emit the elements of the collection.
* Collection types include: Array, Map, Set and Attribute.
*
* When producing collections with a number of elements that is not known
* beforehand, the function can be called with a special flag
* REDISMODULE_POSTPONED_LEN (REDISMODULE_POSTPONED_ARRAY_LEN in the past),
* and the actual number of elements can be later set with RM_ReplySet*Length()
* call (which will set the latest "open" count if there are multiple ones).
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Send an error about the number of arguments given to the command, /* Send an error about the number of arguments given to the command,
...@@ -1537,35 +1549,129 @@ int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) { ...@@ -1537,35 +1549,129 @@ int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) {
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* Reply with an array type of 'len' elements. However 'len' other calls #define COLLECTION_REPLY_ARRAY 1
* to `ReplyWith*` style functions must follow in order to emit the elements #define COLLECTION_REPLY_MAP 2
* of the array. #define COLLECTION_REPLY_SET 3
* #define COLLECTION_REPLY_ATTRIBUTE 4
* When producing arrays with a number of element that is not known beforehand
* the function can be called with the special count int moduleReplyWithCollection(RedisModuleCtx *ctx, long len, int type) {
* REDISMODULE_POSTPONED_ARRAY_LEN, and the actual number of elements can be
* later set with RedisModule_ReplySetArrayLength() (which will set the
* latest "open" count if there are multiple ones).
*
* The function always returns REDISMODULE_OK. */
int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
client *c = moduleGetReplyClient(ctx); client *c = moduleGetReplyClient(ctx);
if (c == NULL) return REDISMODULE_OK; if (c == NULL) return REDISMODULE_OK;
if (len == REDISMODULE_POSTPONED_ARRAY_LEN) { if (len == REDISMODULE_POSTPONED_LEN) {
ctx->postponed_arrays = zrealloc(ctx->postponed_arrays,sizeof(void*)* ctx->postponed_arrays = zrealloc(ctx->postponed_arrays,sizeof(void*)*
(ctx->postponed_arrays_count+1)); (ctx->postponed_arrays_count+1));
ctx->postponed_arrays[ctx->postponed_arrays_count] = ctx->postponed_arrays[ctx->postponed_arrays_count] =
addReplyDeferredLen(c); addReplyDeferredLen(c);
ctx->postponed_arrays_count++; ctx->postponed_arrays_count++;
} else if (len == 0) {
switch (type) {
case COLLECTION_REPLY_ARRAY:
addReply(c, shared.emptyarray);
break;
case COLLECTION_REPLY_MAP:
addReply(c, shared.emptymap[c->resp]);
break;
case COLLECTION_REPLY_SET:
addReply(c, shared.emptyset[c->resp]);
break;
case COLLECTION_REPLY_ATTRIBUTE:
addReplyAttributeLen(c,len);
break;
default:
serverPanic("Invalid module empty reply type %d", type); }
} else { } else {
addReplyArrayLen(c,len); switch (type) {
case COLLECTION_REPLY_ARRAY:
addReplyArrayLen(c,len);
break;
case COLLECTION_REPLY_MAP:
addReplyMapLen(c,len);
break;
case COLLECTION_REPLY_SET:
addReplySetLen(c,len);
break;
case COLLECTION_REPLY_ATTRIBUTE:
addReplyAttributeLen(c,len);
break;
default:
serverPanic("Invalid module reply type %d", type);
}
} }
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* Reply to the client with a null array, simply null in RESP3 /* Reply with an array type of 'len' elements.
*
* After starting an array reply, the module must make `len` calls to other
* `ReplyWith*` style functions in order to emit the elements of the array.
* See Reply APIs section for more details.
*
* Use RM_ReplySetArrayLength() to set deferred length.
*
* The function always returns REDISMODULE_OK. */
int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_ARRAY);
}
/* Reply with a RESP3 Map type of 'len' pairs.
* Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3.
*
* After starting a map reply, the module must make `len*2` calls to other
* `ReplyWith*` style functions in order to emit the elements of the map.
* See Reply APIs section for more details.
*
* If the connected client is using RESP2, the reply will be converted to a flat
* array.
*
* Use RM_ReplySetMapLength() to set deferred length.
*
* The function always returns REDISMODULE_OK. */
int RM_ReplyWithMap(RedisModuleCtx *ctx, long len) {
return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_MAP);
}
/* Reply with a RESP3 Set type of 'len' elements.
* Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3.
*
* After starting a set reply, the module must make `len` calls to other
* `ReplyWith*` style functions in order to emit the elements of the set.
* See Reply APIs section for more details.
*
* If the connected client is using RESP2, the reply will be converted to an
* array type.
*
* Use RM_ReplySetSetLength() to set deferred length.
*
* The function always returns REDISMODULE_OK. */
int RM_ReplyWithSet(RedisModuleCtx *ctx, long len) {
return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_SET);
}
/* Add attributes (metadata) to the reply. Should be done before adding the
* actual reply. see https://github.com/antirez/RESP3/blob/master/spec.md#attribute-type
*
* After starting an attributes reply, the module must make `len*2` calls to other
* `ReplyWith*` style functions in order to emit the elements of the attribtute map.
* See Reply APIs section for more details.
*
* Use RM_ReplySetAttributeLength() to set deferred length.
*
* Not supported by RESP2 and will return REDISMODULE_ERR, otherwise
* the function always returns REDISMODULE_OK. */
int RM_ReplyWithAttribute(RedisModuleCtx *ctx, long len) {
if (ctx->client->resp == 2) return REDISMODULE_ERR;
return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_ATTRIBUTE);
}
/* Reply to the client with a null array, simply null in RESP3,
* null array in RESP2. * null array in RESP2.
* *
* Note: In RESP3 there's no difference between Null reply and
* NullArray reply, so to prevent ambiguity it's better to avoid
* using this API and use RedisModule_ReplyWithNull instead.
*
* The function always returns REDISMODULE_OK. */ * The function always returns REDISMODULE_OK. */
int RM_ReplyWithNullArray(RedisModuleCtx *ctx) { int RM_ReplyWithNullArray(RedisModuleCtx *ctx) {
client *c = moduleGetReplyClient(ctx); client *c = moduleGetReplyClient(ctx);
...@@ -1584,8 +1690,42 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) { ...@@ -1584,8 +1690,42 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) {
return REDISMODULE_OK; return REDISMODULE_OK;
} }
void moduleReplySetCollectionLength(RedisModuleCtx *ctx, long len, int type) {
client *c = moduleGetReplyClient(ctx);
if (c == NULL) return;
if (ctx->postponed_arrays_count == 0) {
serverLog(LL_WARNING,
"API misuse detected in module %s: "
"RedisModule_ReplySet*Length() called without previous "
"RedisModule_ReplyWith*(ctx,REDISMODULE_POSTPONED_LEN) "
"call.", ctx->module->name);
return;
}
ctx->postponed_arrays_count--;
switch(type) {
case COLLECTION_REPLY_ARRAY:
setDeferredArrayLen(c,ctx->postponed_arrays[ctx->postponed_arrays_count],len);
break;
case COLLECTION_REPLY_MAP:
setDeferredMapLen(c,ctx->postponed_arrays[ctx->postponed_arrays_count],len);
break;
case COLLECTION_REPLY_SET:
setDeferredSetLen(c,ctx->postponed_arrays[ctx->postponed_arrays_count],len);
break;
case COLLECTION_REPLY_ATTRIBUTE:
setDeferredAttributeLen(c,ctx->postponed_arrays[ctx->postponed_arrays_count],len);
break;
default:
serverPanic("Invalid module reply type %d", type);
}
if (ctx->postponed_arrays_count == 0) {
zfree(ctx->postponed_arrays);
ctx->postponed_arrays = NULL;
}
}
/* When RedisModule_ReplyWithArray() is used with the argument /* When RedisModule_ReplyWithArray() is used with the argument
* REDISMODULE_POSTPONED_ARRAY_LEN, because we don't know beforehand the number * REDISMODULE_POSTPONED_LEN, because we don't know beforehand the number
* of items we are going to output as elements of the array, this function * of items we are going to output as elements of the array, this function
* will take care to set the array length. * will take care to set the array length.
* *
...@@ -1596,9 +1736,9 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) { ...@@ -1596,9 +1736,9 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) {
* For example in order to output an array like [1,[10,20,30]] we * For example in order to output an array like [1,[10,20,30]] we
* could write: * could write:
* *
* RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN); * RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN);
* RedisModule_ReplyWithLongLong(ctx,1); * RedisModule_ReplyWithLongLong(ctx,1);
* RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN); * RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN);
* RedisModule_ReplyWithLongLong(ctx,10); * RedisModule_ReplyWithLongLong(ctx,10);
* RedisModule_ReplyWithLongLong(ctx,20); * RedisModule_ReplyWithLongLong(ctx,20);
* RedisModule_ReplyWithLongLong(ctx,30); * RedisModule_ReplyWithLongLong(ctx,30);
...@@ -1611,24 +1751,27 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) { ...@@ -1611,24 +1751,27 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) {
* that is not easy to calculate in advance the number of elements. * that is not easy to calculate in advance the number of elements.
*/ */
void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) { void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
client *c = moduleGetReplyClient(ctx); moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_ARRAY);
if (c == NULL) return; }
if (ctx->postponed_arrays_count == 0) {
serverLog(LL_WARNING, /* Very similar to RedisModule_ReplySetArrayLength except `len` should
"API misuse detected in module %s: " * exactly half of the number of `ReplyWith*` functions called in the
"RedisModule_ReplySetArrayLength() called without previous " * context of the map.
"RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN) " * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. */
"call.", ctx->module->name); void RM_ReplySetMapLength(RedisModuleCtx *ctx, long len) {
return; moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_MAP);
} }
ctx->postponed_arrays_count--;
setDeferredArrayLen(c, /* Very similar to RedisModule_ReplySetArrayLength
ctx->postponed_arrays[ctx->postponed_arrays_count], * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. */
len); void RM_ReplySetSetLength(RedisModuleCtx *ctx, long len) {
if (ctx->postponed_arrays_count == 0) { moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_SET);
zfree(ctx->postponed_arrays); }
ctx->postponed_arrays = NULL;
} /* Very similar to RedisModule_ReplySetMapLength
* Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. */
void RM_ReplySetAttributeLength(RedisModuleCtx *ctx, long len) {
moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_ATTRIBUTE);
} }
/* Reply with a bulk string, taking in input a C buffer pointer and length. /* Reply with a bulk string, taking in input a C buffer pointer and length.
...@@ -1693,6 +1836,16 @@ int RM_ReplyWithNull(RedisModuleCtx *ctx) { ...@@ -1693,6 +1836,16 @@ int RM_ReplyWithNull(RedisModuleCtx *ctx) {
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* Reply to the client with a boolean value.
*
* The function always returns REDISMODULE_OK. */
int RM_ReplyWithBool(RedisModuleCtx *ctx, int b) {
client *c = moduleGetReplyClient(ctx);
if (c == NULL) return REDISMODULE_OK;
addReplyBool(c,b);
return REDISMODULE_OK;
}
/* Reply exactly what a Redis command returned us with RedisModule_Call(). /* Reply exactly what a Redis command returned us with RedisModule_Call().
* This function is useful when we use RedisModule_Call() in order to * This function is useful when we use RedisModule_Call() in order to
* execute some command, as we want to reply to the client exactly the * execute some command, as we want to reply to the client exactly the
...@@ -9404,15 +9557,22 @@ void moduleRegisterCoreAPI(void) { ...@@ -9404,15 +9557,22 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(ReplyWithError); REGISTER_API(ReplyWithError);
REGISTER_API(ReplyWithSimpleString); REGISTER_API(ReplyWithSimpleString);
REGISTER_API(ReplyWithArray); REGISTER_API(ReplyWithArray);
REGISTER_API(ReplyWithMap);
REGISTER_API(ReplyWithSet);
REGISTER_API(ReplyWithAttribute);
REGISTER_API(ReplyWithNullArray); REGISTER_API(ReplyWithNullArray);
REGISTER_API(ReplyWithEmptyArray); REGISTER_API(ReplyWithEmptyArray);
REGISTER_API(ReplySetArrayLength); REGISTER_API(ReplySetArrayLength);
REGISTER_API(ReplySetMapLength);
REGISTER_API(ReplySetSetLength);
REGISTER_API(ReplySetAttributeLength);
REGISTER_API(ReplyWithString); REGISTER_API(ReplyWithString);
REGISTER_API(ReplyWithEmptyString); REGISTER_API(ReplyWithEmptyString);
REGISTER_API(ReplyWithVerbatimString); REGISTER_API(ReplyWithVerbatimString);
REGISTER_API(ReplyWithStringBuffer); REGISTER_API(ReplyWithStringBuffer);
REGISTER_API(ReplyWithCString); REGISTER_API(ReplyWithCString);
REGISTER_API(ReplyWithNull); REGISTER_API(ReplyWithNull);
REGISTER_API(ReplyWithBool);
REGISTER_API(ReplyWithCallReply); REGISTER_API(ReplyWithCallReply);
REGISTER_API(ReplyWithDouble); REGISTER_API(ReplyWithDouble);
REGISTER_API(ReplyWithLongDouble); REGISTER_API(ReplyWithLongDouble);
......
...@@ -48,7 +48,8 @@ ...@@ -48,7 +48,8 @@
#define REDISMODULE_REPLY_NULL 4 #define REDISMODULE_REPLY_NULL 4
/* Postponed array length. */ /* Postponed array length. */
#define REDISMODULE_POSTPONED_ARRAY_LEN -1 #define REDISMODULE_POSTPONED_ARRAY_LEN -1 /* Deprecated, please use REDISMODULE_POSTPONED_LEN */
#define REDISMODULE_POSTPONED_LEN -1
/* Expire */ /* Expire */
#define REDISMODULE_NO_EXPIRE -1 #define REDISMODULE_NO_EXPIRE -1
...@@ -623,15 +624,24 @@ REDISMODULE_API const char * (*RedisModule_StringPtrLen)(const RedisModuleString ...@@ -623,15 +624,24 @@ REDISMODULE_API const char * (*RedisModule_StringPtrLen)(const RedisModuleString
REDISMODULE_API int (*RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithMap)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithSet)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithAttribute)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithPush)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ReplySetMapLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ReplySetSetLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ReplySetAttributeLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_ReplySetPushLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithNull)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithNull)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithBool)(RedisModuleCtx *ctx, int b) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply) REDISMODULE_ATTR;
...@@ -884,15 +894,24 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -884,15 +894,24 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(ReplyWithError); REDISMODULE_GET_API(ReplyWithError);
REDISMODULE_GET_API(ReplyWithSimpleString); REDISMODULE_GET_API(ReplyWithSimpleString);
REDISMODULE_GET_API(ReplyWithArray); REDISMODULE_GET_API(ReplyWithArray);
REDISMODULE_GET_API(ReplyWithMap);
REDISMODULE_GET_API(ReplyWithSet);
REDISMODULE_GET_API(ReplyWithAttribute);
REDISMODULE_GET_API(ReplyWithPush);
REDISMODULE_GET_API(ReplyWithNullArray); REDISMODULE_GET_API(ReplyWithNullArray);
REDISMODULE_GET_API(ReplyWithEmptyArray); REDISMODULE_GET_API(ReplyWithEmptyArray);
REDISMODULE_GET_API(ReplySetArrayLength); REDISMODULE_GET_API(ReplySetArrayLength);
REDISMODULE_GET_API(ReplySetMapLength);
REDISMODULE_GET_API(ReplySetSetLength);
REDISMODULE_GET_API(ReplySetAttributeLength);
REDISMODULE_GET_API(ReplySetPushLength);
REDISMODULE_GET_API(ReplyWithStringBuffer); REDISMODULE_GET_API(ReplyWithStringBuffer);
REDISMODULE_GET_API(ReplyWithCString); REDISMODULE_GET_API(ReplyWithCString);
REDISMODULE_GET_API(ReplyWithString); REDISMODULE_GET_API(ReplyWithString);
REDISMODULE_GET_API(ReplyWithEmptyString); REDISMODULE_GET_API(ReplyWithEmptyString);
REDISMODULE_GET_API(ReplyWithVerbatimString); REDISMODULE_GET_API(ReplyWithVerbatimString);
REDISMODULE_GET_API(ReplyWithNull); REDISMODULE_GET_API(ReplyWithNull);
REDISMODULE_GET_API(ReplyWithBool);
REDISMODULE_GET_API(ReplyWithCallReply); REDISMODULE_GET_API(ReplyWithCallReply);
REDISMODULE_GET_API(ReplyWithDouble); REDISMODULE_GET_API(ReplyWithDouble);
REDISMODULE_GET_API(ReplyWithLongDouble); REDISMODULE_GET_API(ReplyWithLongDouble);
......
/*
* A module the tests RM_ReplyWith family of commands
*/
#include "redismodule.h"
int rw_string(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
return RedisModule_ReplyWithString(ctx, argv[1]);
}
int rw_cstring(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
if (argc != 1) return RedisModule_WrongArity(ctx);
return RedisModule_ReplyWithSimpleString(ctx, "A simple string");
}
int rw_int(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
long long integer;
if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK)
return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as an integer");
return RedisModule_ReplyWithLongLong(ctx, integer);
}
int rw_double(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
double dbl;
if (RedisModule_StringToDouble(argv[1], &dbl) != REDISMODULE_OK)
return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a double");
return RedisModule_ReplyWithDouble(ctx, dbl);
}
int rw_longdouble(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
long double longdbl;
if (RedisModule_StringToLongDouble(argv[1], &longdbl) != REDISMODULE_OK)
return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a double");
return RedisModule_ReplyWithLongDouble(ctx, longdbl);
}
int rw_array(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
long long integer;
if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK)
return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer");
RedisModule_ReplyWithArray(ctx, integer);
for (int i = 0; i < integer; ++i) {
RedisModule_ReplyWithLongLong(ctx, i);
}
return REDISMODULE_OK;
}
int rw_map(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
long long integer;
if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK)
return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer");
RedisModule_ReplyWithMap(ctx, integer);
for (int i = 0; i < integer; ++i) {
RedisModule_ReplyWithLongLong(ctx, i);
RedisModule_ReplyWithDouble(ctx, i * 1.5);
}
return REDISMODULE_OK;
}
int rw_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
long long integer;
if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK)
return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer");
RedisModule_ReplyWithSet(ctx, integer);
for (int i = 0; i < integer; ++i) {
RedisModule_ReplyWithLongLong(ctx, i);
}
return REDISMODULE_OK;
}
int rw_attribute(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
long long integer;
if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK)
return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer");
if (RedisModule_ReplyWithAttribute(ctx, integer) != REDISMODULE_OK) {
return RedisModule_ReplyWithError(ctx, "Attributes aren't supported by RESP 2");
}
for (int i = 0; i < integer; ++i) {
RedisModule_ReplyWithLongLong(ctx, i);
}
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK;
}
int rw_bool(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
if (argc != 1) return RedisModule_WrongArity(ctx);
RedisModule_ReplyWithArray(ctx, 2);
RedisModule_ReplyWithBool(ctx, 0);
return RedisModule_ReplyWithBool(ctx, 1);
}
int rw_null(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
if (argc != 1) return RedisModule_WrongArity(ctx);
return RedisModule_ReplyWithNull(ctx);
}
int rw_error(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
if (argc != 1) return RedisModule_WrongArity(ctx);
return RedisModule_ReplyWithError(ctx, "An error");
}
int rw_verbatim(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 1) return RedisModule_WrongArity(ctx);
return RedisModule_ReplyWithVerbatimString(ctx, argv[1]);
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "replywith", 1, REDISMODULE_APIVER_1) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.string",rw_string,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.cstring",rw_cstring,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.int",rw_int,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.double",rw_double,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.longdouble",rw_longdouble,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.array",rw_array,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.map",rw_map,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.attribute",rw_attribute,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.set",rw_set,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.bool",rw_bool,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.null",rw_null,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.error",rw_error,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"rw.verbatim",rw_verbatim,"",0,0,0) != REDISMODULE_OK)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
...@@ -254,6 +254,12 @@ proc ::redis::redis_read_bool fd { ...@@ -254,6 +254,12 @@ proc ::redis::redis_read_bool fd {
return -code error "Bad protocol, '$v' as bool type" return -code error "Bad protocol, '$v' as bool type"
} }
proc ::redis::redis_read_verbatim_str fd {
set v [redis_bulk_read $fd]
# strip the first 4 chars ("txt:")
return [string range $v 4 end]
}
proc ::redis::redis_read_reply {id fd} { proc ::redis::redis_read_reply {id fd} {
if {$::redis::readraw($id)} { if {$::redis::readraw($id)} {
return [redis_read_line $fd] return [redis_read_line $fd]
...@@ -268,6 +274,7 @@ proc ::redis::redis_read_reply {id fd} { ...@@ -268,6 +274,7 @@ proc ::redis::redis_read_reply {id fd} {
+ {return [redis_read_line $fd]} + {return [redis_read_line $fd]}
, {return [expr {double([redis_read_line $fd])}]} , {return [expr {double([redis_read_line $fd])}]}
# {return [redis_read_bool $fd]} # {return [redis_read_bool $fd]}
= {return [redis_read_verbatim_str $fd]}
- {return -code error [redis_read_line $fd]} - {return -code error [redis_read_line $fd]}
$ {return [redis_bulk_read $fd]} $ {return [redis_bulk_read $fd]}
> - > -
......
set testmodule [file normalize tests/modules/reply.so]
# test all with hello 2/3
start_server {tags {"modules"}} {
r module load $testmodule
for {proto=2; proto<=3; incr proto} {
r hello $proto
test {RM_ReplyWithString: an string reply} {
# RedisString
set string [r rw.string "Redis"]
assert_equal "Redis" $string
# C string
set string [r rw.cstring]
assert_equal "A simple string" $string
}
test {RM_ReplyWithInt: an integer reply} {
assert_equal 42 [r rw.int 42]
}
test {RM_ReplyWithDouble: a float reply} {
assert_equal 3.141 [r rw.double 3.141]
}
test {RM_ReplyWithLongDouble: a float reply} {
assert_equal 3.141 [r rw.longdouble 3.141]
}
test {RM_ReplyWithVerbatimString: a string reply} {
assert_equal "bla\nbla\nbla" [r rw.verbatim "bla\nbla\nbla"]
}
test {RM_ReplyWithArray: an array reply} {
assert_equal {0 1 2 3 4} [r rw.array 5]
}
test {RM_ReplyWithMap: an map reply} {
set res [r rw.map 3]
if {$proto == 2} {
assert_equal {0 0.0 1 1.5 2 3.0} $res
} else {
assert_equal [dict create 0 0.0 1 1.5 2 3.0] $res
}
}
test {RM_ReplyWithSet: an set reply} {
assert_equal {0 1 2} [r rw.set 3]
}
test {RM_ReplyWithAttribute: an set reply} {
set res [r rw.attribute 3]
if {$proto == 2} {
catch {r rw.error} e
assert_match "Attributes aren't supported by RESP 2" $e
} else {
assert_equal [dict create 0 0.0 1 1.5 2 3.0] $res
}
assert_equal "OK" $res
}
test {RM_ReplyWithBool: a boolean reply} {
assert_equal {0 1} [r rw.bool]
}
test {RM_ReplyWithNull: a NULL reply} {
assert_equal {} [r rw.null]
}
test {RM_ReplyWithError: an error reply} {
catch {r rw.error} e
assert_match "An error" $e
}
}
}
...@@ -190,6 +190,12 @@ start_server {tags {"protocol network"}} { ...@@ -190,6 +190,12 @@ start_server {tags {"protocol network"}} {
assert_equal [r debug protocol false] 0 assert_equal [r debug protocol false] 0
set _ {} set _ {}
} {} {needs:debug resp3} } {} {needs:debug resp3}
test "test verbatim str parsing" {
r hello 3
r debug protocol verbatim
} "This is a verbatim\nstring" {needs:debug resp3}
} }
start_server {tags {"regression"}} { start_server {tags {"regression"}} {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment