]> git.saurik.com Git - redis.git/blob - src/t_hash.c
Merge pull request #593 from steevel/unstable
[redis.git] / src / t_hash.c
1 #include "redis.h"
2 #include <math.h>
3
4 /*-----------------------------------------------------------------------------
5 * Hash type API
6 *----------------------------------------------------------------------------*/
7
8 /* Check the length of a number of objects to see if we need to convert a
9 * ziplist to a real hash. Note that we only check string encoded objects
10 * as their string length can be queried in constant time. */
11 void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
12 int i;
13
14 if (o->encoding != REDIS_ENCODING_ZIPLIST) return;
15
16 for (i = start; i <= end; i++) {
17 if (argv[i]->encoding == REDIS_ENCODING_RAW &&
18 sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
19 {
20 hashTypeConvert(o, REDIS_ENCODING_HT);
21 break;
22 }
23 }
24 }
25
26 /* Encode given objects in-place when the hash uses a dict. */
27 void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) {
28 if (subject->encoding == REDIS_ENCODING_HT) {
29 if (o1) *o1 = tryObjectEncoding(*o1);
30 if (o2) *o2 = tryObjectEncoding(*o2);
31 }
32 }
33
34 /* Get the value from a ziplist encoded hash, identified by field.
35 * Returns -1 when the field cannot be found. */
36 int hashTypeGetFromZiplist(robj *o, robj *field,
37 unsigned char **vstr,
38 unsigned int *vlen,
39 long long *vll)
40 {
41 unsigned char *zl, *fptr = NULL, *vptr = NULL;
42 int ret;
43
44 redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST);
45
46 field = getDecodedObject(field);
47
48 zl = o->ptr;
49 fptr = ziplistIndex(zl, ZIPLIST_HEAD);
50 if (fptr != NULL) {
51 fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
52 if (fptr != NULL) {
53 /* Grab pointer to the value (fptr points to the field) */
54 vptr = ziplistNext(zl, fptr);
55 redisAssert(vptr != NULL);
56 }
57 }
58
59 decrRefCount(field);
60
61 if (vptr != NULL) {
62 ret = ziplistGet(vptr, vstr, vlen, vll);
63 redisAssert(ret);
64 return 0;
65 }
66
67 return -1;
68 }
69
70 /* Get the value from a hash table encoded hash, identified by field.
71 * Returns -1 when the field cannot be found. */
72 int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) {
73 dictEntry *de;
74
75 redisAssert(o->encoding == REDIS_ENCODING_HT);
76
77 de = dictFind(o->ptr, field);
78 if (de == NULL) return -1;
79 *value = dictGetVal(de);
80 return 0;
81 }
82
83 /* Higher level function of hashTypeGet*() that always returns a Redis
84 * object (either new or with refcount incremented), so that the caller
85 * can retain a reference or call decrRefCount after the usage.
86 *
87 * The lower level function can prevent copy on write so it is
88 * the preferred way of doing read operations. */
89 robj *hashTypeGetObject(robj *o, robj *field) {
90 robj *value = NULL;
91
92 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
93 unsigned char *vstr = NULL;
94 unsigned int vlen = UINT_MAX;
95 long long vll = LLONG_MAX;
96
97 if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) {
98 if (vstr) {
99 value = createStringObject((char*)vstr, vlen);
100 } else {
101 value = createStringObjectFromLongLong(vll);
102 }
103 }
104
105 } else if (o->encoding == REDIS_ENCODING_HT) {
106 robj *aux;
107
108 if (hashTypeGetFromHashTable(o, field, &aux) == 0) {
109 incrRefCount(aux);
110 value = aux;
111 }
112 } else {
113 redisPanic("Unknown hash encoding");
114 }
115 return value;
116 }
117
118 /* Test if the specified field exists in the given hash. Returns 1 if the field
119 * exists, and 0 when it doesn't. */
120 int hashTypeExists(robj *o, robj *field) {
121 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
122 unsigned char *vstr = NULL;
123 unsigned int vlen = UINT_MAX;
124 long long vll = LLONG_MAX;
125
126 if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1;
127 } else if (o->encoding == REDIS_ENCODING_HT) {
128 robj *aux;
129
130 if (hashTypeGetFromHashTable(o, field, &aux) == 0) return 1;
131 } else {
132 redisPanic("Unknown hash encoding");
133 }
134 return 0;
135 }
136
137 /* Add an element, discard the old if the key already exists.
138 * Return 0 on insert and 1 on update.
139 * This function will take care of incrementing the reference count of the
140 * retained fields and value objects. */
141 int hashTypeSet(robj *o, robj *field, robj *value) {
142 int update = 0;
143
144 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
145 unsigned char *zl, *fptr, *vptr;
146
147 field = getDecodedObject(field);
148 value = getDecodedObject(value);
149
150 zl = o->ptr;
151 fptr = ziplistIndex(zl, ZIPLIST_HEAD);
152 if (fptr != NULL) {
153 fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
154 if (fptr != NULL) {
155 /* Grab pointer to the value (fptr points to the field) */
156 vptr = ziplistNext(zl, fptr);
157 redisAssert(vptr != NULL);
158 update = 1;
159
160 /* Delete value */
161 zl = ziplistDelete(zl, &vptr);
162
163 /* Insert new value */
164 zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr));
165 }
166 }
167
168 if (!update) {
169 /* Push new field/value pair onto the tail of the ziplist */
170 zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
171 zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL);
172 }
173 o->ptr = zl;
174 decrRefCount(field);
175 decrRefCount(value);
176
177 /* Check if the ziplist needs to be converted to a hash table */
178 if (hashTypeLength(o) > server.hash_max_ziplist_entries)
179 hashTypeConvert(o, REDIS_ENCODING_HT);
180 } else if (o->encoding == REDIS_ENCODING_HT) {
181 if (dictReplace(o->ptr, field, value)) { /* Insert */
182 incrRefCount(field);
183 } else { /* Update */
184 update = 1;
185 }
186 incrRefCount(value);
187 } else {
188 redisPanic("Unknown hash encoding");
189 }
190 return update;
191 }
192
193 /* Delete an element from a hash.
194 * Return 1 on deleted and 0 on not found. */
195 int hashTypeDelete(robj *o, robj *field) {
196 int deleted = 0;
197
198 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
199 unsigned char *zl, *fptr;
200
201 field = getDecodedObject(field);
202
203 zl = o->ptr;
204 fptr = ziplistIndex(zl, ZIPLIST_HEAD);
205 if (fptr != NULL) {
206 fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
207 if (fptr != NULL) {
208 zl = ziplistDelete(zl,&fptr);
209 zl = ziplistDelete(zl,&fptr);
210 o->ptr = zl;
211 deleted = 1;
212 }
213 }
214
215 decrRefCount(field);
216
217 } else if (o->encoding == REDIS_ENCODING_HT) {
218 if (dictDelete((dict*)o->ptr, field) == REDIS_OK) {
219 deleted = 1;
220
221 /* Always check if the dictionary needs a resize after a delete. */
222 if (htNeedsResize(o->ptr)) dictResize(o->ptr);
223 }
224
225 } else {
226 redisPanic("Unknown hash encoding");
227 }
228
229 return deleted;
230 }
231
232 /* Return the number of elements in a hash. */
233 unsigned long hashTypeLength(robj *o) {
234 unsigned long length = ULONG_MAX;
235
236 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
237 length = ziplistLen(o->ptr) / 2;
238 } else if (o->encoding == REDIS_ENCODING_HT) {
239 length = dictSize((dict*)o->ptr);
240 } else {
241 redisPanic("Unknown hash encoding");
242 }
243
244 return length;
245 }
246
247 hashTypeIterator *hashTypeInitIterator(robj *subject) {
248 hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator));
249 hi->subject = subject;
250 hi->encoding = subject->encoding;
251
252 if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
253 hi->fptr = NULL;
254 hi->vptr = NULL;
255 } else if (hi->encoding == REDIS_ENCODING_HT) {
256 hi->di = dictGetIterator(subject->ptr);
257 } else {
258 redisPanic("Unknown hash encoding");
259 }
260
261 return hi;
262 }
263
264 void hashTypeReleaseIterator(hashTypeIterator *hi) {
265 if (hi->encoding == REDIS_ENCODING_HT) {
266 dictReleaseIterator(hi->di);
267 }
268
269 zfree(hi);
270 }
271
272 /* Move to the next entry in the hash. Return REDIS_OK when the next entry
273 * could be found and REDIS_ERR when the iterator reaches the end. */
274 int hashTypeNext(hashTypeIterator *hi) {
275 if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
276 unsigned char *zl;
277 unsigned char *fptr, *vptr;
278
279 zl = hi->subject->ptr;
280 fptr = hi->fptr;
281 vptr = hi->vptr;
282
283 if (fptr == NULL) {
284 /* Initialize cursor */
285 redisAssert(vptr == NULL);
286 fptr = ziplistIndex(zl, 0);
287 } else {
288 /* Advance cursor */
289 redisAssert(vptr != NULL);
290 fptr = ziplistNext(zl, vptr);
291 }
292 if (fptr == NULL) return REDIS_ERR;
293
294 /* Grab pointer to the value (fptr points to the field) */
295 vptr = ziplistNext(zl, fptr);
296 redisAssert(vptr != NULL);
297
298 /* fptr, vptr now point to the first or next pair */
299 hi->fptr = fptr;
300 hi->vptr = vptr;
301 } else if (hi->encoding == REDIS_ENCODING_HT) {
302 if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR;
303 } else {
304 redisPanic("Unknown hash encoding");
305 }
306 return REDIS_OK;
307 }
308
309 /* Get the field or value at iterator cursor, for an iterator on a hash value
310 * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */
311 void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what,
312 unsigned char **vstr,
313 unsigned int *vlen,
314 long long *vll)
315 {
316 int ret;
317
318 redisAssert(hi->encoding == REDIS_ENCODING_ZIPLIST);
319
320 if (what & REDIS_HASH_KEY) {
321 ret = ziplistGet(hi->fptr, vstr, vlen, vll);
322 redisAssert(ret);
323 } else {
324 ret = ziplistGet(hi->vptr, vstr, vlen, vll);
325 redisAssert(ret);
326 }
327 }
328
329 /* Get the field or value at iterator cursor, for an iterator on a hash value
330 * encoded as a ziplist. Prototype is similar to `hashTypeGetFromHashTable`. */
331 void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst) {
332 redisAssert(hi->encoding == REDIS_ENCODING_HT);
333
334 if (what & REDIS_HASH_KEY) {
335 *dst = dictGetKey(hi->de);
336 } else {
337 *dst = dictGetVal(hi->de);
338 }
339 }
340
341 /* A non copy-on-write friendly but higher level version of hashTypeCurrent*()
342 * that returns an object with incremented refcount (or a new object). It is up
343 * to the caller to decrRefCount() the object if no reference is retained. */
344 robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) {
345 robj *dst;
346
347 if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
348 unsigned char *vstr = NULL;
349 unsigned int vlen = UINT_MAX;
350 long long vll = LLONG_MAX;
351
352 hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
353 if (vstr) {
354 dst = createStringObject((char*)vstr, vlen);
355 } else {
356 dst = createStringObjectFromLongLong(vll);
357 }
358
359 } else if (hi->encoding == REDIS_ENCODING_HT) {
360 hashTypeCurrentFromHashTable(hi, what, &dst);
361 incrRefCount(dst);
362
363 } else {
364 redisPanic("Unknown hash encoding");
365 }
366
367 return dst;
368 }
369
370 robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) {
371 robj *o = lookupKeyWrite(c->db,key);
372 if (o == NULL) {
373 o = createHashObject();
374 dbAdd(c->db,key,o);
375 } else {
376 if (o->type != REDIS_HASH) {
377 addReply(c,shared.wrongtypeerr);
378 return NULL;
379 }
380 }
381 return o;
382 }
383
384 void hashTypeConvertZiplist(robj *o, int enc) {
385 redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST);
386
387 if (enc == REDIS_ENCODING_ZIPLIST) {
388 /* Nothing to do... */
389
390 } else if (enc == REDIS_ENCODING_HT) {
391 hashTypeIterator *hi;
392 dict *dict;
393 int ret;
394
395 hi = hashTypeInitIterator(o);
396 dict = dictCreate(&hashDictType, NULL);
397
398 while (hashTypeNext(hi) != REDIS_ERR) {
399 robj *field, *value;
400
401 field = hashTypeCurrentObject(hi, REDIS_HASH_KEY);
402 field = tryObjectEncoding(field);
403 value = hashTypeCurrentObject(hi, REDIS_HASH_VALUE);
404 value = tryObjectEncoding(value);
405 ret = dictAdd(dict, field, value);
406 if (ret != DICT_OK) {
407 redisLogHexDump(REDIS_WARNING,"ziplist with dup elements dump",
408 o->ptr,ziplistBlobLen(o->ptr));
409 redisAssert(ret == DICT_OK);
410 }
411 }
412
413 hashTypeReleaseIterator(hi);
414 zfree(o->ptr);
415
416 o->encoding = REDIS_ENCODING_HT;
417 o->ptr = dict;
418
419 } else {
420 redisPanic("Unknown hash encoding");
421 }
422 }
423
424 void hashTypeConvert(robj *o, int enc) {
425 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
426 hashTypeConvertZiplist(o, enc);
427 } else if (o->encoding == REDIS_ENCODING_HT) {
428 redisPanic("Not implemented");
429 } else {
430 redisPanic("Unknown hash encoding");
431 }
432 }
433
434 /*-----------------------------------------------------------------------------
435 * Hash type commands
436 *----------------------------------------------------------------------------*/
437
438 void hsetCommand(redisClient *c) {
439 int update;
440 robj *o;
441
442 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
443 hashTypeTryConversion(o,c->argv,2,3);
444 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
445 update = hashTypeSet(o,c->argv[2],c->argv[3]);
446 addReply(c, update ? shared.czero : shared.cone);
447 signalModifiedKey(c->db,c->argv[1]);
448 server.dirty++;
449 }
450
451 void hsetnxCommand(redisClient *c) {
452 robj *o;
453 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
454 hashTypeTryConversion(o,c->argv,2,3);
455
456 if (hashTypeExists(o, c->argv[2])) {
457 addReply(c, shared.czero);
458 } else {
459 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
460 hashTypeSet(o,c->argv[2],c->argv[3]);
461 addReply(c, shared.cone);
462 signalModifiedKey(c->db,c->argv[1]);
463 server.dirty++;
464 }
465 }
466
467 void hmsetCommand(redisClient *c) {
468 int i;
469 robj *o;
470
471 if ((c->argc % 2) == 1) {
472 addReplyError(c,"wrong number of arguments for HMSET");
473 return;
474 }
475
476 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
477 hashTypeTryConversion(o,c->argv,2,c->argc-1);
478 for (i = 2; i < c->argc; i += 2) {
479 hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
480 hashTypeSet(o,c->argv[i],c->argv[i+1]);
481 }
482 addReply(c, shared.ok);
483 signalModifiedKey(c->db,c->argv[1]);
484 server.dirty++;
485 }
486
487 void hincrbyCommand(redisClient *c) {
488 long long value, incr, oldvalue;
489 robj *o, *current, *new;
490
491 if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
492 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
493 if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
494 if (getLongLongFromObjectOrReply(c,current,&value,
495 "hash value is not an integer") != REDIS_OK) {
496 decrRefCount(current);
497 return;
498 }
499 decrRefCount(current);
500 } else {
501 value = 0;
502 }
503
504 oldvalue = value;
505 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
506 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
507 addReplyError(c,"increment or decrement would overflow");
508 return;
509 }
510 value += incr;
511 new = createStringObjectFromLongLong(value);
512 hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
513 hashTypeSet(o,c->argv[2],new);
514 decrRefCount(new);
515 addReplyLongLong(c,value);
516 signalModifiedKey(c->db,c->argv[1]);
517 server.dirty++;
518 }
519
520 void hincrbyfloatCommand(redisClient *c) {
521 double long value, incr;
522 robj *o, *current, *new, *aux;
523
524 if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
525 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
526 if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
527 if (getLongDoubleFromObjectOrReply(c,current,&value,
528 "hash value is not a valid float") != REDIS_OK) {
529 decrRefCount(current);
530 return;
531 }
532 decrRefCount(current);
533 } else {
534 value = 0;
535 }
536
537 value += incr;
538 new = createStringObjectFromLongDouble(value);
539 hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
540 hashTypeSet(o,c->argv[2],new);
541 addReplyBulk(c,new);
542 signalModifiedKey(c->db,c->argv[1]);
543 server.dirty++;
544
545 /* Always replicate HINCRBYFLOAT as an HSET command with the final value
546 * in order to make sure that differences in float pricision or formatting
547 * will not create differences in replicas or after an AOF restart. */
548 aux = createStringObject("HSET",4);
549 rewriteClientCommandArgument(c,0,aux);
550 decrRefCount(aux);
551 rewriteClientCommandArgument(c,3,new);
552 decrRefCount(new);
553 }
554
555 static void addHashFieldToReply(redisClient *c, robj *o, robj *field) {
556 int ret;
557
558 if (o == NULL) {
559 addReply(c, shared.nullbulk);
560 return;
561 }
562
563 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
564 unsigned char *vstr = NULL;
565 unsigned int vlen = UINT_MAX;
566 long long vll = LLONG_MAX;
567
568 ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
569 if (ret < 0) {
570 addReply(c, shared.nullbulk);
571 } else {
572 if (vstr) {
573 addReplyBulkCBuffer(c, vstr, vlen);
574 } else {
575 addReplyBulkLongLong(c, vll);
576 }
577 }
578
579 } else if (o->encoding == REDIS_ENCODING_HT) {
580 robj *value;
581
582 ret = hashTypeGetFromHashTable(o, field, &value);
583 if (ret < 0) {
584 addReply(c, shared.nullbulk);
585 } else {
586 addReplyBulk(c, value);
587 }
588
589 } else {
590 redisPanic("Unknown hash encoding");
591 }
592 }
593
594 void hgetCommand(redisClient *c) {
595 robj *o;
596
597 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
598 checkType(c,o,REDIS_HASH)) return;
599
600 addHashFieldToReply(c, o, c->argv[2]);
601 }
602
603 void hmgetCommand(redisClient *c) {
604 robj *o;
605 int i;
606
607 /* Don't abort when the key cannot be found. Non-existing keys are empty
608 * hashes, where HMGET should respond with a series of null bulks. */
609 o = lookupKeyRead(c->db, c->argv[1]);
610 if (o != NULL && o->type != REDIS_HASH) {
611 addReply(c, shared.wrongtypeerr);
612 return;
613 }
614
615 addReplyMultiBulkLen(c, c->argc-2);
616 for (i = 2; i < c->argc; i++) {
617 addHashFieldToReply(c, o, c->argv[i]);
618 }
619 }
620
621 void hdelCommand(redisClient *c) {
622 robj *o;
623 int j, deleted = 0;
624
625 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
626 checkType(c,o,REDIS_HASH)) return;
627
628 for (j = 2; j < c->argc; j++) {
629 if (hashTypeDelete(o,c->argv[j])) {
630 deleted++;
631 if (hashTypeLength(o) == 0) {
632 dbDelete(c->db,c->argv[1]);
633 break;
634 }
635 }
636 }
637 if (deleted) {
638 signalModifiedKey(c->db,c->argv[1]);
639 server.dirty += deleted;
640 }
641 addReplyLongLong(c,deleted);
642 }
643
644 void hlenCommand(redisClient *c) {
645 robj *o;
646 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
647 checkType(c,o,REDIS_HASH)) return;
648
649 addReplyLongLong(c,hashTypeLength(o));
650 }
651
652 static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) {
653 if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
654 unsigned char *vstr = NULL;
655 unsigned int vlen = UINT_MAX;
656 long long vll = LLONG_MAX;
657
658 hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
659 if (vstr) {
660 addReplyBulkCBuffer(c, vstr, vlen);
661 } else {
662 addReplyBulkLongLong(c, vll);
663 }
664
665 } else if (hi->encoding == REDIS_ENCODING_HT) {
666 robj *value;
667
668 hashTypeCurrentFromHashTable(hi, what, &value);
669 addReplyBulk(c, value);
670
671 } else {
672 redisPanic("Unknown hash encoding");
673 }
674 }
675
676 void genericHgetallCommand(redisClient *c, int flags) {
677 robj *o;
678 hashTypeIterator *hi;
679 int multiplier = 0;
680 int length, count = 0;
681
682 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
683 || checkType(c,o,REDIS_HASH)) return;
684
685 if (flags & REDIS_HASH_KEY) multiplier++;
686 if (flags & REDIS_HASH_VALUE) multiplier++;
687
688 length = hashTypeLength(o) * multiplier;
689 addReplyMultiBulkLen(c, length);
690
691 hi = hashTypeInitIterator(o);
692 while (hashTypeNext(hi) != REDIS_ERR) {
693 if (flags & REDIS_HASH_KEY) {
694 addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY);
695 count++;
696 }
697 if (flags & REDIS_HASH_VALUE) {
698 addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE);
699 count++;
700 }
701 }
702
703 hashTypeReleaseIterator(hi);
704 redisAssert(count == length);
705 }
706
707 void hkeysCommand(redisClient *c) {
708 genericHgetallCommand(c,REDIS_HASH_KEY);
709 }
710
711 void hvalsCommand(redisClient *c) {
712 genericHgetallCommand(c,REDIS_HASH_VALUE);
713 }
714
715 void hgetallCommand(redisClient *c) {
716 genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
717 }
718
719 void hexistsCommand(redisClient *c) {
720 robj *o;
721 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
722 checkType(c,o,REDIS_HASH)) return;
723
724 addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
725 }