hiredis 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@ ID ivar_hiredis_error;
13
13
  * Note that the parent should always be of type T_ARRAY. */
14
14
  static void *tryParentize(const redisReadTask *task, VALUE v) {
15
15
  if (task && task->parent != NULL) {
16
- VALUE parent = (VALUE)task->parent;
16
+ VALUE parent = (VALUE)task->parent->obj;
17
17
  assert(TYPE(parent) == T_ARRAY);
18
18
  rb_ary_store(parent,task->idx,v);
19
19
  }
@@ -1,3 +1,3 @@
1
1
  module Hiredis
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -6,7 +6,7 @@ OBJ = net.o hiredis.o sds.o async.o
6
6
  BINS = hiredis-example hiredis-test
7
7
 
8
8
  uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
9
- OPTIMIZATION?=-O2
9
+ OPTIMIZATION?=-O3
10
10
  ifeq ($(uname_S),SunOS)
11
11
  CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6
12
12
  CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
@@ -31,7 +31,7 @@ else
31
31
  STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
32
32
  endif
33
33
  CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
34
- DEBUG?= -g -ggdb
34
+ DEBUG?= -g -ggdb
35
35
 
36
36
  PREFIX?= /usr/local
37
37
  INSTALL_INC= $(PREFIX)/include/hiredis
@@ -43,8 +43,6 @@ all: ${DYLIBNAME} ${BINS}
43
43
  # Deps (use make dep to generate this)
44
44
  net.o: net.c fmacros.h net.h
45
45
  async.o: async.c async.h hiredis.h sds.h util.h
46
- example-libev.o: example-libev.c hiredis.h async.h adapters/libev.h
47
- example-libevent.o: example-libevent.c hiredis.h async.h adapters/libevent.h
48
46
  example.o: example.c hiredis.h
49
47
  hiredis.o: hiredis.c hiredis.h net.h sds.h util.h
50
48
  sds.o: sds.c sds.h
@@ -60,12 +58,21 @@ dynamic: ${DYLIBNAME}
60
58
  static: ${STLIBNAME}
61
59
 
62
60
  # Binaries:
63
- hiredis-example-libevent: example-libevent.o ${DYLIBNAME}
61
+ hiredis-example-libevent: example-libevent.c adapters/libevent.h ${DYLIBNAME}
64
62
  $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -levent -Wl,-rpath,. example-libevent.c
65
63
 
66
- hiredis-example-libev: example-libev.o ${DYLIBNAME}
64
+ hiredis-example-libev: example-libev.c adapters/libev.h ${DYLIBNAME}
67
65
  $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -lev -Wl,-rpath,. example-libev.c
68
66
 
67
+ ifndef AE_DIR
68
+ hiredis-example-ae:
69
+ @echo "Please specify AE_DIR (e.g. <redis repository>/src)"
70
+ @false
71
+ else
72
+ hiredis-example-ae: example-ae.c adapters/ae.h ${DYLIBNAME}
73
+ $(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR) -L. -lhiredis -Wl,-rpath,. example-ae.c $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o
74
+ endif
75
+
69
76
  hiredis-%: %.o ${DYLIBNAME}
70
77
  $(CC) -o $@ $(CCOPT) $(DEBUG) -L. -lhiredis -Wl,-rpath,. $<
71
78
 
@@ -38,8 +38,18 @@ void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
38
38
 
39
39
  static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
40
40
  redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
41
- /* Set all bytes in the async part of the context to 0 */
42
- memset(ac+sizeof(redisContext),0,sizeof(redisAsyncContext)-sizeof(redisContext));
41
+ ac->err = 0;
42
+ ac->errstr = NULL;
43
+ ac->data = NULL;
44
+ ac->_adapter_data = NULL;
45
+ ac->evAddRead = NULL;
46
+ ac->evDelRead = NULL;
47
+ ac->evAddWrite = NULL;
48
+ ac->evDelWrite = NULL;
49
+ ac->evCleanup = NULL;
50
+ ac->onDisconnect = NULL;
51
+ ac->replies.head = NULL;
52
+ ac->replies.tail = NULL;
43
53
  return ac;
44
54
  }
45
55
 
@@ -153,7 +163,7 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
153
163
  }
154
164
 
155
165
  /* Signal event lib to clean up */
156
- if (ac->evCleanup) ac->evCleanup(ac->data);
166
+ if (ac->evCleanup) ac->evCleanup(ac->_adapter_data);
157
167
 
158
168
  /* Execute callback with proper status */
159
169
  if (ac->onDisconnect) ac->onDisconnect(ac,status);
@@ -206,7 +216,7 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
206
216
  __redisAsyncDisconnect(ac);
207
217
  } else {
208
218
  /* Always re-schedule reads */
209
- if (ac->evAddRead) ac->evAddRead(ac->data);
219
+ if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
210
220
  redisProcessCallbacks(ac);
211
221
  }
212
222
  }
@@ -220,13 +230,13 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
220
230
  } else {
221
231
  /* Continue writing when not done, stop writing otherwise */
222
232
  if (!done) {
223
- if (ac->evAddWrite) ac->evAddWrite(ac->data);
233
+ if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
224
234
  } else {
225
- if (ac->evDelWrite) ac->evDelWrite(ac->data);
235
+ if (ac->evDelWrite) ac->evDelWrite(ac->_adapter_data);
226
236
  }
227
237
 
228
238
  /* Always schedule reads when something was written */
229
- if (ac->evAddRead) ac->evAddRead(ac->data);
239
+ if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
230
240
  }
