]>
git.saurik.com Git - redis.git/blob - src/t_hash.c
5 /*-----------------------------------------------------------------------------
7 *----------------------------------------------------------------------------*/
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
) {
14 if (subject
->encoding
!= REDIS_ENCODING_ZIPMAP
) return;
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
)
20 convertToRealHash(subject
);
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 hash identified by key.
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.
41 * If the object was not found -1 is returned.
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
,
48 if (o
->encoding
== REDIS_ENCODING_ZIPMAP
) {
51 key
= getDecodedObject(key
);
52 found
= zipmapGet(o
->ptr
,key
->ptr
,sdslen(key
->ptr
),v
,vlen
);
54 if (!found
) return -1;
56 dictEntry
*de
= dictFind(o
->ptr
,key
);
57 if (de
== NULL
) return -1;
58 *objval
= dictGetEntryVal(de
);
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.
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
) {
74 int encoding
= hashTypeGet(o
,key
,&objval
,&v
,&vlen
);
76 case REDIS_ENCODING_HT
:
79 case REDIS_ENCODING_ZIPMAP
:
80 objval
= createStringObject((char*)v
,vlen
);
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
))) {
97 if (dictFind(o
->ptr
,key
) != NULL
) {
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
) {
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
);
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
);
121 if (dictReplace(o
->ptr
,key
,value
)) {
133 /* Delete an element from a hash.
134 * Return 1 on deleted and 0 on not found. */
135 int hashTypeDelete(robj
*o
, robj
*key
) {
137 if (o
->encoding
== REDIS_ENCODING_ZIPMAP
) {
138 key
= getDecodedObject(key
);
139 o
->ptr
= zipmapDel(o
->ptr
,key
->ptr
,sdslen(key
->ptr
), &deleted
);
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
);
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
);
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
);
168 void hashTypeReleaseIterator(hashTypeIterator
*hi
) {
169 if (hi
->encoding
== REDIS_ENCODING_HT
) {
170 dictReleaseIterator(hi
->di
);
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
;
182 if ((hi
->de
= dictNext(hi
->di
)) == NULL
) return REDIS_ERR
;
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.
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
) {
208 if (what
& REDIS_HASH_KEY
)
209 *objval
= dictGetEntryKey(hi
->de
);
211 *objval
= dictGetEntryVal(hi
->de
);
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
) {
224 int encoding
= hashTypeCurrent(hi
,what
,&obj
,&v
,&vlen
);
226 if (encoding
== REDIS_ENCODING_HT
) {
230 return createStringObject((char*)v
,vlen
);
234 robj
*hashTypeLookupWriteOrCreate(redisClient
*c
, robj
*key
) {
235 robj
*o
= lookupKeyWrite(c
->db
,key
);
237 o
= createHashObject();
240 if (o
->type
!= REDIS_HASH
) {
241 addReply(c
,shared
.wrongtypeerr
);
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
);
253 redisAssert(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
;
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
);
264 o
->encoding
= REDIS_ENCODING_HT
;
269 /*-----------------------------------------------------------------------------
271 *----------------------------------------------------------------------------*/
273 void hsetCommand(redisClient
*c
) {
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 touchWatchedKey(c
->db
,c
->argv
[1]);
286 void hsetnxCommand(redisClient
*c
) {
288 if ((o
= hashTypeLookupWriteOrCreate(c
,c
->argv
[1])) == NULL
) return;
289 hashTypeTryConversion(o
,c
->argv
,2,3);
291 if (hashTypeExists(o
, c
->argv
[2])) {
292 addReply(c
, shared
.czero
);
294 hashTypeTryObjectEncoding(o
,&c
->argv
[2], &c
->argv
[3]);
295 hashTypeSet(o
,c
->argv
[2],c
->argv
[3]);
296 addReply(c
, shared
.cone
);
297 touchWatchedKey(c
->db
,c
->argv
[1]);
302 void hmsetCommand(redisClient
*c
) {
306 if ((c
->argc
% 2) == 1) {
307 addReplyError(c
,"wrong number of arguments for HMSET");
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]);
317 addReply(c
, shared
.ok
);
318 touchWatchedKey(c
->db
,c
->argv
[1]);
322 void hincrbyCommand(redisClient
*c
) {
323 long long value
, incr
;
324 robj
*o
, *current
, *new;
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
);
334 decrRefCount(current
);
340 new = createStringObjectFromLongLong(value
);
341 hashTypeTryObjectEncoding(o
,&c
->argv
[2],NULL
);
342 hashTypeSet(o
,c
->argv
[2],new);
344 addReplyLongLong(c
,value
);
345 touchWatchedKey(c
->db
,c
->argv
[1]);
349 void hgetCommand(redisClient
*c
) {
355 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.nullbulk
)) == NULL
||
356 checkType(c
,o
,REDIS_HASH
)) return;
358 if ((encoding
= hashTypeGet(o
,c
->argv
[2],&value
,&v
,&vlen
)) != -1) {
359 if (encoding
== REDIS_ENCODING_HT
)
360 addReplyBulk(c
,value
);
362 addReplyBulkCBuffer(c
,v
,vlen
);
364 addReply(c
,shared
.nullbulk
);
368 void hmgetCommand(redisClient
*c
) {
374 o
= lookupKeyRead(c
->db
,c
->argv
[1]);
375 if (o
!= NULL
&& o
->type
!= REDIS_HASH
) {
376 addReply(c
,shared
.wrongtypeerr
);
380 /* Note the check for o != NULL happens inside the loop. This is
381 * done because objects that cannot be found are considered to be
382 * an empty hash. The reply should then be a series of NULLs. */
383 addReplyMultiBulkLen(c
,c
->argc
-2);
384 for (i
= 2; i
< c
->argc
; i
++) {
386 (encoding
= hashTypeGet(o
,c
->argv
[i
],&value
,&v
,&vlen
)) != -1) {
387 if (encoding
== REDIS_ENCODING_HT
)
388 addReplyBulk(c
,value
);
390 addReplyBulkCBuffer(c
,v
,vlen
);
392 addReply(c
,shared
.nullbulk
);
397 void hdelCommand(redisClient
*c
) {
399 if ((o
= lookupKeyWriteOrReply(c
,c
->argv
[1],shared
.czero
)) == NULL
||
400 checkType(c
,o
,REDIS_HASH
)) return;
402 if (hashTypeDelete(o
,c
->argv
[2])) {
403 if (hashTypeLength(o
) == 0) dbDelete(c
->db
,c
->argv
[1]);
404 addReply(c
,shared
.cone
);
405 touchWatchedKey(c
->db
,c
->argv
[1]);
408 addReply(c
,shared
.czero
);
412 void hlenCommand(redisClient
*c
) {
414 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.czero
)) == NULL
||
415 checkType(c
,o
,REDIS_HASH
)) return;
417 addReplyLongLong(c
,hashTypeLength(o
));
420 void genericHgetallCommand(redisClient
*c
, int flags
) {
422 unsigned long count
= 0;
423 hashTypeIterator
*hi
;
424 void *replylen
= NULL
;
426 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.emptymultibulk
)) == NULL
427 || checkType(c
,o
,REDIS_HASH
)) return;
429 replylen
= addDeferredMultiBulkLength(c
);
430 hi
= hashTypeInitIterator(o
);
431 while (hashTypeNext(hi
) != REDIS_ERR
) {
437 if (flags
& REDIS_HASH_KEY
) {
438 encoding
= hashTypeCurrent(hi
,REDIS_HASH_KEY
,&obj
,&v
,&vlen
);
439 if (encoding
== REDIS_ENCODING_HT
)
442 addReplyBulkCBuffer(c
,v
,vlen
);
445 if (flags
& REDIS_HASH_VALUE
) {
446 encoding
= hashTypeCurrent(hi
,REDIS_HASH_VALUE
,&obj
,&v
,&vlen
);
447 if (encoding
== REDIS_ENCODING_HT
)
450 addReplyBulkCBuffer(c
,v
,vlen
);
454 hashTypeReleaseIterator(hi
);
455 setDeferredMultiBulkLength(c
,replylen
,count
);
458 void hkeysCommand(redisClient
*c
) {
459 genericHgetallCommand(c
,REDIS_HASH_KEY
);
462 void hvalsCommand(redisClient
*c
) {
463 genericHgetallCommand(c
,REDIS_HASH_VALUE
);
466 void hgetallCommand(redisClient
*c
) {
467 genericHgetallCommand(c
,REDIS_HASH_KEY
|REDIS_HASH_VALUE
);
470 void hexistsCommand(redisClient
*c
) {
472 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.czero
)) == NULL
||
473 checkType(c
,o
,REDIS_HASH
)) return;
475 addReply(c
, hashTypeExists(o
,c
->argv
[2]) ? shared
.cone
: shared
.czero
);