Change redisFormatCommand to return the command in a char*

This allows users of the API to format a command without the need to
have all the sds functions included, only for free'ing the returned
wire-level command.
This commit is contained in:
Pieter Noordhuis 2010-10-30 20:38:21 +02:00
parent bc5dcdbc85
commit e95c9d4c5b
2 changed files with 84 additions and 58 deletions

138
hiredis.c
View File

@ -460,61 +460,49 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
return REDIS_OK; return REDIS_OK;
} }
/* Helper function for redisCommand(). It's used to append the next argument /* Calculate the number of bytes needed to represent an integer as string. */
* to the argument vector. */ static int intlen(int i) {
static void addArgument(sds a, char ***argv, int *argc) { int len = 0;
if (i < 0) {
len++;
i = -i;
}
do {
len++;
i /= 10;
} while(i);
return len;
}
/* Helper function for redisvFormatCommand(). */
static void addArgument(sds a, char ***argv, int *argc, int *totlen) {
(*argc)++; (*argc)++;
if ((*argv = realloc(*argv, sizeof(char*)*(*argc))) == NULL) redisOOM(); if ((*argv = realloc(*argv, sizeof(char*)*(*argc))) == NULL) redisOOM();
if (totlen) *totlen = *totlen+1+intlen(sdslen(a))+2+sdslen(a)+2;
(*argv)[(*argc)-1] = a; (*argv)[(*argc)-1] = a;
} }
/* Execute a command. This function is printf alike: int redisvFormatCommand(char **target, const char *format, va_list ap) {
*
* %s represents a C nul terminated string you want to interpolate
* %b represents a binary safe string
*
* When using %b you need to provide both the pointer to the string
* and the length in bytes. Examples:
*
* redisCommand("GET %s", mykey);
* redisCommand("SET %s %b", mykey, somevalue, somevalue_len);
*
* RETURN VALUE:
*
* The returned value is a redisReply object that must be freed using the
* redisFreeReply() function.
*
* given a redisReply "reply" you can test if there was an error in this way:
*
* if (reply->type == REDIS_REPLY_ERROR) {
* printf("Error in request: %s\n", reply->reply);
* }
*
* The replied string itself is in reply->reply if the reply type is
* a REDIS_REPLY_STRING. If the reply is a multi bulk reply then
* reply->type is REDIS_REPLY_ARRAY and you can access all the elements
* in this way:
*
* for (i = 0; i < reply->elements; i++)
* printf("%d: %s\n", i, reply->element[i]);
*
* Finally when type is REDIS_REPLY_INTEGER the long long integer is
* stored at reply->integer.
*/
static sds redisFormatCommand(const char *format, va_list ap) {
size_t size; size_t size;
const char *arg, *c = format; const char *arg, *c = format;
sds cmd = sdsempty(); /* whole command buffer */ char *cmd = NULL; /* final command */
sds current = sdsempty(); /* current argument */ int pos; /* position in final command */
sds current; /* current argument */
char **argv = NULL; char **argv = NULL;
int argc = 0, j; int argc = 0, j;
int totlen = 0;
/* Abort if there is not target to set */
if (target == NULL)
return -1;
/* Build the command string accordingly to protocol */ /* Build the command string accordingly to protocol */
current = sdsempty();
while(*c != '\0') { while(*c != '\0') {
if (*c != '%' || c[1] == '\0') { if (*c != '%' || c[1] == '\0') {
if (*c == ' ') { if (*c == ' ') {
if (sdslen(current) != 0) { if (sdslen(current) != 0) {
addArgument(current, &argv, &argc); addArgument(current, &argv, &argc, &totlen);
current = sdsempty(); current = sdsempty();
} }
} else { } else {
@ -541,21 +529,53 @@ static sds redisFormatCommand(const char *format, va_list ap) {
} }
/* Add the last argument if needed */ /* Add the last argument if needed */
if (sdslen(current) != 0) if (sdslen(current) != 0) {
addArgument(current, &argv, &argc); addArgument(current, &argv, &argc, &totlen);
else } else {
sdsfree(current); sdsfree(current);
}
/* Add bytes needed to hold multi bulk count */
totlen += 1+intlen(argc)+2;
/* Build the command at protocol level */ /* Build the command at protocol level */
cmd = sdscatprintf(cmd,"*%d\r\n",argc); cmd = malloc(totlen+1);
if (!cmd) redisOOM();
pos = sprintf(cmd,"*%d\r\n",argc);
for (j = 0; j < argc; j++) { for (j = 0; j < argc; j++) {
cmd = sdscatprintf(cmd,"$%zu\r\n",sdslen(argv[j])); pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(argv[j]));
cmd = sdscatlen(cmd,argv[j],sdslen(argv[j])); memcpy(cmd+pos,argv[j],sdslen(argv[j]));
cmd = sdscatlen(cmd,"\r\n",2); pos += sdslen(argv[j]);
sdsfree(argv[j]); sdsfree(argv[j]);
cmd[pos++] = '\r';
cmd[pos++] = '\n';
} }
assert(pos == totlen);
free(argv); free(argv);
return cmd; cmd[totlen] = '\0';
*target = cmd;
return totlen;
}
/* Format a command according to the Redis protocol. This function
* takes a format similar to printf:
*
* %s represents a C null terminated string you want to interpolate
* %b represents a binary safe string
*
* When using %b you need to provide both the pointer to the string
* and the length in bytes. Examples:
*
* len = redisFormatCommand(target, "GET %s", mykey);
* len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
*/
int redisFormatCommand(char **target, const char *format, ...) {
va_list ap;
int len;
va_start(ap,format);
len = redisvFormatCommand(target,format,ap);
va_end(ap);
return len;
} }
static int redisContextConnect(redisContext *c, const char *ip, int port) { static int redisContextConnect(redisContext *c, const char *ip, int port) {
@ -833,21 +853,22 @@ static int redisCommandWriteNonBlock(redisContext *c, redisCallback *cb, char *s
* the error field in the context will be set. */ * the error field in the context will be set. */
void *redisCommand(redisContext *c, const char *format, ...) { void *redisCommand(redisContext *c, const char *format, ...) {
va_list ap; va_list ap;
sds cmd; char *cmd;
int len;
void *reply = NULL; void *reply = NULL;
va_start(ap,format); va_start(ap,format);
cmd = redisFormatCommand(format,ap); len = redisvFormatCommand(&cmd,format,ap);
va_end(ap); va_end(ap);
if (c->flags & REDIS_BLOCK) { if (c->flags & REDIS_BLOCK) {
if (redisCommandWriteBlock(c,&reply,cmd,sdslen(cmd)) == REDIS_OK) { if (redisCommandWriteBlock(c,&reply,cmd,len) == REDIS_OK) {
sdsfree(cmd); free(cmd);
return reply; return reply;
} }
} else { } else {
redisCommandWriteNonBlock(c,NULL,cmd,sdslen(cmd)); redisCommandWriteNonBlock(c,NULL,cmd,len);
} }
sdsfree(cmd); free(cmd);
return NULL; return NULL;
} }
@ -859,7 +880,8 @@ void *redisCommand(redisContext *c, const char *format, ...) {
* have no effect (a callback in a blocking context makes no sense). */ * have no effect (a callback in a blocking context makes no sense). */
void *redisCommandWithCallback(redisContext *c, redisCallbackFn *fn, void *privdata, const char *format, ...) { void *redisCommandWithCallback(redisContext *c, redisCallbackFn *fn, void *privdata, const char *format, ...) {
va_list ap; va_list ap;
sds cmd; char *cmd;
int len;
int status; int status;
redisCallback cb = { fn, privdata }; redisCallback cb = { fn, privdata };
@ -867,10 +889,10 @@ void *redisCommandWithCallback(redisContext *c, redisCallbackFn *fn, void *privd
if (c->flags & REDIS_BLOCK) return NULL; if (c->flags & REDIS_BLOCK) return NULL;
va_start(ap,format); va_start(ap,format);
cmd = redisFormatCommand(format,ap); len = redisvFormatCommand(&cmd,format,ap);
va_end(ap); va_end(ap);
status = redisCommandWriteNonBlock(c,&cb,cmd,sdslen(cmd)); status = redisCommandWriteNonBlock(c,&cb,cmd,len);
sdsfree(cmd); free(cmd);
return NULL; return NULL;
} }

View File

@ -122,6 +122,10 @@ void redisReplyReaderFree(void *ptr);
void redisReplyReaderFeed(void *reader, char *buf, int len); void redisReplyReaderFeed(void *reader, char *buf, int len);
int redisReplyReaderGetReply(void *reader, void **reply); int redisReplyReaderGetReply(void *reader, void **reply);
/* Functions to format a command according to the protocol. */
int redisvFormatCommand(char **target, const char *format, va_list ap);
int redisFormatCommand(char **target, const char *format, ...);
redisContext *redisConnect(const char *ip, int port, redisReplyObjectFunctions *fn); redisContext *redisConnect(const char *ip, int port, redisReplyObjectFunctions *fn);
redisContext *redisConnectNonBlock(const char *ip, int port, redisReplyObjectFunctions *fn); redisContext *redisConnectNonBlock(const char *ip, int port, redisReplyObjectFunctions *fn);
void redisDisconnect(redisContext *c); void redisDisconnect(redisContext *c);