231
241
  }
232
242
 
@@ -249,7 +259,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
249
259
  __redisPushCallback(&ac->replies,&cb);
250
260
 
251
261
  /* Always schedule a write when the write buffer is non-empty */
252
- if (ac->evAddWrite) ac->evAddWrite(ac->data);
262
+ if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
253
263
 
254
264
  return REDIS_OK;
255
265
  }
@@ -31,6 +31,10 @@
31
31
  #define __HIREDIS_ASYNC_H
32
32
  #include "hiredis.h"
33
33
 
34
+ #ifdef __cplusplus
35
+ extern "C" {
36
+ #endif
37
+
34
38
  struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
35
39
 
36
40
  /* Reply callback prototype and container */
@@ -58,6 +62,12 @@ typedef struct redisAsyncContext {
58
62
  int err;
59
63
  char *errstr;
60
64
 
65
+ /* Not used by hiredis */
66
+ void *data;
67
+
68
+ /* Used by the different event lib adapters to store their private data */
69
+ void *_adapter_data;
70
+
61
71
  /* Called when the library expects to start reading/writing.
62
72
  * The supplied functions should be idempotent. */
63
73
  void (*evAddRead)(void *privdata);
@@ -65,7 +75,6 @@ typedef struct redisAsyncContext {
65
75
  void (*evAddWrite)(void *privdata);
66
76
  void (*evDelWrite)(void *privdata);
67
77
  void (*evCleanup)(void *privdata);
68
- void *data;
69
78
 
70
79
  /* Called when either the connection is terminated due to an error or per
71
80
  * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
@@ -91,4 +100,8 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
91
100
  int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
92
101
  int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
93
102
 
103
+ #ifdef __cplusplus
104
+ }
105
+ #endif
106
+
94
107
  #endif
@@ -32,6 +32,7 @@
32
32
  #include <unistd.h>
33
33
  #include <assert.h>
34
34
  #include <errno.h>
35
+ #include <ctype.h>
35
36
 
36
37
  #include "hiredis.h"
37
38
  #include "net.h"
@@ -44,10 +45,12 @@ typedef struct redisReader {
44
45
  void *reply; /* holds temporary reply */
45
46
 
46
47
  sds buf; /* read buffer */
47
- unsigned int pos; /* buffer cursor */
48
+ size_t pos; /* buffer cursor */
49
+ size_t len; /* buffer length */
48
50
 
49
51
  redisReadTask rstack[3]; /* stack of read tasks */
50
52
  int ridx; /* index of stack */
53
+ void *privdata; /* user-settable arbitrary field */
51
54
  } redisReader;
52
55
 
53
56
  static redisReply *createReplyObject(int type);
@@ -68,7 +71,7 @@ static redisReplyObjectFunctions defaultFunctions = {
68
71
 
69
72
  /* Create a reply object */
70
73
  static redisReply *createReplyObject(int type) {
71
- redisReply *r = calloc(sizeof(*r),1);
74
+ redisReply *r = malloc(sizeof(*r));
72
75
 
73
76
  if (!r) redisOOM();
74
77
  r->type = type;
@@ -88,9 +91,10 @@ void freeReplyObject(void *reply) {
88
91
  if (r->element[j]) freeReplyObject(r->element[j]);
89
92
  free(r->element);
90
93
  break;
91
- default:
92
- if (r->str != NULL)
93
- free(r->str);
94
+ case REDIS_REPLY_ERROR:
95
+ case REDIS_REPLY_STATUS:
96
+ case REDIS_REPLY_STRING:
97
+ free(r->str);
94
98
  break;
95
99
  }
96
100
  free(r);
@@ -111,7 +115,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
111
115
  r->len = len;
112
116
 
113
117
  if (task->parent) {
114
- redisReply *parent = task->parent;
118
+ redisReply *parent = task->parent->obj;
115
119
  assert(parent->type == REDIS_REPLY_ARRAY);
116
120
  parent->element[task->idx] = r;
117
121
  }
@@ -124,7 +128,7 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
124
128
  if ((r->element = calloc(sizeof(redisReply*),elements)) == NULL)
125
129
  redisOOM();
126
130
  if (task->parent) {
127
- redisReply *parent = task->parent;
131
+ redisReply *parent = task->parent->obj;
128
132
  assert(parent->type == REDIS_REPLY_ARRAY);
129
133
  parent->element[task->idx] = r;
130
134
  }
@@ -135,7 +139,7 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
135
139
  redisReply *r = createReplyObject(REDIS_REPLY_INTEGER);
136
140
  r->integer = value;
137
141
  if (task->parent) {
138
- redisReply *parent = task->parent;
142
+ redisReply *parent = task->parent->obj;
139
143
  assert(parent->type == REDIS_REPLY_ARRAY);
140
144
  parent->element[task->idx] = r;
141
145
  }
@@ -145,7 +149,7 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
145
149
  static void *createNilObject(const redisReadTask *task) {
146
150
  redisReply *r = createReplyObject(REDIS_REPLY_NIL);
147
151
  if (task->parent) {
148
- redisReply *parent = task->parent;
152
+ redisReply *parent = task->parent->obj;
149
153
  assert(parent->type == REDIS_REPLY_ARRAY);
150
154
  parent->element[task->idx] = r;
151
155
  }
@@ -154,7 +158,7 @@ static void *createNilObject(const redisReadTask *task) {
154
158
 
155
159
  static char *readBytes(redisReader *r, unsigned int bytes) {
156
160
  char *p;
157
- if (sdslen(r->buf)-r->pos >= bytes) {
161
+ if (r->len-r->pos >= bytes) {
158
162
  p = r->buf+r->pos;
159
163
  r->pos += bytes;
160
164
  return p;
@@ -162,20 +166,60 @@ static char *readBytes(redisReader *r, unsigned int bytes) {
162
166
  return NULL;
163
167
  }
164
168
 
165
- static char *seekNewline(char *s) {
166
- /* Find pointer to \r\n without strstr */
167
- while (s != NULL) {
168
- s = strchr(s,'\r');
169
- if (s != NULL) {
170
- if (s[1] == '\n')
171
- break;
172
- else
173
- s++;
169
+ /* Find pointer to \r\n. */
170
+ static char *seekNewline(char *s, size_t len) {
171
+ int pos = 0;
172
+ int _len = len-1;
173
+
174
+ /* Position should be < len-1 because the character at "pos" should be
175
+ * followed by a \n. Note that strchr cannot be used because it doesn't
176
+ * allow to search a limited length and the buffer that is being searched
177
+ * might not have a trailing NULL character. */
178
+ while (pos < _len) {
179
+ while(pos < _len && s[pos] != '\r') pos++;
180
+ if (s[pos] != '\r') {
181
+ /* Not found. */
182
+ return NULL;
174
183
  } else {
175
- break;
184
+ if (s[pos+1] == '\n') {
185
+ /* Found. */
186
+ return s+pos;
187
+ } else {
188
+ /* Continue searching. */
189
+ pos++;
190
+ }
176
191
  }
177
192
  }
178
- return s;
193
+ return NULL;
194
+ }
195
+
196
+ /* Read a long long value starting at *s, under the assumption that it will be
197
+ * terminated by \r\n. Ambiguously returns -1 for unexpected input. */
198
+ static long long readLongLong(char *s) {
199
+ long long v = 0;
200
+ int dec, mult = 1;
201
+ char c;
202
+
203
+ if (*s == '-') {
204
+ mult = -1;
205
+ s++;
206
+ } else if (*s == '+') {
207
+ mult = 1;
208
+ s++;
209
+ }
210
+
211
+ while ((c = *(s++)) != '\r') {
212
+ dec = c - '0';
213
+ if (dec >= 0 && dec < 10) {
214
+ v *= 10;
215
+ v += dec;
216
+ } else {
217
+ /* Should not happen... */
218
+ return -1;
219
+ }
220
+ }
221
+
222
+ return mult*v;
179
223
  }
180
224
 
181
225
  static char *readLine(redisReader *r, int *_len) {
@@ -183,7 +227,7 @@ static char *readLine(redisReader *r, int *_len) {
183
227
  int len;
184
228
 
185
229
  p = r->buf+r->pos;
186
- s = seekNewline(p);
230
+ s = seekNewline(p,(r->len-r->pos));
187
231
  if (s != NULL) {
188
232
  len = s-(r->buf+r->pos);
189
233
  r->pos += len+2; /* skip \r\n */
@@ -227,7 +271,7 @@ static int processLineItem(redisReader *r) {
227
271
  if ((p = readLine(r,&len)) != NULL) {
228
272
  if (r->fn) {
229
273
  if (cur->type == REDIS_REPLY_INTEGER) {
230
- obj = r->fn->createInteger(cur,strtoll(p,NULL,10));
274
+ obj = r->fn->createInteger(cur,readLongLong(p));
231
275
  } else {
232
276
  obj = r->fn->createString(cur,p,len);
233
277
  }
@@ -235,9 +279,8 @@ static int processLineItem(redisReader *r) {
235
279
  obj = (void*)(size_t)(cur->type);
236
280
  }
237
281
 
238
- /* If there is no root yet, register this object as root. */
239
- if (r->reply == NULL)
240
- r->reply = obj;
282
+ /* Set reply if this is the root object. */
283
+ if (r->ridx == 0) r->reply = obj;
241
284
  moveToNextTask(r);
242
285
  return 0;
243
286
  }
@@ -250,32 +293,36 @@ static int processBulkItem(redisReader *r) {
250
293
  char *p, *s;
251
294
  long len;
252
295
  unsigned long bytelen;
296
+ int success = 0;
253
297
 
254
298
  p = r->buf+r->pos;
255
- s = seekNewline(p);
299
+ s = seekNewline(p,r->len-r->pos);
256
300
  if (s != NULL) {
257
301
  p = r->buf+r->pos;
258
302
  bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
259
- len = strtol(p,NULL,10);
303
+ len = readLongLong(p);
260
304
 
261
305
  if (len < 0) {
262
306
  /* The nil object can always be created. */
263
307
  obj = r->fn ? r->fn->createNil(cur) :
264
308
  (void*)REDIS_REPLY_NIL;
309
+ success = 1;
265
310
  } else {
266
311
  /* Only continue when the buffer contains the entire bulk item. */
267
312
  bytelen += len+2; /* include \r\n */
268
- if (r->pos+bytelen <= sdslen(r->buf)) {
313
+ if (r->pos+bytelen <= r->len) {
269
314
  obj = r->fn ? r->fn->createString(cur,s+2,len) :
270
315
  (void*)REDIS_REPLY_STRING;
316
+ success = 1;
271
317
  }
272
318
  }
273
319
 
274
320
  /* Proceed when obj was created. */
275
- if (obj != NULL) {
321
+ if (success) {
276
322
  r->pos += bytelen;
277
- if (r->reply == NULL)
278
- r->reply = obj;
323
+
324
+ /* Set reply if this is the root object. */
325
+ if (r->ridx == 0) r->reply = obj;
279
326
  moveToNextTask(r);
280
327
  return 0;
281
328
  }
@@ -288,9 +335,19 @@ static int processMultiBulkItem(redisReader *r) {
288
335
  void *obj;
289
336
  char *p;
290
337
  long elements;
338
+ int root = 0;
339
+
340
+ /* Set error for nested multi bulks with depth > 1 */
341
+ if (r->ridx == 2) {
342
+ redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
343
+ "No support for nested multi bulk replies with depth > 1"));
344
+ return -1;
345
+ }
291
346
 
292
347
  if ((p = readLine(r,NULL)) != NULL) {
293
- elements = strtol(p,NULL,10);
348
+ elements = readLongLong(p);
349
+ root = (r->ridx == 0);
350
+
294
351
  if (elements == -1) {
295
352
  obj = r->fn ? r->fn->createNil(cur) :
296
353
  (void*)REDIS_REPLY_NIL;
@@ -302,19 +359,21 @@ static int processMultiBulkItem(redisReader *r) {
302
359
  /* Modify task stack when there are more than 0 elements. */
303
360
  if (elements > 0) {
304
361
  cur->elements = elements;
362
+ cur->obj = obj;
305
363
  r->ridx++;
306
364
  r->rstack[r->ridx].type = -1;
307
365
  r->rstack[r->ridx].elements = -1;
308
- r->rstack[r->ridx].parent = obj;
309
366
  r->rstack[r->ridx].idx = 0;
367
+ r->rstack[r->ridx].obj = NULL;
368
+ r->rstack[r->ridx].parent = cur;
369
+ r->rstack[r->ridx].privdata = r->privdata;
310
370
  } else {
311
371
  moveToNextTask(r);
312
372
  }
313
373
  }
314
374
 
315
- /* Object was created, so we can always continue. */
316
- if (r->reply == NULL)
317
- r->reply = obj;
375
+ /* Set reply if this is the root object. */
376
+ if (root) r->reply = obj;
318
377
  return 0;
319
378
  }
320
379
  return -1;
@@ -347,7 +406,7 @@ static int processItem(redisReader *r) {
347
406
  default:
348
407
  byte = sdscatrepr(sdsempty(),p,1);
349
408
  redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
350
- "protocol error, got %s as reply type byte", byte));
409
+ "Protocol error, got %s as reply type byte", byte));
351
410
  sdsfree(byte);
352
411
  return -1;
353
412
  }
@@ -368,8 +427,7 @@ static int processItem(redisReader *r) {
368
427
  case REDIS_REPLY_ARRAY:
369
428
  return processMultiBulkItem(r);
370
429
  default:
371
- redisSetReplyReaderError(r,sdscatprintf(sdsempty(),
372
- "unknown item type '%d'", cur->type));
430
+ assert(NULL);
373
431
  return -1;
374
432
  }
375
433
  }
@@ -394,6 +452,17 @@ int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFuncti
394
452
  return REDIS_ERR;
395
453
  }
396
454
 
455
+ /* Set the private data field that is used in the read tasks. This argument can
456
+ * be used to curry arbitrary data to the custom reply object functions. */
457
+ int redisReplyReaderSetPrivdata(void *reader, void *privdata) {
458
+ redisReader *r = reader;
459
+ if (r->reply == NULL) {
460
+ r->privdata = privdata;
461
+ return REDIS_OK;
462
+ }
463
+ return REDIS_ERR;
464
+ }
465
+
397
466
  /* External libraries wrapping hiredis might need access to the temporary
398
467
  * variable while the reply is built up. When the reader contains an
399
468
  * object in between receiving some bytes to parse, this object might
@@ -437,8 +506,10 @@ void redisReplyReaderFeed(void *reader, char *buf, size_t len) {
437
506
  redisReader *r = reader;
438
507
 
439
508
  /* Copy the provided buffer. */
440
- if (buf != NULL && len >= 1)
509
+ if (buf != NULL && len >= 1) {
441
510
  r->buf = sdscatlen(r->buf,buf,len);
511
+ r->len = sdslen(r->buf);
512
+ }
442
513
  }
443
514
 
444
515
  int redisReplyReaderGetReply(void *reader, void **reply) {
@@ -446,15 +517,17 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
446
517
  if (reply != NULL) *reply = NULL;
447
518
 
448
519
  /* When the buffer is empty, there will never be a reply. */
449
- if (sdslen(r->buf) == 0)
520
+ if (r->len == 0)
450
521
  return REDIS_OK;
451
522
 
452
523
  /* Set first item to process when the stack is empty. */
453
524
  if (r->ridx == -1) {
454
525
  r->rstack[0].type = -1;
455
526
  r->rstack[0].elements = -1;
456
- r->rstack[0].parent = NULL;
457
527
  r->rstack[0].idx = -1;
528
+ r->rstack[0].obj = NULL;
529
+ r->rstack[0].parent = NULL;
530
+ r->rstack[0].privdata = r->privdata;
458
531
  r->ridx = 0;
459
532
  }
460
533
 
@@ -465,14 +538,15 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
465
538
 
466
539
  /* Discard the consumed part of the buffer. */
467
540
  if (r->pos > 0) {
468
- if (r->pos == sdslen(r->buf)) {
541
+ if (r->pos == r->len) {
469
542
  /* sdsrange has a quirck on this edge case. */
470
543
  sdsfree(r->buf);
471
544
  r->buf = sdsempty();
472
545
  } else {
473
- r->buf = sdsrange(r->buf,r->pos,sdslen(r->buf));
546
+ r->buf = sdsrange(r->buf,r->pos,r->len);
474
547
  }
475
548
  r->pos = 0;
549
+ r->len = sdslen(r->buf);
476
550
  }
477
551
 
478
552
  /* Emit a reply when there is one. */
@@ -481,7 +555,7 @@ int redisReplyReaderGetReply(void *reader, void **reply) {
481
555
  r->reply = NULL;
482
556
 
483
557
  /* Destroy the buffer when it is empty and is quite large. */
484
- if (sdslen(r->buf) == 0 && sdsavail(r->buf) > 16*1024) {
558
+ if (r->len == 0 && sdsavail(r->buf) > 16*1024) {
485
559
  sdsfree(r->buf);
486
560
  r->buf = sdsempty();
487
561
  r->pos = 0;
@@ -525,6 +599,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
525
599
  char *cmd = NULL; /* final command */
526
600
  int pos; /* position in final command */
527
601
  sds current; /* current argument */
602
+ int interpolated = 0; /* did we do interpolation on an argument? */
528
603
  char **argv = NULL;
529
604
  int argc = 0, j;
530
605
  int totlen = 0;
@@ -541,6 +616,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
541
616
  if (sdslen(current) != 0) {
542
617
  addArgument(current, &argv, &argc, &totlen);
543
618
  current = sdsempty();
619
+ interpolated = 0;
544
620
  }
545
621
  } else {
546
622
  current = sdscatlen(current,c,1);
@@ -549,16 +625,74 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
549
625
  switch(c[1]) {
550
626
  case 's':
551
627
  arg = va_arg(ap,char*);
552
- current = sdscat(current,arg);
628
+ size = strlen(arg);
629
+ if (size > 0)
630
+ current = sdscatlen(current,arg,size);
631
+ interpolated = 1;
553
632
  break;
554
633
  case 'b':
555
634
  arg = va_arg(ap,char*);
556
635
  size = va_arg(ap,size_t);
557
- current = sdscatlen(current,arg,size);
636
+ if (size > 0)
637
+ current = sdscatlen(current,arg,size);
638
+ interpolated = 1;
558
639
  break;
559
640
  case '%':
560
- cmd = sdscat(cmd,"%");
641
+ current = sdscat(current,"%");
561
642
  break;
643
+ default:
644
+ /* Try to detect printf format */
645
+ {
646
+ char _format[16];
647
+ const char *_p = c+1;
648
+ size_t _l = 0;
649
+ va_list _cpy;
650
+
651
+ /* Flags */
652
+ if (*_p != '\0' && *_p == '#') _p++;
653
+ if (*_p != '\0' && *_p == '0') _p++;
654
+ if (*_p != '\0' && *_p == '-') _p++;
655
+ if (*_p != '\0' && *_p == ' ') _p++;
656
+ if (*_p != '\0' && *_p == '+') _p++;
657
+
658
+ /* Field width */
659
+ while (*_p != '\0' && isdigit(*_p)) _p++;
660
+
661
+ /* Precision */
662
+ if (*_p == '.') {
663
+ _p++;
664
+ while (*_p != '\0' && isdigit(*_p)) _p++;
665
+ }
666
+
667
+ /* Modifiers */
668
+ if (*_p != '\0') {
669
+ if (*_p == 'h' || *_p == 'l') {
670
+ /* Allow a single repetition for these modifiers */
671
+ if (_p[0] == _p[1]) _p++;
672
+ _p++;
673
+ }
674
+ }
675
+
676
+ /* Conversion specifier */
677
+ if (*_p != '\0' && strchr("diouxXeEfFgGaA",*_p) != NULL) {
678
+ _l = (_p+1)-c;
679
+ if (_l < sizeof(_format)-2) {
680
+ memcpy(_format,c,_l);
681
+ _format[_l] = '\0';
682
+ va_copy(_cpy,ap);
683
+ current = sdscatvprintf(current,_format,_cpy);
684
+ interpolated = 1;
685
+ va_end(_cpy);
686
+
687
+ /* Update current position (note: outer blocks
688
+ * increment c twice so compensate here) */
689
+ c = _p-1;
690
+ }
691
+ }
692
+
693
+ /* Consume and discard vararg */
694
+ va_arg(ap,void);
695
+ }
562
696
  }
563
697
  c++;
564
698
  }
@@ -566,7 +700,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
566
700
  }
567
701
 
568
702
  /* Add the last argument if needed */
569
- if (sdslen(current) != 0) {
703
+ if (interpolated || sdslen(current) != 0) {
570
704
  addArgument(current, &argv, &argc, &totlen);
571
705
  } else {
572
706
  sdsfree(current);
@@ -691,7 +825,6 @@ void redisFree(redisContext *c) {
691
825
  redisContext *redisConnect(const char *ip, int port) {
692
826
  redisContext *c = redisContextInit();
693
827
  c->flags |= REDIS_BLOCK;
694
- c->flags |= REDIS_CONNECTED;
695
828
  redisContextConnectTcp(c,ip,port);
696
829
  return c;
697
830
  }
@@ -699,7 +832,6 @@ redisContext *redisConnect(const char *ip, int port) {
699
832
  redisContext *redisConnectNonBlock(const char *ip, int port) {
700
833
  redisContext *c = redisContextInit();
701
834
  c->flags &= ~REDIS_BLOCK;
702
- c->flags |= REDIS_CONNECTED;
703
835
  redisContextConnectTcp(c,ip,port);
704
836
  return c;
705
837
  }
@@ -707,7 +839,6 @@ redisContext *redisConnectNonBlock(const char *ip, int port) {
707
839
  redisContext *redisConnectUnix(const char *path) {
708
840
  redisContext *c = redisContextInit();
709
841
  c->flags |= REDIS_BLOCK;
710
- c->flags |= REDIS_CONNECTED;
711
842
  redisContextConnectUnix(c,path);
712
843
  return c;
713
844
  }
@@ -715,7 +846,6 @@ redisContext *redisConnectUnix(const char *path) {
715
846
  redisContext *redisConnectUnixNonBlock(const char *path) {
716
847
  redisContext *c = redisContextInit();
717
848
  c->flags &= ~REDIS_BLOCK;
718
- c->flags |= REDIS_CONNECTED;
719
849
  redisContextConnectUnix(c,path);
720
850
  return c;
721
851
  }
@@ -34,7 +34,7 @@
34
34
 
35
35
  #define HIREDIS_MAJOR 0
36
36
  #define HIREDIS_MINOR 9
37
- #define HIREDIS_PATCH 0
37
+ #define HIREDIS_PATCH 1
38
38
 
39
39
  #define REDIS_ERR -1
40
40
  #define REDIS_OK 0
@@ -69,6 +69,10 @@
69
69
  #define REDIS_REPLY_NIL 4
70
70
  #define REDIS_REPLY_STATUS 5
71
71
 
72
+ #ifdef __cplusplus
73
+ extern "C" {
74
+ #endif
75
+
72
76
  /* This is the reply object returned by redisCommand() */
73
77
  typedef struct redisReply {
74
78
  int type; /* REDIS_REPLY_* */
@@ -82,8 +86,10 @@ typedef struct redisReply {
82
86
  typedef struct redisReadTask {
83
87
  int type;
84
88
  int elements; /* number of elements in multibulk container */
85
- void *parent; /* optional pointer to parent object */
86
89
  int idx; /* index in parent (array) object */
90
+ void *obj; /* holds user-generated value for a read task */
91
+ struct redisReadTask *parent; /* parent task */
92
+ void *privdata; /* user-settable arbitrary field */
87
93
  } redisReadTask;
88
94
 
89
95
  typedef struct redisReplyObjectFunctions {
@@ -112,6 +118,7 @@ typedef struct redisContext {
112
118
  void freeReplyObject(void *reply);
113
119
  void *redisReplyReaderCreate();
114
120
  int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFunctions *fn);
121
+ int redisReplyReaderSetPrivdata(void *reader, void *privdata);
115
122
  void *redisReplyReaderGetObject(void *reader);
116
123
  char *redisReplyReaderGetError(void *reader);
117
124
  void redisReplyReaderFree(void *ptr);
@@ -154,4 +161,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap);
154
161
  void *redisCommand(redisContext *c, const char *format, ...);
155
162
  void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
156
163
 
164
+ #ifdef __cplusplus
165
+ }
166
+ #endif
167
+
157
168
  #endif
@@ -114,7 +114,7 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
114
114
  he = gethostbyname(addr);
115
115
  if (he == NULL) {
116
116
  __redisSetError(c,REDIS_ERR_OTHER,
117
- sdscatprintf(sdsempty(),"can't resolve: %s",addr));
117
+ sdscatprintf(sdsempty(),"Can't resolve: %s",addr));
118
118
  close(s);
119
119
  return REDIS_ERR;
120
120
  }
@@ -137,6 +137,7 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
137
137
  }
138
138
 
139
139
  c->fd = s;
140
+ c->flags |= REDIS_CONNECTED;
140
141
  return REDIS_OK;
141
142
  }
142
143
 
@@ -163,5 +164,6 @@ int redisContextConnectUnix(redisContext *c, const char *path) {
163
164
  }
164
165
 
165
166
  c->fd = s;
167
+ c->flags |= REDIS_CONNECTED;
166
168
  return REDIS_OK;
167
169
  }
@@ -5,6 +5,7 @@
5
5
  #include <sys/time.h>
6
6
  #include <assert.h>
7
7
  #include <unistd.h>
8
+ #include <signal.h>
8
9
 
9
10
  #include "hiredis.h"
10
11
 
@@ -46,17 +47,59 @@ static void test_format_commands() {
46
47
  len == 4+4+(3+2)+4+(3+2)+4+(3+2));
47
48
  free(cmd);
48
49
 
50
+ test("Format command with %%s and an empty string: ");
51
+ len = redisFormatCommand(&cmd,"SET %s %s","foo","");
52
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
53
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
54
+ free(cmd);
55
+
49
56
  test("Format command with %%b string interpolation: ");
50
57
  len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
51
58
  test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
52
59
  len == 4+4+(3+2)+4+(3+2)+4+(3+2));
53
60
  free(cmd);
54
61
 
62
+ test("Format command with %%b and an empty string: ");
63
+ len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"",0);
64
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
65
+ len == 4+4+(3+2)+4+(3+2)+4+(0+2));
66
+ free(cmd);
67
+
68
+ test("Format command with literal %%: ");
69
+ len = redisFormatCommand(&cmd,"SET %% %%");
70
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
71
+ len == 4+4+(3+2)+4+(1+2)+4+(1+2));
72
+ free(cmd);
73
+
74
+ test("Format command with printf-delegation (long long): ");
75
+ len = redisFormatCommand(&cmd,"key:%08lld",1234ll);
76
+ test_cond(strncmp(cmd,"*1\r\n$12\r\nkey:00001234\r\n",len) == 0 &&
77
+ len == 4+5+(12+2));
78
+ free(cmd);
79
+
80
+ test("Format command with printf-delegation (float): ");
81
+ len = redisFormatCommand(&cmd,"v:%06.1f",12.34f);
82
+ test_cond(strncmp(cmd,"*1\r\n$8\r\nv:0012.3\r\n",len) == 0 &&
83
+ len == 4+4+(8+2));
84
+ free(cmd);
85
+
86
+ test("Format command with printf-delegation and extra interpolation: ");
87
+ len = redisFormatCommand(&cmd,"key:%d %b",1234,"foo",3);
88
+ test_cond(strncmp(cmd,"*2\r\n$8\r\nkey:1234\r\n$3\r\nfoo\r\n",len) == 0 &&
89
+ len == 4+4+(8+2)+4+(3+2));
90
+ free(cmd);
91
+
92
+ test("Format command with wrong printf format and extra interpolation: ");
93
+ len = redisFormatCommand(&cmd,"key:%08p %b",1234,"foo",3);
94
+ test_cond(strncmp(cmd,"*2\r\n$6\r\nkey:8p\r\n$3\r\nfoo\r\n",len) == 0 &&
95
+ len == 4+4+(6+2)+4+(3+2));
96
+ free(cmd);
97
+
55
98
  const char *argv[3];
56
99
  argv[0] = "SET";
57
- argv[1] = "foo";
100
+ argv[1] = "foo\0xxx";
58
101
  argv[2] = "bar";
59
- size_t lens[3] = { 3, 3, 3 };
102
+ size_t lens[3] = { 3, 7, 3 };
60
103
  int argc = 3;
61
104
 
62
105
  test("Format command by passing argc/argv without lengths: ");
@@ -67,38 +110,29 @@ static void test_format_commands() {
67
110
 
68
111
  test("Format command by passing argc/argv with lengths: ");
69
112
  len = redisFormatCommandArgv(&cmd,argc,argv,lens);
70
- test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
71
- len == 4+4+(3+2)+4+(3+2)+4+(3+2));
113
+ test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
114
+ len == 4+4+(3+2)+4+(7+2)+4+(3+2));
72
115
  free(cmd);
73
116
  }
74
117
 
75
118
  static void test_blocking_connection() {
76
119
  redisContext *c;
77
120
  redisReply *reply;
121
+ int major, minor;
78
122
 
79
- __connect(&c);
80
- test("Returns I/O error when the connection is lost: ");
81
- reply = redisCommand(c,"QUIT");
82
- test_cond(strcasecmp(reply->str,"OK") == 0 && redisCommand(c,"PING") == NULL);
83
-
84
- /* Two conditions may happen, depending on the type of connection.
85
- * When connected via TCP, the socket will not yet be aware of the closed
86
- * connection and the write(2) call will succeed, but the read(2) will
87
- * result in an EOF. When connected via Unix sockets, the socket will be
88
- * immediately aware that it was closed and fail on the write(2) call. */
89
- if (use_unix) {
90
- fprintf(stderr,"Error: %s\n", c->errstr);
91
- assert(c->err == REDIS_ERR_IO &&
92
- strcmp(c->errstr,"Broken pipe") == 0);
93
- } else {
94
- fprintf(stderr,"Error: %s\n", c->errstr);
95
- assert(c->err == REDIS_ERR_EOF &&
96
- strcmp(c->errstr,"Server closed the connection") == 0);
97
- }
98
- freeReplyObject(reply);
123
+ test("Returns error when host cannot be resolved: ");
124
+ c = redisConnect((char*)"idontexist.local", 6379);
125
+ test_cond(c->err == REDIS_ERR_OTHER &&
126
+ strcmp(c->errstr,"Can't resolve: idontexist.local") == 0);
99
127
  redisFree(c);
100
128
 
101
- __connect(&c); /* reconnect */
129
+ test("Returns error when the port is not open: ");
130
+ c = redisConnect((char*)"localhost", 56380);
131
+ test_cond(c->err == REDIS_ERR_IO &&
132
+ strcmp(c->errstr,"Connection refused") == 0);
133
+ redisFree(c);
134
+
135
+ __connect(&c);
102
136
  test("Is able to deliver commands: ");
103
137
  reply = redisCommand(c,"PING");
104
138
  test_cond(reply->type == REDIS_REPLY_STATUS &&
@@ -111,12 +145,9 @@ static void test_blocking_connection() {
111
145
 
112
146
  /* Make sure the DB is emtpy */
113
147
  reply = redisCommand(c,"DBSIZE");
114
- if (reply->type != REDIS_REPLY_INTEGER ||
115
- reply->integer != 0) {
116
- printf("Sorry DB 9 is not empty, test can not continue\n");
148
+ if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) {
149
+ printf("Database #9 is not empty, test can not continue\n");
117
150
  exit(1);
118
- } else {
119
- printf("DB 9 is empty... test can continue\n");
120
151
  }
121
152
  freeReplyObject(reply);
122
153
 
@@ -182,6 +213,43 @@ static void test_blocking_connection() {
182
213
  reply->element[1]->type == REDIS_REPLY_STATUS &&
183
214
  strcasecmp(reply->element[1]->str,"pong") == 0);
184
215
  freeReplyObject(reply);
216
+
217
+ {
218
+ /* Find out Redis version to determine the path for the next test */
219
+ const char *field = "redis_version:";
220
+ char *p, *eptr;
221
+
222
+ reply = redisCommand(c,"INFO");
223
+ p = strstr(reply->str,field);
224
+ major = strtol(p+strlen(field),&eptr,10);
225
+ p = eptr+1; /* char next to the first "." */
226
+ minor = strtol(p,&eptr,10);
227
+ freeReplyObject(reply);
228
+ }
229
+
230
+ test("Returns I/O error when the connection is lost: ");
231
+ reply = redisCommand(c,"QUIT");
232
+ if (major >= 2 && minor > 0) {
233
+ /* > 2.0 returns OK on QUIT and read() should be issued once more
234
+ * to know the descriptor is at EOF. */
235
+ test_cond(strcasecmp(reply->str,"OK") == 0 &&
236
+ redisGetReply(c,(void**)&reply) == REDIS_ERR);
237
+ freeReplyObject(reply);
238
+ } else {
239
+ test_cond(reply == NULL);
240
+ }
241
+
242
+ /* On 2.0, QUIT will cause the connection to be closed immediately and
243
+ * the read(2) for the reply on QUIT will set the error to EOF.
244
+ * On >2.0, QUIT will return with OK and another read(2) needed to be
245
+ * issued to find out the socket was closed by the server. In both
246
+ * conditions, the error will be set to EOF. */
247
+ assert(c->err == REDIS_ERR_EOF &&
248
+ strcmp(c->errstr,"Server closed the connection") == 0);
249
+
250
+ /* Clean up context and reconnect again */
251
+ redisFree(c);
252
+ __connect(&c);
185
253
  }
186
254
 
187
255
  static void test_reply_reader() {
@@ -196,7 +264,7 @@ static void test_reply_reader() {
196
264
  ret = redisReplyReaderGetReply(reader,NULL);
197
265
  err = redisReplyReaderGetError(reader);
198
266
  test_cond(ret == REDIS_ERR &&
199
- strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
267
+ strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
200
268
  redisReplyReaderFree(reader);
201
269
 
202
270
  /* when the reply already contains multiple items, they must be free'd
@@ -209,7 +277,18 @@ static void test_reply_reader() {
209
277
  ret = redisReplyReaderGetReply(reader,NULL);
210
278
  err = redisReplyReaderGetError(reader);
211
279
  test_cond(ret == REDIS_ERR &&
212
- strcasecmp(err,"protocol error, got \"@\" as reply type byte") == 0);
280
+ strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
281
+ redisReplyReaderFree(reader);
282
+
283
+ test("Set error on nested multi bulks with depth > 1: ");
284
+ reader = redisReplyReaderCreate();
285
+ redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
286
+ redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
287
+ redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
288
+ ret = redisReplyReaderGetReply(reader,NULL);
289
+ err = redisReplyReaderGetError(reader);
290
+ test_cond(ret == REDIS_ERR &&
291
+ strncasecmp(err,"No support for",14) == 0);
213
292
  redisReplyReaderFree(reader);
214
293
 
215
294
  test("Works with NULL functions for reply: ");
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiredis
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 19
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
8
  - 1
8
- - 3
9
- version: 0.1.3
9
+ - 4
10
+ version: 0.1.4
10
11
  platform: ruby
11
12
  authors:
12
13
  - Pieter Noordhuis
@@ -14,7 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-11-06 00:00:00 -03:00
18
+ date: 2010-12-06 00:00:00 +01:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
@@ -25,6 +26,7 @@ dependencies:
25
26
  requirements:
26
27
  - - ~>
27
28
  - !ruby/object:Gem::Version
29
+ hash: 1
28
30
  segments:
29
31
  - 0
30
32
  - 7
@@ -40,6 +42,7 @@ dependencies:
40
42
  requirements:
41
43
  - - ~>
42
44
  - !ruby/object:Gem::Version
45
+ hash: 9
43
46
  segments:
44
47
  - 2
45
48
  - 1
@@ -94,6 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
97
  requirements:
95
98
  - - ">="
96
99
  - !ruby/object:Gem::Version
100
+ hash: 3
97
101
  segments:
98
102
  - 0
99
103
  version: "0"
@@ -102,6 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
106
  requirements:
103
107
  - - ">="
104
108
  - !ruby/object:Gem::Version
109
+ hash: 3
105
110
  segments:
106
111
  - 0
107
112
  version: "0"