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: Guy Benoish <guy.benoish@redislabs.com>
Co-authored-by: Oran Agra <oran@redislabs.com>
This commit is contained in:
Ariel Shtul 2021-08-03 11:37:19 +03:00 committed by GitHub
parent 4bd7748362
commit bdbf5eedae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 486 additions and 39 deletions

View File

@ -660,8 +660,8 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
ctx->postponed_arrays_count = 0;
serverLog(LL_WARNING,
"API misuse detected in module %s: "
"RedisModule_ReplyWithArray(REDISMODULE_POSTPONED_ARRAY_LEN) "
"not matched by the same number of RedisModule_SetReplyArrayLen() "
"RedisModule_ReplyWith*(REDISMODULE_POSTPONED_LEN) "
"not matched by the same number of RedisModule_SetReply*Len() "
"calls.",
ctx->module->name);
}
@ -1448,6 +1448,18 @@ int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const cha
*
* if (... some condition ...)
* 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,
@ -1537,35 +1549,129 @@ int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) {
return REDISMODULE_OK;
}
/* Reply with an array type of 'len' elements. However 'len' other calls
* to `ReplyWith*` style functions must follow in order to emit the elements
* of the array.
*
* When producing arrays with a number of element that is not known beforehand
* the function can be called with the special count
* 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) {
#define COLLECTION_REPLY_ARRAY 1
#define COLLECTION_REPLY_MAP 2
#define COLLECTION_REPLY_SET 3
#define COLLECTION_REPLY_ATTRIBUTE 4
int moduleReplyWithCollection(RedisModuleCtx *ctx, long len, int type) {
client *c = moduleGetReplyClient(ctx);
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_count+1));
ctx->postponed_arrays[ctx->postponed_arrays_count] =
addReplyDeferredLen(c);
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 {
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;
}
/* 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.
*
* 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. */
int RM_ReplyWithNullArray(RedisModuleCtx *ctx) {
client *c = moduleGetReplyClient(ctx);
@ -1584,8 +1690,42 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) {
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
* 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
* will take care to set the array length.
*
@ -1596,9 +1736,9 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) {
* For example in order to output an array like [1,[10,20,30]] we
* could write:
*
* RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
* RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN);
* RedisModule_ReplyWithLongLong(ctx,1);
* RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
* RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN);
* RedisModule_ReplyWithLongLong(ctx,10);
* RedisModule_ReplyWithLongLong(ctx,20);
* RedisModule_ReplyWithLongLong(ctx,30);
@ -1611,24 +1751,27 @@ int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) {
* that is not easy to calculate in advance the number of elements.
*/
void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
client *c = moduleGetReplyClient(ctx);
if (c == NULL) return;
if (ctx->postponed_arrays_count == 0) {
serverLog(LL_WARNING,
"API misuse detected in module %s: "
"RedisModule_ReplySetArrayLength() called without previous "
"RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN) "
"call.", ctx->module->name);
return;
}
ctx->postponed_arrays_count--;
setDeferredArrayLen(c,
ctx->postponed_arrays[ctx->postponed_arrays_count],
len);
if (ctx->postponed_arrays_count == 0) {
zfree(ctx->postponed_arrays);
ctx->postponed_arrays = NULL;
}
moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_ARRAY);
}
/* Very similar to RedisModule_ReplySetArrayLength except `len` should
* exactly half of the number of `ReplyWith*` functions called in the
* context of the map.
* Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. */
void RM_ReplySetMapLength(RedisModuleCtx *ctx, long len) {
moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_MAP);
}
/* Very similar to RedisModule_ReplySetArrayLength
* Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. */
void RM_ReplySetSetLength(RedisModuleCtx *ctx, long len) {
moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_SET);
}
/* 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.
@ -1693,6 +1836,16 @@ int RM_ReplyWithNull(RedisModuleCtx *ctx) {
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().
* 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
@ -9404,15 +9557,22 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(ReplyWithError);
REGISTER_API(ReplyWithSimpleString);
REGISTER_API(ReplyWithArray);
REGISTER_API(ReplyWithMap);
REGISTER_API(ReplyWithSet);
REGISTER_API(ReplyWithAttribute);
REGISTER_API(ReplyWithNullArray);
REGISTER_API(ReplyWithEmptyArray);
REGISTER_API(ReplySetArrayLength);
REGISTER_API(ReplySetMapLength);
REGISTER_API(ReplySetSetLength);
REGISTER_API(ReplySetAttributeLength);
REGISTER_API(ReplyWithString);
REGISTER_API(ReplyWithEmptyString);
REGISTER_API(ReplyWithVerbatimString);
REGISTER_API(ReplyWithStringBuffer);
REGISTER_API(ReplyWithCString);
REGISTER_API(ReplyWithNull);
REGISTER_API(ReplyWithBool);
REGISTER_API(ReplyWithCallReply);
REGISTER_API(ReplyWithDouble);
REGISTER_API(ReplyWithLongDouble);

View File

@ -48,7 +48,8 @@
#define REDISMODULE_REPLY_NULL 4
/* 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 */
#define REDISMODULE_NO_EXPIRE -1
@ -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_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg) 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_ReplyWithEmptyArray)(RedisModuleCtx *ctx) 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_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf) 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_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len) 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_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d) 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
REDISMODULE_GET_API(ReplyWithError);
REDISMODULE_GET_API(ReplyWithSimpleString);
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(ReplyWithEmptyArray);
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(ReplyWithCString);
REDISMODULE_GET_API(ReplyWithString);
REDISMODULE_GET_API(ReplyWithEmptyString);
REDISMODULE_GET_API(ReplyWithVerbatimString);
REDISMODULE_GET_API(ReplyWithNull);
REDISMODULE_GET_API(ReplyWithBool);
REDISMODULE_GET_API(ReplyWithCallReply);
REDISMODULE_GET_API(ReplyWithDouble);
REDISMODULE_GET_API(ReplyWithLongDouble);

178
tests/modules/reply.c Normal file
View File

@ -0,0 +1,178 @@
/*
* 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;
}

View File

@ -254,6 +254,12 @@ proc ::redis::redis_read_bool fd {
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} {
if {$::redis::readraw($id)} {
return [redis_read_line $fd]
@ -268,6 +274,7 @@ proc ::redis::redis_read_reply {id fd} {
+ {return [redis_read_line $fd]}
, {return [expr {double([redis_read_line $fd])}]}
# {return [redis_read_bool $fd]}
= {return [redis_read_verbatim_str $fd]}
- {return -code error [redis_read_line $fd]}
$ {return [redis_bulk_read $fd]}
> -

View File

@ -0,0 +1,77 @@
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
}
}
}

View File

@ -190,6 +190,12 @@ start_server {tags {"protocol network"}} {
assert_equal [r debug protocol false] 0
set _ {}
} {} {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"}} {