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;
}
/* Helper function for redisCommand(). It's used to append the next argument
* to the argument vector. */
static void addArgument(sds a, char ***argv, int *argc) {
/* Calculate the number of bytes needed to represent an integer as string. */
static int intlen(int i) {
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)++;
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;
}
/* Execute a command. This function is printf alike:
*
* %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) {
int redisvFormatCommand(char **target, const char *format, va_list ap) {
size_t size;
const char *arg, *c = format;
sds cmd = sdsempty(); /* whole command buffer */
sds current = sdsempty(); /* current argument */
char *cmd = NULL; /* final command */
int pos; /* position in final command */
sds current; /* current argument */
char **argv = NULL;
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 */
current = sdsempty();
while(*c != '\0') {
if (*c != '%' || c[1] == '\0') {
if (*c == ' ') {
if (sdslen(current) != 0) {
addArgument(current, &argv, &argc);
addArgument(current, &argv, &argc, &totlen);
current = sdsempty();
}
} else {
@ -541,21 +529,53 @@ static sds redisFormatCommand(const char *format, va_list ap) {
}
/* Add the last argument if needed */
if (sdslen(current) != 0)
addArgument(current, &argv, &argc);
else
if (sdslen(current) != 0) {
addArgument(current, &argv, &argc, &totlen);
} else {
sdsfree(current);
}
/* Add bytes needed to hold multi bulk count */
totlen += 1+intlen(argc)+2;
/* 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++) {
cmd = sdscatprintf(cmd,"$%zu\r\n",sdslen(argv[j]));
cmd = sdscatlen(cmd,argv[j],sdslen(argv[j]));
cmd = sdscatlen(cmd,"\r\n",2);
pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(argv[j]));
memcpy(cmd+pos,argv[j],sdslen(argv[j]));
pos += sdslen(argv[j]);
sdsfree(argv[j]);
cmd[pos++] = '\r';
cmd[pos++] = '\n';
}
assert(pos == totlen);
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) {
@ -833,21 +853,22 @@ static int redisCommandWriteNonBlock(redisContext *c, redisCallback *cb, char *s
* the error field in the context will be set. */
void *redisCommand(redisContext *c, const char *format, ...) {
va_list ap;
sds cmd;
char *cmd;
int len;
void *reply = NULL;
va_start(ap,format);
cmd = redisFormatCommand(format,ap);
len = redisvFormatCommand(&cmd,format,ap);
va_end(ap);
if (c->flags & REDIS_BLOCK) {
if (redisCommandWriteBlock(c,&reply,cmd,sdslen(cmd)) == REDIS_OK) {
sdsfree(cmd);
if (redisCommandWriteBlock(c,&reply,cmd,len) == REDIS_OK) {
free(cmd);
return reply;
}
} else {
redisCommandWriteNonBlock(c,NULL,cmd,sdslen(cmd));
redisCommandWriteNonBlock(c,NULL,cmd,len);
}
sdsfree(cmd);
free(cmd);
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). */
void *redisCommandWithCallback(redisContext *c, redisCallbackFn *fn, void *privdata, const char *format, ...) {
va_list ap;
sds cmd;
char *cmd;
int len;
int status;
redisCallback cb = { fn, privdata };
@ -867,10 +889,10 @@ void *redisCommandWithCallback(redisContext *c, redisCallbackFn *fn, void *privd
if (c->flags & REDIS_BLOCK) return NULL;
va_start(ap,format);
cmd = redisFormatCommand(format,ap);
len = redisvFormatCommand(&cmd,format,ap);
va_end(ap);
status = redisCommandWriteNonBlock(c,&cb,cmd,sdslen(cmd));
sdsfree(cmd);
status = redisCommandWriteNonBlock(c,&cb,cmd,len);
free(cmd);
return NULL;
}

View File

@ -122,6 +122,10 @@ void redisReplyReaderFree(void *ptr);
void redisReplyReaderFeed(void *reader, char *buf, int len);
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 *redisConnectNonBlock(const char *ip, int port, redisReplyObjectFunctions *fn);
void redisDisconnect(redisContext *c);