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