2 * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of Redis nor the names of its contributors may be used
14 * to endorse or promote products derived from this software without
15 * specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
41 typedef struct redisReader
{
42 struct redisReplyObjectFunctions
*fn
;
43 sds error
; /* holds optional error */
44 void *reply
; /* holds temporary reply */
46 sds buf
; /* read buffer */
47 unsigned int pos
; /* buffer cursor */
49 redisReadTask rstack
[3]; /* stack of read tasks */
50 int ridx
; /* index of stack */
53 static redisReply
*createReplyObject(int type
);
54 static void *createStringObject(const redisReadTask
*task
, char *str
, size_t len
);
55 static void *createArrayObject(const redisReadTask
*task
, int elements
);
56 static void *createIntegerObject(const redisReadTask
*task
, long long value
);
57 static void *createNilObject(const redisReadTask
*task
);
58 static void redisSetReplyReaderError(redisReader
*r
, sds err
);
60 /* Default set of functions to build the reply. */
61 static redisReplyObjectFunctions defaultFunctions
= {
69 /* Create a reply object */
70 static redisReply
*createReplyObject(int type
) {
71 redisReply
*r
= calloc(sizeof(*r
),1);
78 /* Free a reply object */
79 void freeReplyObject(void *reply
) {
80 redisReply
*r
= reply
;
84 case REDIS_REPLY_INTEGER
:
85 break; /* Nothing to free */
86 case REDIS_REPLY_ARRAY
:
87 for (j
= 0; j
< r
->elements
; j
++)
88 if (r
->element
[j
]) freeReplyObject(r
->element
[j
]);
99 static void *createStringObject(const redisReadTask
*task
, char *str
, size_t len
) {
100 redisReply
*r
= createReplyObject(task
->type
);
101 char *value
= malloc(len
+1);
102 if (!value
) redisOOM();
103 assert(task
->type
== REDIS_REPLY_ERROR
||
104 task
->type
== REDIS_REPLY_STATUS
||
105 task
->type
== REDIS_REPLY_STRING
);
107 /* Copy string value */
108 memcpy(value
,str
,len
);
114 redisReply
*parent
= task
->parent
;
115 assert(parent
->type
== REDIS_REPLY_ARRAY
);
116 parent
->element
[task
->idx
] = r
;
121 static void *createArrayObject(const redisReadTask
*task
, int elements
) {
122 redisReply
*r
= createReplyObject(REDIS_REPLY_ARRAY
);
123 r
->elements
= elements
;
124 if ((r
->element
= calloc(sizeof(redisReply
*),elements
)) == NULL
)
127 redisReply
*parent
= task
->parent
;
128 assert(parent
->type
== REDIS_REPLY_ARRAY
);
129 parent
->element
[task
->idx
] = r
;
134 static void *createIntegerObject(const redisReadTask
*task
, long long value
) {
135 redisReply
*r
= createReplyObject(REDIS_REPLY_INTEGER
);
138 redisReply
*parent
= task
->parent
;
139 assert(parent
->type
== REDIS_REPLY_ARRAY
);
140 parent
->element
[task
->idx
] = r
;
145 static void *createNilObject(const redisReadTask
*task
) {
146 redisReply
*r
= createReplyObject(REDIS_REPLY_NIL
);
148 redisReply
*parent
= task
->parent
;
149 assert(parent
->type
== REDIS_REPLY_ARRAY
);
150 parent
->element
[task
->idx
] = r
;
155 static char *readBytes(redisReader
*r
, unsigned int bytes
) {
157 if (sdslen(r
->buf
)-r
->pos
>= bytes
) {
165 static char *seekNewline(char *s
) {
166 /* Find pointer to \r\n without strstr */
167 while(s
!= NULL
&& s
[0] != '\r' && s
[1] != '\n')
172 static char *readLine(redisReader
*r
, int *_len
) {
179 len
= s
-(r
->buf
+r
->pos
);
180 r
->pos
+= len
+2; /* skip \r\n */
181 if (_len
) *_len
= len
;
187 static void moveToNextTask(redisReader
*r
) {
188 redisReadTask
*cur
, *prv
;
189 while (r
->ridx
>= 0) {
190 /* Return a.s.a.p. when the stack is now empty. */
196 cur
= &(r
->rstack
[r
->ridx
]);
197 prv
= &(r
->rstack
[r
->ridx
-1]);
198 assert(prv
->type
== REDIS_REPLY_ARRAY
);
199 if (cur
->idx
== prv
->elements
-1) {
202 /* Reset the type because the next item can be anything */
203 assert(cur
->idx
< prv
->elements
);
212 static int processLineItem(redisReader
*r
) {
213 redisReadTask
*cur
= &(r
->rstack
[r
->ridx
]);
218 if ((p
= readLine(r
,&len
)) != NULL
) {
220 if (cur
->type
== REDIS_REPLY_INTEGER
) {
221 obj
= r
->fn
->createInteger(cur
,strtoll(p
,NULL
,10));
223 obj
= r
->fn
->createString(cur
,p
,len
);
226 obj
= (void*)(size_t)(cur
->type
);
229 /* If there is no root yet, register this object as root. */
230 if (r
->reply
== NULL
)
238 static int processBulkItem(redisReader
*r
) {
239 redisReadTask
*cur
= &(r
->rstack
[r
->ridx
]);
243 unsigned long bytelen
;
249 bytelen
= s
-(r
->buf
+r
->pos
)+2; /* include \r\n */
250 len
= strtol(p
,NULL
,10);
253 /* The nil object can always be created. */
254 obj
= r
->fn
? r
->fn
->createNil(cur
) :
255 (void*)REDIS_REPLY_NIL
;
257 /* Only continue when the buffer contains the entire bulk item. */
258 bytelen
+= len
+2; /* include \r\n */
259 if (r
->pos
+bytelen
<= sdslen(r
->buf
)) {
260 obj
= r
->fn
? r
->fn
->createString(cur
,s
+2,len
) :
261 (void*)REDIS_REPLY_STRING
;
265 /* Proceed when obj was created. */
268 if (r
->reply
== NULL
)
277 static int processMultiBulkItem(redisReader
*r
) {
278 redisReadTask
*cur
= &(r
->rstack
[r
->ridx
]);
283 if ((p
= readLine(r
,NULL
)) != NULL
) {
284 elements
= strtol(p
,NULL
,10);
285 if (elements
== -1) {
286 obj
= r
->fn
? r
->fn
->createNil(cur
) :
287 (void*)REDIS_REPLY_NIL
;
290 obj
= r
->fn
? r
->fn
->createArray(cur
,elements
) :
291 (void*)REDIS_REPLY_ARRAY
;
293 /* Modify task stack when there are more than 0 elements. */
295 cur
->elements
= elements
;
297 r
->rstack
[r
->ridx
].type
= -1;
298 r
->rstack
[r
->ridx
].elements
= -1;
299 r
->rstack
[r
->ridx
].parent
= obj
;
300 r
->rstack
[r
->ridx
].idx
= 0;
306 /* Object was created, so we can always continue. */
307 if (r
->reply
== NULL
)
314 static int processItem(redisReader
*r
) {
315 redisReadTask
*cur
= &(r
->rstack
[r
->ridx
]);
319 /* check if we need to read type */
321 if ((p
= readBytes(r
,1)) != NULL
) {
324 cur
->type
= REDIS_REPLY_ERROR
;
327 cur
->type
= REDIS_REPLY_STATUS
;
330 cur
->type
= REDIS_REPLY_INTEGER
;
333 cur
->type
= REDIS_REPLY_STRING
;
336 cur
->type
= REDIS_REPLY_ARRAY
;
339 byte
= sdscatrepr(sdsempty(),p
,1);
340 redisSetReplyReaderError(r
,sdscatprintf(sdsempty(),
341 "protocol error, got %s as reply type byte", byte
));
346 /* could not consume 1 byte */
351 /* process typed item */
353 case REDIS_REPLY_ERROR
:
354 case REDIS_REPLY_STATUS
:
355 case REDIS_REPLY_INTEGER
:
356 return processLineItem(r
);
357 case REDIS_REPLY_STRING
:
358 return processBulkItem(r
);
359 case REDIS_REPLY_ARRAY
:
360 return processMultiBulkItem(r
);
362 redisSetReplyReaderError(r
,sdscatprintf(sdsempty(),
363 "unknown item type '%d'", cur
->type
));
368 void *redisReplyReaderCreate() {
369 redisReader
*r
= calloc(sizeof(redisReader
),1);
371 r
->fn
= &defaultFunctions
;
377 /* Set the function set to build the reply. Returns REDIS_OK when there
378 * is no temporary object and it can be set, REDIS_ERR otherwise. */
379 int redisReplyReaderSetReplyObjectFunctions(void *reader
, redisReplyObjectFunctions
*fn
) {
380 redisReader
*r
= reader
;
381 if (r
->reply
== NULL
) {
388 /* External libraries wrapping hiredis might need access to the temporary
389 * variable while the reply is built up. When the reader contains an
390 * object in between receiving some bytes to parse, this object might
391 * otherwise be free'd by garbage collection. */
392 void *redisReplyReaderGetObject(void *reader
) {
393 redisReader
*r
= reader
;
397 void redisReplyReaderFree(void *reader
) {
398 redisReader
*r
= reader
;
399 if (r
->error
!= NULL
)
401 if (r
->reply
!= NULL
&& r
->fn
)
402 r
->fn
->freeObject(r
->reply
);
408 static void redisSetReplyReaderError(redisReader
*r
, sds err
) {
409 if (r
->reply
!= NULL
)
410 r
->fn
->freeObject(r
->reply
);
412 /* Clear remaining buffer when we see a protocol error. */
413 if (r
->buf
!= NULL
) {
422 char *redisReplyReaderGetError(void *reader
) {
423 redisReader
*r
= reader
;
427 void redisReplyReaderFeed(void *reader
, char *buf
, int len
) {
428 redisReader
*r
= reader
;
430 /* Copy the provided buffer. */
431 if (buf
!= NULL
&& len
>= 1)
432 r
->buf
= sdscatlen(r
->buf
,buf
,len
);
435 int redisReplyReaderGetReply(void *reader
, void **reply
) {
436 redisReader
*r
= reader
;
437 if (reply
!= NULL
) *reply
= NULL
;
439 /* When the buffer is empty, there will never be a reply. */
440 if (sdslen(r
->buf
) == 0)
443 /* Set first item to process when the stack is empty. */
445 r
->rstack
[0].type
= -1;
446 r
->rstack
[0].elements
= -1;
447 r
->rstack
[0].parent
= NULL
;
448 r
->rstack
[0].idx
= -1;
452 /* Process items in reply. */
454 if (processItem(r
) < 0)
457 /* Discard the consumed part of the buffer. */
459 if (r
->pos
== sdslen(r
->buf
)) {
460 /* sdsrange has a quirck on this edge case. */
464 r
->buf
= sdsrange(r
->buf
,r
->pos
,sdslen(r
->buf
));
469 /* Emit a reply when there is one. */
471 void *aux
= r
->reply
;
474 /* Destroy the buffer when it is empty and is quite large. */
475 if (sdslen(r
->buf
) == 0 && sdsavail(r
->buf
) > 16*1024) {
481 /* Check if there actually *is* a reply. */
482 if (r
->error
!= NULL
) {
485 if (reply
!= NULL
) *reply
= aux
;
491 /* Calculate the number of bytes needed to represent an integer as string. */
492 static int intlen(int i
) {
505 /* Helper function for redisvFormatCommand(). */
506 static void addArgument(sds a
, char ***argv
, int *argc
, int *totlen
) {
508 if ((*argv
= realloc(*argv
, sizeof(char*)*(*argc
))) == NULL
) redisOOM();
509 if (totlen
) *totlen
= *totlen
+1+intlen(sdslen(a
))+2+sdslen(a
)+2;
510 (*argv
)[(*argc
)-1] = a
;
513 int redisvFormatCommand(char **target
, const char *format
, va_list ap
) {
515 const char *arg
, *c
= format
;
516 char *cmd
= NULL
; /* final command */
517 int pos
; /* position in final command */
518 sds current
; /* current argument */
523 /* Abort if there is not target to set */
527 /* Build the command string accordingly to protocol */
528 current
= sdsempty();
530 if (*c
!= '%' || c
[1] == '\0') {
532 if (sdslen(current
) != 0) {
533 addArgument(current
, &argv
, &argc
, &totlen
);
534 current
= sdsempty();
537 current
= sdscatlen(current
,c
,1);
542 arg
= va_arg(ap
,char*);
543 current
= sdscat(current
,arg
);
546 arg
= va_arg(ap
,char*);
547 size
= va_arg(ap
,size_t);
548 current
= sdscatlen(current
,arg
,size
);
551 cmd
= sdscat(cmd
,"%");
559 /* Add the last argument if needed */
560 if (sdslen(current
) != 0) {
561 addArgument(current
, &argv
, &argc
, &totlen
);
566 /* Add bytes needed to hold multi bulk count */
567 totlen
+= 1+intlen(argc
)+2;
569 /* Build the command at protocol level */
570 cmd
= malloc(totlen
+1);
571 if (!cmd
) redisOOM();
572 pos
= sprintf(cmd
,"*%d\r\n",argc
);
573 for (j
= 0; j
< argc
; j
++) {
574 pos
+= sprintf(cmd
+pos
,"$%zu\r\n",sdslen(argv
[j
]));
575 memcpy(cmd
+pos
,argv
[j
],sdslen(argv
[j
]));
576 pos
+= sdslen(argv
[j
]);
581 assert(pos
== totlen
);
588 /* Format a command according to the Redis protocol. This function
589 * takes a format similar to printf:
591 * %s represents a C null terminated string you want to interpolate
592 * %b represents a binary safe string
594 * When using %b you need to provide both the pointer to the string
595 * and the length in bytes. Examples:
597 * len = redisFormatCommand(target, "GET %s", mykey);
598 * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
600 int redisFormatCommand(char **target
, const char *format
, ...) {
604 len
= redisvFormatCommand(target
,format
,ap
);
609 /* Format a command according to the Redis protocol. This function takes the
610 * number of arguments, an array with arguments and an array with their
611 * lengths. If the latter is set to NULL, strlen will be used to compute the
614 int redisFormatCommandArgv(char **target
, int argc
, const char **argv
, const size_t *argvlen
) {
615 char *cmd
= NULL
; /* final command */
616 int pos
; /* position in final command */
620 /* Calculate number of bytes needed for the command */
621 totlen
= 1+intlen(argc
)+2;
622 for (j
= 0; j
< argc
; j
++) {
623 len
= argvlen
? argvlen
[j
] : strlen(argv
[j
]);
624 totlen
+= 1+intlen(len
)+2+len
+2;
627 /* Build the command at protocol level */
628 cmd
= malloc(totlen
+1);
629 if (!cmd
) redisOOM();
630 pos
= sprintf(cmd
,"*%d\r\n",argc
);
631 for (j
= 0; j
< argc
; j
++) {
632 len
= argvlen
? argvlen
[j
] : strlen(argv
[j
]);
633 pos
+= sprintf(cmd
+pos
,"$%zu\r\n",len
);
634 memcpy(cmd
+pos
,argv
[j
],len
);
639 assert(pos
== totlen
);
645 void __redisSetError(redisContext
*c
, int type
, const sds errstr
) {
647 if (errstr
!= NULL
) {
650 /* Only REDIS_ERR_IO may lack a description! */
651 assert(type
== REDIS_ERR_IO
);
652 c
->errstr
= sdsnew(strerror(errno
));
656 static redisContext
*redisContextInit() {
657 redisContext
*c
= calloc(sizeof(redisContext
),1);
660 c
->obuf
= sdsempty();
661 c
->fn
= &defaultFunctions
;
666 void redisFree(redisContext
*c
) {
667 /* Disconnect before free'ing if not yet disconnected. */
668 if (c
->flags
& REDIS_CONNECTED
)
670 if (c
->errstr
!= NULL
)
674 if (c
->reader
!= NULL
)
675 redisReplyReaderFree(c
->reader
);
679 /* Connect to a Redis instance. On error the field error in the returned
680 * context will be set to the return value of the error function.
681 * When no set of reply functions is given, the default set will be used. */
682 redisContext
*redisConnect(const char *ip
, int port
) {
683 redisContext
*c
= redisContextInit();
684 c
->flags
|= REDIS_BLOCK
;
685 c
->flags
|= REDIS_CONNECTED
;
686 redisContextConnectTcp(c
,ip
,port
);
690 redisContext
*redisConnectNonBlock(const char *ip
, int port
) {
691 redisContext
*c
= redisContextInit();
692 c
->flags
&= ~REDIS_BLOCK
;
693 c
->flags
|= REDIS_CONNECTED
;
694 redisContextConnectTcp(c
,ip
,port
);
698 redisContext
*redisConnectUnix(const char *path
) {
699 redisContext
*c
= redisContextInit();
700 c
->flags
|= REDIS_BLOCK
;
701 c
->flags
|= REDIS_CONNECTED
;
702 redisContextConnectUnix(c
,path
);
706 redisContext
*redisConnectUnixNonBlock(const char *path
) {
707 redisContext
*c
= redisContextInit();
708 c
->flags
&= ~REDIS_BLOCK
;
709 c
->flags
|= REDIS_CONNECTED
;
710 redisContextConnectUnix(c
,path
);
714 /* Set the replyObjectFunctions to use. Returns REDIS_ERR when the reader
715 * was already initialized and the function set could not be re-set.
716 * Return REDIS_OK when they could be set. */
717 int redisSetReplyObjectFunctions(redisContext
*c
, redisReplyObjectFunctions
*fn
) {
718 if (c
->reader
!= NULL
)
724 /* Helper function to lazily create a reply reader. */
725 static void __redisCreateReplyReader(redisContext
*c
) {
726 if (c
->reader
== NULL
) {
727 c
->reader
= redisReplyReaderCreate();
728 assert(redisReplyReaderSetReplyObjectFunctions(c
->reader
,c
->fn
) == REDIS_OK
);
732 /* Use this function to handle a read event on the descriptor. It will try
733 * and read some bytes from the socket and feed them to the reply parser.
735 * After this function is called, you may use redisContextReadReply to
736 * see if there is a reply available. */
737 int redisBufferRead(redisContext
*c
) {
739 int nread
= read(c
->fd
,buf
,sizeof(buf
));
741 if (errno
== EAGAIN
) {
742 /* Try again later */
744 __redisSetError(c
,REDIS_ERR_IO
,NULL
);
747 } else if (nread
== 0) {
748 __redisSetError(c
,REDIS_ERR_EOF
,
749 sdsnew("Server closed the connection"));
752 __redisCreateReplyReader(c
);
753 redisReplyReaderFeed(c
->reader
,buf
,nread
);
758 /* Write the output buffer to the socket.
760 * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
761 * succesfully written to the socket. When the buffer is empty after the
762 * write operation, "wdone" is set to 1 (if given).
764 * Returns REDIS_ERR if an error occured trying to write and sets
765 * c->error to hold the appropriate error string.
767 int redisBufferWrite(redisContext
*c
, int *done
) {
769 if (sdslen(c
->obuf
) > 0) {
770 nwritten
= write(c
->fd
,c
->obuf
,sdslen(c
->obuf
));
771 if (nwritten
== -1) {
772 if (errno
== EAGAIN
) {
773 /* Try again later */
775 __redisSetError(c
,REDIS_ERR_IO
,NULL
);
778 } else if (nwritten
> 0) {
779 if (nwritten
== (signed)sdslen(c
->obuf
)) {
781 c
->obuf
= sdsempty();
783 c
->obuf
= sdsrange(c
->obuf
,nwritten
,-1);
787 if (done
!= NULL
) *done
= (sdslen(c
->obuf
) == 0);
791 /* Internal helper function to try and get a reply from the reader,
792 * or set an error in the context otherwise. */
793 int redisGetReplyFromReader(redisContext
*c
, void **reply
) {
794 __redisCreateReplyReader(c
);
795 if (redisReplyReaderGetReply(c
->reader
,reply
) == REDIS_ERR
) {
796 __redisSetError(c
,REDIS_ERR_PROTOCOL
,
797 sdsnew(((redisReader
*)c
->reader
)->error
));
803 int redisGetReply(redisContext
*c
, void **reply
) {
807 /* Try to read pending replies */
808 if (redisGetReplyFromReader(c
,&aux
) == REDIS_ERR
)
811 /* For the blocking context, flush output buffer and read reply */
812 if (aux
== NULL
&& c
->flags
& REDIS_BLOCK
) {
813 /* Write until done */
815 if (redisBufferWrite(c
,&wdone
) == REDIS_ERR
)
819 /* Read until there is a reply */
821 if (redisBufferRead(c
) == REDIS_ERR
)
823 if (redisGetReplyFromReader(c
,&aux
) == REDIS_ERR
)
825 } while (aux
== NULL
);
828 /* Set reply object */
829 if (reply
!= NULL
) *reply
= aux
;
834 /* Helper function for the redisAppendCommand* family of functions.
836 * Write a formatted command to the output buffer. When this family
837 * is used, you need to call redisGetReply yourself to retrieve
838 * the reply (or replies in pub/sub).
840 void __redisAppendCommand(redisContext
*c
, char *cmd
, size_t len
) {
841 c
->obuf
= sdscatlen(c
->obuf
,cmd
,len
);
844 void redisvAppendCommand(redisContext
*c
, const char *format
, va_list ap
) {
847 len
= redisvFormatCommand(&cmd
,format
,ap
);
848 __redisAppendCommand(c
,cmd
,len
);
852 void redisAppendCommand(redisContext
*c
, const char *format
, ...) {
855 redisvAppendCommand(c
,format
,ap
);
859 void redisAppendCommandArgv(redisContext
*c
, int argc
, const char **argv
, const size_t *argvlen
) {
862 len
= redisFormatCommandArgv(&cmd
,argc
,argv
,argvlen
);
863 __redisAppendCommand(c
,cmd
,len
);
867 /* Helper function for the redisCommand* family of functions.
869 * Write a formatted command to the output buffer. If the given context is
870 * blocking, immediately read the reply into the "reply" pointer. When the
871 * context is non-blocking, the "reply" pointer will not be used and the
872 * command is simply appended to the write buffer.
874 * Returns the reply when a reply was succesfully retrieved. Returns NULL
875 * otherwise. When NULL is returned in a blocking context, the error field
876 * in the context will be set.
878 static void *__redisCommand(redisContext
*c
, char *cmd
, size_t len
) {
880 __redisAppendCommand(c
,cmd
,len
);
882 if (c
->flags
& REDIS_BLOCK
) {
883 if (redisGetReply(c
,&aux
) == REDIS_OK
)
890 void *redisvCommand(redisContext
*c
, const char *format
, va_list ap
) {
894 len
= redisvFormatCommand(&cmd
,format
,ap
);
895 reply
= __redisCommand(c
,cmd
,len
);
900 void *redisCommand(redisContext
*c
, const char *format
, ...) {
904 reply
= redisvCommand(c
,format
,ap
);
909 void *redisCommandArgv(redisContext
*c
, int argc
, const char **argv
, const size_t *argvlen
) {
913 len
= redisFormatCommandArgv(&cmd
,argc
,argv
,argvlen
);
914 reply
= __redisCommand(c
,cmd
,len
);