xref: /redis-3.2.3/src/t_hash.c (revision 22c9c407)
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             redisAssert(ret == DICT_OK);
407         }
408 
409         hashTypeReleaseIterator(hi);
410         zfree(o->ptr);
411 
412         o->encoding = REDIS_ENCODING_HT;
413         o->ptr = dict;
414 
415     } else {
416         redisPanic("Unknown hash encoding");
417     }
418 }
419 
420 void hashTypeConvert(robj *o, int enc) {
421     if (o->encoding == REDIS_ENCODING_ZIPLIST) {
422         hashTypeConvertZiplist(o, enc);
423     } else if (o->encoding == REDIS_ENCODING_HT) {
424         redisPanic("Not implemented");
425     } else {
426         redisPanic("Unknown hash encoding");
427     }
428 }
429 
430 /*-----------------------------------------------------------------------------
431  * Hash type commands
432  *----------------------------------------------------------------------------*/
433 
434 void hsetCommand(redisClient *c) {
435     int update;
436     robj *o;
437 
438     if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
439     hashTypeTryConversion(o,c->argv,2,3);
440     hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
441     update = hashTypeSet(o,c->argv[2],c->argv[3]);
442     addReply(c, update ? shared.czero : shared.cone);
443     signalModifiedKey(c->db,c->argv[1]);
444     server.dirty++;
445 }
446 
447 void hsetnxCommand(redisClient *c) {
448     robj *o;
449     if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
450     hashTypeTryConversion(o,c->argv,2,3);
451 
452     if (hashTypeExists(o, c->argv[2])) {
453         addReply(c, shared.czero);
454     } else {
455         hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
456         hashTypeSet(o,c->argv[2],c->argv[3]);
457         addReply(c, shared.cone);
458         signalModifiedKey(c->db,c->argv[1]);
459         server.dirty++;
460     }
461 }
462 
463 void hmsetCommand(redisClient *c) {
464     int i;
465     robj *o;
466 
467     if ((c->argc % 2) == 1) {
468         addReplyError(c,"wrong number of arguments for HMSET");
469         return;
470     }
471 
472     if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
473     hashTypeTryConversion(o,c->argv,2,c->argc-1);
474     for (i = 2; i < c->argc; i += 2) {
475         hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
476         hashTypeSet(o,c->argv[i],c->argv[i+1]);
477     }
478     addReply(c, shared.ok);
479     signalModifiedKey(c->db,c->argv[1]);
480     server.dirty++;
481 }
482 
483 void hincrbyCommand(redisClient *c) {
484     long long value, incr, oldvalue;
485     robj *o, *current, *new;
486 
487     if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
488     if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
489     if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
490         if (getLongLongFromObjectOrReply(c,current,&value,
491             "hash value is not an integer") != REDIS_OK) {
492             decrRefCount(current);
493             return;
494         }
495         decrRefCount(current);
496     } else {
497         value = 0;
498     }
499 
500     oldvalue = value;
501     if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
502         (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
503         addReplyError(c,"increment or decrement would overflow");
504         return;
505     }
506     value += incr;
507     new = createStringObjectFromLongLong(value);
508     hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
509     hashTypeSet(o,c->argv[2],new);
510     decrRefCount(new);
511     addReplyLongLong(c,value);
512     signalModifiedKey(c->db,c->argv[1]);
513     server.dirty++;
514 }
515 
516 void hincrbyfloatCommand(redisClient *c) {
517     double long value, incr;
518     robj *o, *current, *new, *aux;
519 
520     if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
521     if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
522     if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
523         if (getLongDoubleFromObjectOrReply(c,current,&value,
524             "hash value is not a valid float") != REDIS_OK) {
525             decrRefCount(current);
526             return;
527         }
528         decrRefCount(current);
529     } else {
530         value = 0;
531     }
532 
533     value += incr;
534     new = createStringObjectFromLongDouble(value);
535     hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
536     hashTypeSet(o,c->argv[2],new);
537     addReplyBulk(c,new);
538     signalModifiedKey(c->db,c->argv[1]);
539     server.dirty++;
540 
541     /* Always replicate HINCRBYFLOAT as an HSET command with the final value
542      * in order to make sure that differences in float pricision or formatting
543      * will not create differences in replicas or after an AOF restart. */
544     aux = createStringObject("HSET",4);
545     rewriteClientCommandArgument(c,0,aux);
546     decrRefCount(aux);
547     rewriteClientCommandArgument(c,3,new);
548     decrRefCount(new);
549 }
550 
551 static void addHashFieldToReply(redisClient *c, robj *o, robj *field) {
552     int ret;
553 
554     if (o == NULL) {
555         addReply(c, shared.nullbulk);
556         return;
557     }
558 
559     if (o->encoding == REDIS_ENCODING_ZIPLIST) {
560         unsigned char *vstr = NULL;
561         unsigned int vlen = UINT_MAX;
562         long long vll = LLONG_MAX;
563 
564         ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
565         if (ret < 0) {
566             addReply(c, shared.nullbulk);
567         } else {
568             if (vstr) {
569                 addReplyBulkCBuffer(c, vstr, vlen);
570             } else {
571                 addReplyBulkLongLong(c, vll);
572             }
573         }
574 
575     } else if (o->encoding == REDIS_ENCODING_HT) {
576         robj *value;
577 
578         ret = hashTypeGetFromHashTable(o, field, &value);
579         if (ret < 0) {
580             addReply(c, shared.nullbulk);
581         } else {
582             addReplyBulk(c, value);
583         }
584 
585     } else {
586         redisPanic("Unknown hash encoding");
587     }
588 }
589 
590 void hgetCommand(redisClient *c) {
591     robj *o;
592 
593     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
594         checkType(c,o,REDIS_HASH)) return;
595 
596     addHashFieldToReply(c, o, c->argv[2]);
597 }
598 
599 void hmgetCommand(redisClient *c) {
600     robj *o;
601     int i;
602 
603     /* Don't abort when the key cannot be found. Non-existing keys are empty
604      * hashes, where HMGET should respond with a series of null bulks. */
605     o = lookupKeyRead(c->db, c->argv[1]);
606     if (o != NULL && o->type != REDIS_HASH) {
607         addReply(c, shared.wrongtypeerr);
608         return;
609     }
610 
611     addReplyMultiBulkLen(c, c->argc-2);
612     for (i = 2; i < c->argc; i++) {
613         addHashFieldToReply(c, o, c->argv[i]);
614     }
615 }
616 
617 void hdelCommand(redisClient *c) {
618     robj *o;
619     int j, deleted = 0;
620 
621     if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
622         checkType(c,o,REDIS_HASH)) return;
623 
624     for (j = 2; j < c->argc; j++) {
625         if (hashTypeDelete(o,c->argv[j])) {
626             deleted++;
627             if (hashTypeLength(o) == 0) {
628                 dbDelete(c->db,c->argv[1]);
629                 break;
630             }
631         }
632     }
633     if (deleted) {
634         signalModifiedKey(c->db,c->argv[1]);
635         server.dirty += deleted;
636     }
637     addReplyLongLong(c,deleted);
638 }
639 
640 void hlenCommand(redisClient *c) {
641     robj *o;
642     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
643         checkType(c,o,REDIS_HASH)) return;
644 
645     addReplyLongLong(c,hashTypeLength(o));
646 }
647 
648 static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) {
649     if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
650         unsigned char *vstr = NULL;
651         unsigned int vlen = UINT_MAX;
652         long long vll = LLONG_MAX;
653 
654         hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
655         if (vstr) {
656             addReplyBulkCBuffer(c, vstr, vlen);
657         } else {
658             addReplyBulkLongLong(c, vll);
659         }
660 
661     } else if (hi->encoding == REDIS_ENCODING_HT) {
662         robj *value;
663 
664         hashTypeCurrentFromHashTable(hi, what, &value);
665         addReplyBulk(c, value);
666 
667     } else {
668         redisPanic("Unknown hash encoding");
669     }
670 }
671 
672 void genericHgetallCommand(redisClient *c, int flags) {
673     robj *o;
674     hashTypeIterator *hi;
675     int multiplier = 0;
676     int length, count = 0;
677 
678     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
679         || checkType(c,o,REDIS_HASH)) return;
680 
681     if (flags & REDIS_HASH_KEY) multiplier++;
682     if (flags & REDIS_HASH_VALUE) multiplier++;
683 
684     length = hashTypeLength(o) * multiplier;
685     addReplyMultiBulkLen(c, length);
686 
687     hi = hashTypeInitIterator(o);
688     while (hashTypeNext(hi) != REDIS_ERR) {
689         if (flags & REDIS_HASH_KEY) {
690             addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY);
691             count++;
692         }
693         if (flags & REDIS_HASH_VALUE) {
694             addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE);
695             count++;
696         }
697     }
698 
699     hashTypeReleaseIterator(hi);
700     redisAssert(count == length);
701 }
702 
703 void hkeysCommand(redisClient *c) {
704     genericHgetallCommand(c,REDIS_HASH_KEY);
705 }
706 
707 void hvalsCommand(redisClient *c) {
708     genericHgetallCommand(c,REDIS_HASH_VALUE);
709 }
710 
711 void hgetallCommand(redisClient *c) {
712     genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
713 }
714 
715 void hexistsCommand(redisClient *c) {
716     robj *o;
717     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
718         checkType(c,o,REDIS_HASH)) return;
719 
720     addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
721 }
722