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