xref: /redis-3.2.3/src/t_hash.c (revision 414c3dea)
1 #include "redis.h"
2 
3 #include <math.h>
4 
5 /*-----------------------------------------------------------------------------
6  * Hash type API
7  *----------------------------------------------------------------------------*/
8 
9 /* Check the length of a number of objects to see if we need to convert a
10  * zipmap to a real hash. Note that we only check string encoded objects
11  * as their string length can be queried in constant time. */
12 void hashTypeTryConversion(robj *subject, robj **argv, int start, int end) {
13     int i;
14     if (subject->encoding != REDIS_ENCODING_ZIPMAP) 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_zipmap_value)
19         {
20             convertToRealHash(subject);
21             return;
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 hash identified by key.
35  *
36  * If the string is found either REDIS_ENCODING_HT or REDIS_ENCODING_ZIPMAP
37  * is returned, and either **objval or **v and *vlen are set accordingly,
38  * so that objects in hash tables are returend as objects and pointers
39  * inside a zipmap are returned as such.
40  *
41  * If the object was not found -1 is returned.
42  *
43  * This function is copy on write friendly as there is no incr/decr
44  * of refcount needed if objects are accessed just for reading operations. */
45 int hashTypeGet(robj *o, robj *key, robj **objval, unsigned char **v,
46                 unsigned int *vlen)
47 {
48     if (o->encoding == REDIS_ENCODING_ZIPMAP) {
49         int found;
50 
51         key = getDecodedObject(key);
52         found = zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),v,vlen);
53         decrRefCount(key);
54         if (!found) return -1;
55     } else {
56         dictEntry *de = dictFind(o->ptr,key);
57         if (de == NULL) return -1;
58         *objval = dictGetVal(de);
59     }
60     return o->encoding;
61 }
62 
63 /* Higher level function of hashTypeGet() that always returns a Redis
64  * object (either new or with refcount incremented), so that the caller
65  * can retain a reference or call decrRefCount after the usage.
66  *
67  * The lower level function can prevent copy on write so it is
68  * the preferred way of doing read operations. */
69 robj *hashTypeGetObject(robj *o, robj *key) {
70     robj *objval;
71     unsigned char *v;
72     unsigned int vlen;
73 
74     int encoding = hashTypeGet(o,key,&objval,&v,&vlen);
75     switch(encoding) {
76         case REDIS_ENCODING_HT:
77             incrRefCount(objval);
78             return objval;
79         case REDIS_ENCODING_ZIPMAP:
80             objval = createStringObject((char*)v,vlen);
81             return objval;
82         default: return NULL;
83     }
84 }
85 
86 /* Test if the key exists in the given hash. Returns 1 if the key
87  * exists and 0 when it doesn't. */
88 int hashTypeExists(robj *o, robj *key) {
89     if (o->encoding == REDIS_ENCODING_ZIPMAP) {
90         key = getDecodedObject(key);
91         if (zipmapExists(o->ptr,key->ptr,sdslen(key->ptr))) {
92             decrRefCount(key);
93             return 1;
94         }
95         decrRefCount(key);
96     } else {
97         if (dictFind(o->ptr,key) != NULL) {
98             return 1;
99         }
100     }
101     return 0;
102 }
103 
104 /* Add an element, discard the old if the key already exists.
105  * Return 0 on insert and 1 on update. */
106 int hashTypeSet(robj *o, robj *key, robj *value) {
107     int update = 0;
108     if (o->encoding == REDIS_ENCODING_ZIPMAP) {
109         key = getDecodedObject(key);
110         value = getDecodedObject(value);
111         o->ptr = zipmapSet(o->ptr,
112             key->ptr,sdslen(key->ptr),
113             value->ptr,sdslen(value->ptr), &update);
114         decrRefCount(key);
115         decrRefCount(value);
116 
117         /* Check if the zipmap needs to be upgraded to a real hash table */
118         if (zipmapLen(o->ptr) > server.hash_max_zipmap_entries)
119             convertToRealHash(o);
120     } else {
121         if (dictReplace(o->ptr,key,value)) {
122             /* Insert */
123             incrRefCount(key);
124         } else {
125             /* Update */
126             update = 1;
127         }
128         incrRefCount(value);
129     }
130     return update;
131 }
132 
133 /* Delete an element from a hash.
134  * Return 1 on deleted and 0 on not found. */
135 int hashTypeDelete(robj *o, robj *key) {
136     int deleted = 0;
137     if (o->encoding == REDIS_ENCODING_ZIPMAP) {
138         key = getDecodedObject(key);
139         o->ptr = zipmapDel(o->ptr,key->ptr,sdslen(key->ptr), &deleted);
140         decrRefCount(key);
141     } else {
142         deleted = dictDelete((dict*)o->ptr,key) == DICT_OK;
143         /* Always check if the dictionary needs a resize after a delete. */
144         if (deleted && htNeedsResize(o->ptr)) dictResize(o->ptr);
145     }
146     return deleted;
147 }
148 
149 /* Return the number of elements in a hash. */
150 unsigned long hashTypeLength(robj *o) {
151     return (o->encoding == REDIS_ENCODING_ZIPMAP) ?
152         zipmapLen((unsigned char*)o->ptr) : dictSize((dict*)o->ptr);
153 }
154 
155 hashTypeIterator *hashTypeInitIterator(robj *subject) {
156     hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator));
157     hi->encoding = subject->encoding;
158     if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
159         hi->zi = zipmapRewind(subject->ptr);
160     } else if (hi->encoding == REDIS_ENCODING_HT) {
161         hi->di = dictGetIterator(subject->ptr);
162     } else {
163         redisAssertWithInfo(NULL,subject,0);
164     }
165     return hi;
166 }
167 
168 void hashTypeReleaseIterator(hashTypeIterator *hi) {
169     if (hi->encoding == REDIS_ENCODING_HT) {
170         dictReleaseIterator(hi->di);
171     }
172     zfree(hi);
173 }
174 
175 /* Move to the next entry in the hash. Return REDIS_OK when the next entry
176  * could be found and REDIS_ERR when the iterator reaches the end. */
177 int hashTypeNext(hashTypeIterator *hi) {
178     if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
179         if ((hi->zi = zipmapNext(hi->zi, &hi->zk, &hi->zklen,
180             &hi->zv, &hi->zvlen)) == NULL) return REDIS_ERR;
181     } else {
182         if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR;
183     }
184     return REDIS_OK;
185 }
186 
187 /* Get key or value object at current iteration position.
188  * The returned item differs with the hash object encoding:
189  * - When encoding is REDIS_ENCODING_HT, the objval pointer is populated
190  *   with the original object.
191  * - When encoding is REDIS_ENCODING_ZIPMAP, a pointer to the string and
192  *   its length is retunred populating the v and vlen pointers.
193  * This function is copy on write friendly as accessing objects in read only
194  * does not require writing to any memory page.
195  *
196  * The function returns the encoding of the object, so that the caller
197  * can underestand if the key or value was returned as object or C string. */
198 int hashTypeCurrent(hashTypeIterator *hi, int what, robj **objval, unsigned char **v, unsigned int *vlen) {
199     if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
200         if (what & REDIS_HASH_KEY) {
201             *v = hi->zk;
202             *vlen = hi->zklen;
203         } else {
204             *v = hi->zv;
205             *vlen = hi->zvlen;
206         }
207     } else {
208         if (what & REDIS_HASH_KEY)
209             *objval = dictGetKey(hi->de);
210         else
211             *objval = dictGetVal(hi->de);
212     }
213     return hi->encoding;
214 }
215 
216 /* A non copy-on-write friendly but higher level version of hashTypeCurrent()
217  * that always returns an object with refcount incremented by one (or a new
218  * object), so it's up to the caller to decrRefCount() the object if no
219  * reference is retained. */
220 robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) {
221     robj *obj;
222     unsigned char *v = NULL;
223     unsigned int vlen = 0;
224     int encoding = hashTypeCurrent(hi,what,&obj,&v,&vlen);
225 
226     if (encoding == REDIS_ENCODING_HT) {
227         incrRefCount(obj);
228         return obj;
229     } else {
230         return createStringObject((char*)v,vlen);
231     }
232 }
233 
234 robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) {
235     robj *o = lookupKeyWrite(c->db,key);
236     if (o == NULL) {
237         o = createHashObject();
238         dbAdd(c->db,key,o);
239     } else {
240         if (o->type != REDIS_HASH) {
241             addReply(c,shared.wrongtypeerr);
242             return NULL;
243         }
244     }
245     return o;
246 }
247 
248 void convertToRealHash(robj *o) {
249     unsigned char *key, *val, *p, *zm = o->ptr;
250     unsigned int klen, vlen;
251     dict *dict = dictCreate(&hashDictType,NULL);
252 
253     redisAssertWithInfo(NULL,o,o->type == REDIS_HASH && o->encoding != REDIS_ENCODING_HT);
254     p = zipmapRewind(zm);
255     while((p = zipmapNext(p,&key,&klen,&val,&vlen)) != NULL) {
256         robj *keyobj, *valobj;
257 
258         keyobj = createStringObject((char*)key,klen);
259         valobj = createStringObject((char*)val,vlen);
260         keyobj = tryObjectEncoding(keyobj);
261         valobj = tryObjectEncoding(valobj);
262         dictAdd(dict,keyobj,valobj);
263     }
264     o->encoding = REDIS_ENCODING_HT;
265     o->ptr = dict;
266     zfree(zm);
267 }
268 
269 /*-----------------------------------------------------------------------------
270  * Hash type commands
271  *----------------------------------------------------------------------------*/
272 
273 void hsetCommand(redisClient *c) {
274     int update;
275     robj *o;
276 
277     if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
278     hashTypeTryConversion(o,c->argv,2,3);
279     hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
280     update = hashTypeSet(o,c->argv[2],c->argv[3]);
281     addReply(c, update ? shared.czero : shared.cone);
282     signalModifiedKey(c->db,c->argv[1]);
283     server.dirty++;
284 }
285 
286 void hsetnxCommand(redisClient *c) {
287     robj *o;
288     if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
289     hashTypeTryConversion(o,c->argv,2,3);
290 
291     if (hashTypeExists(o, c->argv[2])) {
292         addReply(c, shared.czero);
293     } else {
294         hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
295         hashTypeSet(o,c->argv[2],c->argv[3]);
296         addReply(c, shared.cone);
297         signalModifiedKey(c->db,c->argv[1]);
298         server.dirty++;
299     }
300 }
301 
302 void hmsetCommand(redisClient *c) {
303     int i;
304     robj *o;
305 
306     if ((c->argc % 2) == 1) {
307         addReplyError(c,"wrong number of arguments for HMSET");
308         return;
309     }
310 
311     if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
312     hashTypeTryConversion(o,c->argv,2,c->argc-1);
313     for (i = 2; i < c->argc; i += 2) {
314         hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
315         hashTypeSet(o,c->argv[i],c->argv[i+1]);
316     }
317     addReply(c, shared.ok);
318     signalModifiedKey(c->db,c->argv[1]);
319     server.dirty++;
320 }
321 
322 void hincrbyCommand(redisClient *c) {
323     long long value, incr;
324     robj *o, *current, *new;
325 
326     if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
327     if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
328     if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
329         if (getLongLongFromObjectOrReply(c,current,&value,
330             "hash value is not an integer") != REDIS_OK) {
331             decrRefCount(current);
332             return;
333         }
334         decrRefCount(current);
335     } else {
336         value = 0;
337     }
338 
339     value += incr;
340     new = createStringObjectFromLongLong(value);
341     hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
342     hashTypeSet(o,c->argv[2],new);
343     decrRefCount(new);
344     addReplyLongLong(c,value);
345     signalModifiedKey(c->db,c->argv[1]);
346     server.dirty++;
347 }
348 
349 void hincrbyfloatCommand(redisClient *c) {
350     double long value, incr;
351     robj *o, *current, *new;
352 
353     if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
354     if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
355     if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
356         if (getLongDoubleFromObjectOrReply(c,current,&value,
357             "hash value is not a valid float") != REDIS_OK) {
358             decrRefCount(current);
359             return;
360         }
361         decrRefCount(current);
362     } else {
363         value = 0;
364     }
365 
366     value += incr;
367     new = createStringObjectFromLongDouble(value);
368     hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
369     hashTypeSet(o,c->argv[2],new);
370     addReplyBulk(c,new);
371     decrRefCount(new);
372     signalModifiedKey(c->db,c->argv[1]);
373     server.dirty++;
374 }
375 
376 void hgetCommand(redisClient *c) {
377     robj *o, *value;
378     unsigned char *v;
379     unsigned int vlen;
380     int encoding;
381 
382     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
383         checkType(c,o,REDIS_HASH)) return;
384 
385     if ((encoding = hashTypeGet(o,c->argv[2],&value,&v,&vlen)) != -1) {
386         if (encoding == REDIS_ENCODING_HT)
387             addReplyBulk(c,value);
388         else
389             addReplyBulkCBuffer(c,v,vlen);
390     } else {
391         addReply(c,shared.nullbulk);
392     }
393 }
394 
395 void hmgetCommand(redisClient *c) {
396     int i, encoding;
397     robj *o, *value;
398     unsigned char *v;
399     unsigned int vlen;
400 
401     o = lookupKeyRead(c->db,c->argv[1]);
402     if (o != NULL && o->type != REDIS_HASH) {
403         addReply(c,shared.wrongtypeerr);
404         return;
405     }
406 
407     /* Note the check for o != NULL happens inside the loop. This is
408      * done because objects that cannot be found are considered to be
409      * an empty hash. The reply should then be a series of NULLs. */
410     addReplyMultiBulkLen(c,c->argc-2);
411     for (i = 2; i < c->argc; i++) {
412         if (o != NULL &&
413             (encoding = hashTypeGet(o,c->argv[i],&value,&v,&vlen)) != -1) {
414             if (encoding == REDIS_ENCODING_HT)
415                 addReplyBulk(c,value);
416             else
417                 addReplyBulkCBuffer(c,v,vlen);
418         } else {
419             addReply(c,shared.nullbulk);
420         }
421     }
422 }
423 
424 void hdelCommand(redisClient *c) {
425     robj *o;
426     int j, deleted = 0;
427 
428     if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
429         checkType(c,o,REDIS_HASH)) return;
430 
431     for (j = 2; j < c->argc; j++) {
432         if (hashTypeDelete(o,c->argv[j])) {
433             deleted++;
434             if (hashTypeLength(o) == 0) {
435                 dbDelete(c->db,c->argv[1]);
436                 break;
437             }
438         }
439     }
440     if (deleted) {
441         signalModifiedKey(c->db,c->argv[1]);
442         server.dirty += deleted;
443     }
444     addReplyLongLong(c,deleted);
445 }
446 
447 void hlenCommand(redisClient *c) {
448     robj *o;
449     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
450         checkType(c,o,REDIS_HASH)) return;
451 
452     addReplyLongLong(c,hashTypeLength(o));
453 }
454 
455 void genericHgetallCommand(redisClient *c, int flags) {
456     robj *o;
457     unsigned long count = 0;
458     hashTypeIterator *hi;
459     void *replylen = NULL;
460 
461     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
462         || checkType(c,o,REDIS_HASH)) return;
463 
464     replylen = addDeferredMultiBulkLength(c);
465     hi = hashTypeInitIterator(o);
466     while (hashTypeNext(hi) != REDIS_ERR) {
467         robj *obj;
468         unsigned char *v = NULL;
469         unsigned int vlen = 0;
470         int encoding;
471 
472         if (flags & REDIS_HASH_KEY) {
473             encoding = hashTypeCurrent(hi,REDIS_HASH_KEY,&obj,&v,&vlen);
474             if (encoding == REDIS_ENCODING_HT)
475                 addReplyBulk(c,obj);
476             else
477                 addReplyBulkCBuffer(c,v,vlen);
478             count++;
479         }
480         if (flags & REDIS_HASH_VALUE) {
481             encoding = hashTypeCurrent(hi,REDIS_HASH_VALUE,&obj,&v,&vlen);
482             if (encoding == REDIS_ENCODING_HT)
483                 addReplyBulk(c,obj);
484             else
485                 addReplyBulkCBuffer(c,v,vlen);
486             count++;
487         }
488     }
489     hashTypeReleaseIterator(hi);
490     setDeferredMultiBulkLength(c,replylen,count);
491 }
492 
493 void hkeysCommand(redisClient *c) {
494     genericHgetallCommand(c,REDIS_HASH_KEY);
495 }
496 
497 void hvalsCommand(redisClient *c) {
498     genericHgetallCommand(c,REDIS_HASH_VALUE);
499 }
500 
501 void hgetallCommand(redisClient *c) {
502     genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
503 }
504 
505 void hexistsCommand(redisClient *c) {
506     robj *o;
507     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
508         checkType(c,o,REDIS_HASH)) return;
509 
510     addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
511 }
512