]>
git.saurik.com Git - redis.git/blob - src/t_hash.c
4 /*-----------------------------------------------------------------------------
6 *----------------------------------------------------------------------------*/
8 /* Check the length of a number of objects to see if we need to convert a
9 * ziplist to a real hash. Note that we only check string encoded objects
10 * as their string length can be queried in constant time. */
11 void hashTypeTryConversion(robj
*o
, robj
**argv
, int start
, int end
) {
14 if (o
->encoding
!= REDIS_ENCODING_ZIPLIST
) return;
16 for (i
= start
; i
<= end
; i
++) {
17 if (argv
[i
]->encoding
== REDIS_ENCODING_RAW
&&
18 sdslen(argv
[i
]->ptr
) > server
.hash_max_ziplist_value
)
20 hashTypeConvert(o
, REDIS_ENCODING_HT
);
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
);
34 /* Get the value from a ziplist encoded hash, identified by field.
35 * Returns -1 when the field cannot be found. */
36 int hashTypeGetFromZiplist(robj
*o
, robj
*field
,
41 unsigned char *zl
, *fptr
= NULL
, *vptr
= NULL
;
44 redisAssert(o
->encoding
== REDIS_ENCODING_ZIPLIST
);
46 field
= getDecodedObject(field
);
49 fptr
= ziplistIndex(zl
, ZIPLIST_HEAD
);
50 while (fptr
!= NULL
) {
51 /* Grab pointer to the value (fptr points to the field) */
52 vptr
= ziplistNext(zl
, fptr
);
53 redisAssert(vptr
!= NULL
);
55 /* Compare field in ziplist with specified field */
56 if (ziplistCompare(fptr
, field
->ptr
, sdslen(field
->ptr
))) {
61 fptr
= ziplistNext(zl
, vptr
);
67 ret
= ziplistGet(vptr
, vstr
, vlen
, vll
);
75 /* Get the value from a hash table encoded hash, identified by field.
76 * Returns -1 when the field cannot be found. */
77 int hashTypeGetFromHashTable(robj
*o
, robj
*field
, robj
**value
) {
80 redisAssert(o
->encoding
== REDIS_ENCODING_HT
);
82 de
= dictFind(o
->ptr
, field
);
87 *value
= dictGetVal(de
);
91 /* Higher level function of hashTypeGet*() that always returns a Redis
92 * object (either new or with refcount incremented), so that the caller
93 * can retain a reference or call decrRefCount after the usage.
95 * The lower level function can prevent copy on write so it is
96 * the preferred way of doing read operations. */
97 robj
*hashTypeGetObject(robj
*o
, robj
*field
) {
100 if (o
->encoding
== REDIS_ENCODING_ZIPLIST
) {
101 unsigned char *vstr
= NULL
;
102 unsigned int vlen
= UINT_MAX
;
103 long long vll
= LLONG_MAX
;
105 if (hashTypeGetFromZiplist(o
, field
, &vstr
, &vlen
, &vll
) == 0) {
107 value
= createStringObject((char*)vstr
, vlen
);
109 value
= createStringObjectFromLongLong(vll
);
113 } else if (o
->encoding
== REDIS_ENCODING_HT
) {
116 if (hashTypeGetFromHashTable(o
, field
, &aux
) == 0) {
122 redisPanic("Unknown hash encoding");
128 /* Test if the specified field exists in the given hash. Returns 1 if the field
129 * exists, and 0 when it doesn't. */
130 int hashTypeExists(robj
*o
, robj
*field
) {
131 if (o
->encoding
== REDIS_ENCODING_ZIPLIST
) {
132 unsigned char *vstr
= NULL
;
133 unsigned int vlen
= UINT_MAX
;
134 long long vll
= LLONG_MAX
;
136 if (hashTypeGetFromZiplist(o
, field
, &vstr
, &vlen
, &vll
) == 0) {
140 } else if (o
->encoding
== REDIS_ENCODING_HT
) {
143 if (hashTypeGetFromHashTable(o
, field
, &aux
) == 0) {
148 redisPanic("Unknown hash encoding");
154 /* Add an element, discard the old if the key already exists.
155 * Return 0 on insert and 1 on update. */
156 int hashTypeSet(robj
*o
, robj
*field
, robj
*value
) {
159 if (o
->encoding
== REDIS_ENCODING_ZIPLIST
) {
160 unsigned char *zl
, *fptr
, *vptr
;
162 field
= getDecodedObject(field
);
163 value
= getDecodedObject(value
);
166 fptr
= ziplistIndex(zl
, ZIPLIST_HEAD
);
167 while (fptr
!= NULL
) {
168 /* Compare field in ziplist with specified field */
169 if (ziplistCompare(fptr
, field
->ptr
, sdslen(field
->ptr
))) {
170 zl
= ziplistDelete(zl
,&fptr
);
171 zl
= ziplistDelete(zl
,&fptr
);
177 /* Grab pointer to the value (fptr points to the field) */
178 vptr
= ziplistNext(zl
, fptr
);
179 redisAssert(vptr
!= NULL
);
181 /* Grab pointer (if any) to the next field */
182 fptr
= ziplistNext(zl
, vptr
);
185 /* Push new field/value pair onto the tail of the ziplist */
186 zl
= ziplistPush(zl
, field
->ptr
, sdslen(field
->ptr
), ZIPLIST_TAIL
);
187 zl
= ziplistPush(zl
, value
->ptr
, sdslen(value
->ptr
), ZIPLIST_TAIL
);
193 /* Check if the ziplist needs to be converted to a hash table */
194 if (hashTypeLength(o
) > server
.hash_max_ziplist_entries
) {
195 hashTypeConvert(o
, REDIS_ENCODING_HT
);
198 } else if (o
->encoding
== REDIS_ENCODING_HT
) {
199 if (dictReplace(o
->ptr
, field
, value
)) { /* Insert */
201 } else { /* Update */
208 redisPanic("Unknown hash encoding");
214 /* Delete an element from a hash.
215 * Return 1 on deleted and 0 on not found. */
216 int hashTypeDelete(robj
*o
, robj
*field
) {
219 if (o
->encoding
== REDIS_ENCODING_ZIPLIST
) {
220 unsigned char *zl
, *fptr
, *vptr
;
222 field
= getDecodedObject(field
);
225 fptr
= ziplistIndex(zl
, ZIPLIST_HEAD
);
226 while (fptr
!= NULL
) {
227 /* Compare field in ziplist with specified field */
228 if (ziplistCompare(fptr
, field
->ptr
, sdslen(field
->ptr
))) {
229 zl
= ziplistDelete(zl
,&fptr
);
230 zl
= ziplistDelete(zl
,&fptr
);
236 /* Grab pointer to the value (fptr points to the field) */
237 vptr
= ziplistNext(zl
, fptr
);
238 redisAssert(vptr
!= NULL
);
240 /* Grab pointer (if any) to the next field */
241 fptr
= ziplistNext(zl
, vptr
);
246 } else if (o
->encoding
== REDIS_ENCODING_HT
) {
247 if (dictDelete((dict
*)o
->ptr
, field
) == REDIS_OK
) {
250 /* Always check if the dictionary needs a resize after a delete. */
251 if (htNeedsResize(o
->ptr
)) dictResize(o
->ptr
);
255 redisPanic("Unknown hash encoding");
261 /* Return the number of elements in a hash. */
262 unsigned long hashTypeLength(robj
*o
) {
263 unsigned long length
= ULONG_MAX
;
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
);
270 redisPanic("Unknown hash encoding");
276 hashTypeIterator
*hashTypeInitIterator(robj
*subject
) {
277 hashTypeIterator
*hi
= zmalloc(sizeof(hashTypeIterator
));
278 hi
->subject
= subject
;
279 hi
->encoding
= subject
->encoding
;
281 if (hi
->encoding
== REDIS_ENCODING_ZIPLIST
) {
284 } else if (hi
->encoding
== REDIS_ENCODING_HT
) {
285 hi
->di
= dictGetIterator(subject
->ptr
);
287 redisPanic("Unknown hash encoding");
293 void hashTypeReleaseIterator(hashTypeIterator
*hi
) {
294 if (hi
->encoding
== REDIS_ENCODING_HT
) {
295 dictReleaseIterator(hi
->di
);
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
) {
306 unsigned char *fptr
, *vptr
;
308 zl
= hi
->subject
->ptr
;
313 /* Initialize cursor */
314 redisAssert(vptr
== NULL
);
315 fptr
= ziplistIndex(zl
, 0);
318 redisAssert(vptr
!= NULL
);
319 fptr
= ziplistNext(zl
, vptr
);
326 /* Grab pointer to the value (fptr points to the field) */
327 vptr
= ziplistNext(zl
, fptr
);
328 redisAssert(vptr
!= NULL
);
330 /* fptr, vptr now point to the first or next pair */
334 } else if (hi
->encoding
== REDIS_ENCODING_HT
) {
335 if ((hi
->de
= dictNext(hi
->di
)) == NULL
) {
340 redisPanic("Unknown hash encoding");
346 /* Get the field or value at iterator cursor, for an iterator on a hash value
347 * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */
348 void hashTypeCurrentFromZiplist(hashTypeIterator
*hi
, int what
,
349 unsigned char **vstr
,
355 redisAssert(hi
->encoding
== REDIS_ENCODING_ZIPLIST
);
357 if (what
& REDIS_HASH_KEY
) {
358 ret
= ziplistGet(hi
->fptr
, vstr
, vlen
, vll
);
361 ret
= ziplistGet(hi
->vptr
, vstr
, vlen
, vll
);
366 /* Get the field or value at iterator cursor, for an iterator on a hash value
367 * encoded as a ziplist. Prototype is similar to `hashTypeGetFromHashTable`. */
368 void hashTypeCurrentFromHashTable(hashTypeIterator
*hi
, int what
, robj
**dst
) {
369 redisAssert(hi
->encoding
== REDIS_ENCODING_HT
);
371 if (what
& REDIS_HASH_KEY
) {
372 *dst
= dictGetKey(hi
->de
);
374 *dst
= dictGetVal(hi
->de
);
378 /* A non copy-on-write friendly but higher level version of hashTypeCurrent*()
379 * that returns an object with incremented refcount (or a new object). It is up
380 * to the caller to decrRefCount() the object if no reference is retained. */
381 robj
*hashTypeCurrentObject(hashTypeIterator
*hi
, int what
) {
384 if (hi
->encoding
== REDIS_ENCODING_ZIPLIST
) {
385 unsigned char *vstr
= NULL
;
386 unsigned int vlen
= UINT_MAX
;
387 long long vll
= LLONG_MAX
;
389 hashTypeCurrentFromZiplist(hi
, what
, &vstr
, &vlen
, &vll
);
391 dst
= createStringObject((char*)vstr
, vlen
);
393 dst
= createStringObjectFromLongLong(vll
);
396 } else if (hi
->encoding
== REDIS_ENCODING_HT
) {
397 hashTypeCurrentFromHashTable(hi
, what
, &dst
);
401 redisPanic("Unknown hash encoding");
407 robj
*hashTypeLookupWriteOrCreate(redisClient
*c
, robj
*key
) {
408 robj
*o
= lookupKeyWrite(c
->db
,key
);
410 o
= createHashObject();
413 if (o
->type
!= REDIS_HASH
) {
414 addReply(c
,shared
.wrongtypeerr
);
421 void hashTypeConvertZiplist(robj
*o
, int enc
) {
422 redisAssert(o
->encoding
== REDIS_ENCODING_ZIPLIST
);
424 if (enc
== REDIS_ENCODING_ZIPLIST
) {
425 /* Nothing to do... */
427 } else if (enc
== REDIS_ENCODING_HT
) {
428 hashTypeIterator
*hi
;
432 hi
= hashTypeInitIterator(o
);
433 dict
= dictCreate(&hashDictType
, NULL
);
435 while (hashTypeNext(hi
) != REDIS_ERR
) {
438 field
= hashTypeCurrentObject(hi
, REDIS_HASH_KEY
);
439 field
= tryObjectEncoding(field
);
440 value
= hashTypeCurrentObject(hi
, REDIS_HASH_VALUE
);
441 value
= tryObjectEncoding(value
);
442 ret
= dictAdd(dict
, field
, value
);
443 redisAssert(ret
== DICT_OK
);
446 hashTypeReleaseIterator(hi
);
449 o
->encoding
= REDIS_ENCODING_HT
;
453 redisPanic("Unknown hash encoding");
457 void hashTypeConvert(robj
*o
, int enc
) {
458 if (o
->encoding
== REDIS_ENCODING_ZIPLIST
) {
459 hashTypeConvertZiplist(o
, enc
);
460 } else if (o
->encoding
== REDIS_ENCODING_HT
) {
461 redisPanic("Not implemented");
463 redisPanic("Unknown hash encoding");
467 /*-----------------------------------------------------------------------------
469 *----------------------------------------------------------------------------*/
471 void hsetCommand(redisClient
*c
) {
475 if ((o
= hashTypeLookupWriteOrCreate(c
,c
->argv
[1])) == NULL
) return;
476 hashTypeTryConversion(o
,c
->argv
,2,3);
477 hashTypeTryObjectEncoding(o
,&c
->argv
[2], &c
->argv
[3]);
478 update
= hashTypeSet(o
,c
->argv
[2],c
->argv
[3]);
479 addReply(c
, update
? shared
.czero
: shared
.cone
);
480 signalModifiedKey(c
->db
,c
->argv
[1]);
484 void hsetnxCommand(redisClient
*c
) {
486 if ((o
= hashTypeLookupWriteOrCreate(c
,c
->argv
[1])) == NULL
) return;
487 hashTypeTryConversion(o
,c
->argv
,2,3);
489 if (hashTypeExists(o
, c
->argv
[2])) {
490 addReply(c
, shared
.czero
);
492 hashTypeTryObjectEncoding(o
,&c
->argv
[2], &c
->argv
[3]);
493 hashTypeSet(o
,c
->argv
[2],c
->argv
[3]);
494 addReply(c
, shared
.cone
);
495 signalModifiedKey(c
->db
,c
->argv
[1]);
500 void hmsetCommand(redisClient
*c
) {
504 if ((c
->argc
% 2) == 1) {
505 addReplyError(c
,"wrong number of arguments for HMSET");
509 if ((o
= hashTypeLookupWriteOrCreate(c
,c
->argv
[1])) == NULL
) return;
510 hashTypeTryConversion(o
,c
->argv
,2,c
->argc
-1);
511 for (i
= 2; i
< c
->argc
; i
+= 2) {
512 hashTypeTryObjectEncoding(o
,&c
->argv
[i
], &c
->argv
[i
+1]);
513 hashTypeSet(o
,c
->argv
[i
],c
->argv
[i
+1]);
515 addReply(c
, shared
.ok
);
516 signalModifiedKey(c
->db
,c
->argv
[1]);
520 void hincrbyCommand(redisClient
*c
) {
521 long long value
, incr
;
522 robj
*o
, *current
, *new;
524 if (getLongLongFromObjectOrReply(c
,c
->argv
[3],&incr
,NULL
) != REDIS_OK
) return;
525 if ((o
= hashTypeLookupWriteOrCreate(c
,c
->argv
[1])) == NULL
) return;
526 if ((current
= hashTypeGetObject(o
,c
->argv
[2])) != NULL
) {
527 if (getLongLongFromObjectOrReply(c
,current
,&value
,
528 "hash value is not an integer") != REDIS_OK
) {
529 decrRefCount(current
);
532 decrRefCount(current
);
538 new = createStringObjectFromLongLong(value
);
539 hashTypeTryObjectEncoding(o
,&c
->argv
[2],NULL
);
540 hashTypeSet(o
,c
->argv
[2],new);
542 addReplyLongLong(c
,value
);
543 signalModifiedKey(c
->db
,c
->argv
[1]);
547 void hincrbyfloatCommand(redisClient
*c
) {
548 double long value
, incr
;
549 robj
*o
, *current
, *new;
551 if (getLongDoubleFromObjectOrReply(c
,c
->argv
[3],&incr
,NULL
) != REDIS_OK
) return;
552 if ((o
= hashTypeLookupWriteOrCreate(c
,c
->argv
[1])) == NULL
) return;
553 if ((current
= hashTypeGetObject(o
,c
->argv
[2])) != NULL
) {
554 if (getLongDoubleFromObjectOrReply(c
,current
,&value
,
555 "hash value is not a valid float") != REDIS_OK
) {
556 decrRefCount(current
);
559 decrRefCount(current
);
565 new = createStringObjectFromLongDouble(value
);
566 hashTypeTryObjectEncoding(o
,&c
->argv
[2],NULL
);
567 hashTypeSet(o
,c
->argv
[2],new);
570 signalModifiedKey(c
->db
,c
->argv
[1]);
574 static void addHashFieldToReply(redisClient
*c
, robj
*o
, robj
*field
) {
578 addReply(c
, shared
.nullbulk
);
582 if (o
->encoding
== REDIS_ENCODING_ZIPLIST
) {
583 unsigned char *vstr
= NULL
;
584 unsigned int vlen
= UINT_MAX
;
585 long long vll
= LLONG_MAX
;
587 ret
= hashTypeGetFromZiplist(o
, field
, &vstr
, &vlen
, &vll
);
589 addReply(c
, shared
.nullbulk
);
592 addReplyBulkCBuffer(c
, vstr
, vlen
);
594 addReplyBulkLongLong(c
, vll
);
598 } else if (o
->encoding
== REDIS_ENCODING_HT
) {
601 ret
= hashTypeGetFromHashTable(o
, field
, &value
);
603 addReply(c
, shared
.nullbulk
);
605 addReplyBulk(c
, value
);
609 redisPanic("Unknown hash encoding");
613 void hgetCommand(redisClient
*c
) {
616 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.nullbulk
)) == NULL
||
617 checkType(c
,o
,REDIS_HASH
)) return;
619 addHashFieldToReply(c
, o
, c
->argv
[2]);
622 void hmgetCommand(redisClient
*c
) {
626 /* Don't abort when the key cannot be found. Non-existing keys are empty
627 * hashes, where HMGET should respond with a series of null bulks. */
628 o
= lookupKeyRead(c
->db
, c
->argv
[1]);
629 if (o
!= NULL
&& o
->type
!= REDIS_HASH
) {
630 addReply(c
, shared
.wrongtypeerr
);
634 addReplyMultiBulkLen(c
, c
->argc
-2);
635 for (i
= 2; i
< c
->argc
; i
++) {
636 addHashFieldToReply(c
, o
, c
->argv
[i
]);
640 void hdelCommand(redisClient
*c
) {
644 if ((o
= lookupKeyWriteOrReply(c
,c
->argv
[1],shared
.czero
)) == NULL
||
645 checkType(c
,o
,REDIS_HASH
)) return;
647 for (j
= 2; j
< c
->argc
; j
++) {
648 if (hashTypeDelete(o
,c
->argv
[j
])) {
650 if (hashTypeLength(o
) == 0) {
651 dbDelete(c
->db
,c
->argv
[1]);
657 signalModifiedKey(c
->db
,c
->argv
[1]);
658 server
.dirty
+= deleted
;
660 addReplyLongLong(c
,deleted
);
663 void hlenCommand(redisClient
*c
) {
665 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.czero
)) == NULL
||
666 checkType(c
,o
,REDIS_HASH
)) return;
668 addReplyLongLong(c
,hashTypeLength(o
));
671 static void addHashIteratorCursorToReply(redisClient
*c
, hashTypeIterator
*hi
, int what
) {
672 if (hi
->encoding
== REDIS_ENCODING_ZIPLIST
) {
673 unsigned char *vstr
= NULL
;
674 unsigned int vlen
= UINT_MAX
;
675 long long vll
= LLONG_MAX
;
677 hashTypeCurrentFromZiplist(hi
, what
, &vstr
, &vlen
, &vll
);
679 addReplyBulkCBuffer(c
, vstr
, vlen
);
681 addReplyBulkLongLong(c
, vll
);
684 } else if (hi
->encoding
== REDIS_ENCODING_HT
) {
687 hashTypeCurrentFromHashTable(hi
, what
, &value
);
688 addReplyBulk(c
, value
);
691 redisPanic("Unknown hash encoding");
695 void genericHgetallCommand(redisClient
*c
, int flags
) {
697 hashTypeIterator
*hi
;
699 int length
, count
= 0;
701 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.emptymultibulk
)) == NULL
702 || checkType(c
,o
,REDIS_HASH
)) return;
704 if (flags
& REDIS_HASH_KEY
) multiplier
++;
705 if (flags
& REDIS_HASH_VALUE
) multiplier
++;
707 length
= hashTypeLength(o
) * multiplier
;
708 addReplyMultiBulkLen(c
, length
);
710 hi
= hashTypeInitIterator(o
);
711 while (hashTypeNext(hi
) != REDIS_ERR
) {
712 if (flags
& REDIS_HASH_KEY
) {
713 addHashIteratorCursorToReply(c
, hi
, REDIS_HASH_KEY
);
716 if (flags
& REDIS_HASH_VALUE
) {
717 addHashIteratorCursorToReply(c
, hi
, REDIS_HASH_VALUE
);
722 hashTypeReleaseIterator(hi
);
723 redisAssert(count
== length
);
726 void hkeysCommand(redisClient
*c
) {
727 genericHgetallCommand(c
,REDIS_HASH_KEY
);
730 void hvalsCommand(redisClient
*c
) {
731 genericHgetallCommand(c
,REDIS_HASH_VALUE
);
734 void hgetallCommand(redisClient
*c
) {
735 genericHgetallCommand(c
,REDIS_HASH_KEY
|REDIS_HASH_VALUE
);
738 void hexistsCommand(redisClient
*c
) {
740 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.czero
)) == NULL
||
741 checkType(c
,o
,REDIS_HASH
)) return;
743 addReply(c
, hashTypeExists(o
,c
->argv
[2]) ? shared
.cone
: shared
.czero
);