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