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