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