]>
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 * This increases the refcount of the field object by 1. */
189 robj
*hashTypeCurrent(hashTypeIterator
*hi
, int what
) {
191 if (hi
->encoding
== REDIS_ENCODING_ZIPMAP
) {
192 if (what
& REDIS_HASH_KEY
) {
193 o
= createStringObject((char*)hi
->zk
,hi
->zklen
);
195 o
= createStringObject((char*)hi
->zv
,hi
->zvlen
);
198 if (what
& REDIS_HASH_KEY
) {
199 o
= dictGetEntryKey(hi
->de
);
201 o
= dictGetEntryVal(hi
->de
);
208 robj
*hashTypeLookupWriteOrCreate(redisClient
*c
, robj
*key
) {
209 robj
*o
= lookupKeyWrite(c
->db
,key
);
211 o
= createHashObject();
214 if (o
->type
!= REDIS_HASH
) {
215 addReply(c
,shared
.wrongtypeerr
);
222 void convertToRealHash(robj
*o
) {
223 unsigned char *key
, *val
, *p
, *zm
= o
->ptr
;
224 unsigned int klen
, vlen
;
225 dict
*dict
= dictCreate(&hashDictType
,NULL
);
227 redisAssert(o
->type
== REDIS_HASH
&& o
->encoding
!= REDIS_ENCODING_HT
);
228 p
= zipmapRewind(zm
);
229 while((p
= zipmapNext(p
,&key
,&klen
,&val
,&vlen
)) != NULL
) {
230 robj
*keyobj
, *valobj
;
232 keyobj
= createStringObject((char*)key
,klen
);
233 valobj
= createStringObject((char*)val
,vlen
);
234 keyobj
= tryObjectEncoding(keyobj
);
235 valobj
= tryObjectEncoding(valobj
);
236 dictAdd(dict
,keyobj
,valobj
);
238 o
->encoding
= REDIS_ENCODING_HT
;
243 /*-----------------------------------------------------------------------------
245 *----------------------------------------------------------------------------*/
247 void hsetCommand(redisClient
*c
) {
251 if ((o
= hashTypeLookupWriteOrCreate(c
,c
->argv
[1])) == NULL
) return;
252 hashTypeTryConversion(o
,c
->argv
,2,3);
253 hashTypeTryObjectEncoding(o
,&c
->argv
[2], &c
->argv
[3]);
254 update
= hashTypeSet(o
,c
->argv
[2],c
->argv
[3]);
255 addReply(c
, update
? shared
.czero
: shared
.cone
);
256 touchWatchedKey(c
->db
,c
->argv
[1]);
260 void hsetnxCommand(redisClient
*c
) {
262 if ((o
= hashTypeLookupWriteOrCreate(c
,c
->argv
[1])) == NULL
) return;
263 hashTypeTryConversion(o
,c
->argv
,2,3);
265 if (hashTypeExists(o
, c
->argv
[2])) {
266 addReply(c
, shared
.czero
);
268 hashTypeTryObjectEncoding(o
,&c
->argv
[2], &c
->argv
[3]);
269 hashTypeSet(o
,c
->argv
[2],c
->argv
[3]);
270 addReply(c
, shared
.cone
);
271 touchWatchedKey(c
->db
,c
->argv
[1]);
276 void hmsetCommand(redisClient
*c
) {
280 if ((c
->argc
% 2) == 1) {
281 addReplyError(c
,"wrong number of arguments for HMSET");
285 if ((o
= hashTypeLookupWriteOrCreate(c
,c
->argv
[1])) == NULL
) return;
286 hashTypeTryConversion(o
,c
->argv
,2,c
->argc
-1);
287 for (i
= 2; i
< c
->argc
; i
+= 2) {
288 hashTypeTryObjectEncoding(o
,&c
->argv
[i
], &c
->argv
[i
+1]);
289 hashTypeSet(o
,c
->argv
[i
],c
->argv
[i
+1]);
291 addReply(c
, shared
.ok
);
292 touchWatchedKey(c
->db
,c
->argv
[1]);
296 void hincrbyCommand(redisClient
*c
) {
297 long long value
, incr
;
298 robj
*o
, *current
, *new;
300 if (getLongLongFromObjectOrReply(c
,c
->argv
[3],&incr
,NULL
) != REDIS_OK
) return;
301 if ((o
= hashTypeLookupWriteOrCreate(c
,c
->argv
[1])) == NULL
) return;
302 if ((current
= hashTypeGetObject(o
,c
->argv
[2])) != NULL
) {
303 if (getLongLongFromObjectOrReply(c
,current
,&value
,
304 "hash value is not an integer") != REDIS_OK
) {
305 decrRefCount(current
);
308 decrRefCount(current
);
314 new = createStringObjectFromLongLong(value
);
315 hashTypeTryObjectEncoding(o
,&c
->argv
[2],NULL
);
316 hashTypeSet(o
,c
->argv
[2],new);
318 addReplyLongLong(c
,value
);
319 touchWatchedKey(c
->db
,c
->argv
[1]);
323 void hgetCommand(redisClient
*c
) {
329 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.nullbulk
)) == NULL
||
330 checkType(c
,o
,REDIS_HASH
)) return;
332 if ((encoding
= hashTypeGet(o
,c
->argv
[2],&value
,&v
,&vlen
)) != -1) {
333 if (encoding
== REDIS_ENCODING_HT
)
334 addReplyBulk(c
,value
);
336 addReplyBulkCBuffer(c
,v
,vlen
);
338 addReply(c
,shared
.nullbulk
);
342 void hmgetCommand(redisClient
*c
) {
348 o
= lookupKeyRead(c
->db
,c
->argv
[1]);
349 if (o
!= NULL
&& o
->type
!= REDIS_HASH
) {
350 addReply(c
,shared
.wrongtypeerr
);
354 /* Note the check for o != NULL happens inside the loop. This is
355 * done because objects that cannot be found are considered to be
356 * an empty hash. The reply should then be a series of NULLs. */
357 addReplyMultiBulkLen(c
,c
->argc
-2);
358 for (i
= 2; i
< c
->argc
; i
++) {
360 (encoding
= hashTypeGet(o
,c
->argv
[i
],&value
,&v
,&vlen
)) != -1) {
361 if (encoding
== REDIS_ENCODING_HT
)
362 addReplyBulk(c
,value
);
364 addReplyBulkCBuffer(c
,v
,vlen
);
366 addReply(c
,shared
.nullbulk
);
371 void hdelCommand(redisClient
*c
) {
373 if ((o
= lookupKeyWriteOrReply(c
,c
->argv
[1],shared
.czero
)) == NULL
||
374 checkType(c
,o
,REDIS_HASH
)) return;
376 if (hashTypeDelete(o
,c
->argv
[2])) {
377 if (hashTypeLength(o
) == 0) dbDelete(c
->db
,c
->argv
[1]);
378 addReply(c
,shared
.cone
);
379 touchWatchedKey(c
->db
,c
->argv
[1]);
382 addReply(c
,shared
.czero
);
386 void hlenCommand(redisClient
*c
) {
388 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.czero
)) == NULL
||
389 checkType(c
,o
,REDIS_HASH
)) return;
391 addReplyLongLong(c
,hashTypeLength(o
));
394 void genericHgetallCommand(redisClient
*c
, int flags
) {
396 unsigned long count
= 0;
397 hashTypeIterator
*hi
;
398 void *replylen
= NULL
;
400 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.emptymultibulk
)) == NULL
401 || checkType(c
,o
,REDIS_HASH
)) return;
403 replylen
= addDeferredMultiBulkLength(c
);
404 hi
= hashTypeInitIterator(o
);
405 while (hashTypeNext(hi
) != REDIS_ERR
) {
406 if (flags
& REDIS_HASH_KEY
) {
407 obj
= hashTypeCurrent(hi
,REDIS_HASH_KEY
);
412 if (flags
& REDIS_HASH_VALUE
) {
413 obj
= hashTypeCurrent(hi
,REDIS_HASH_VALUE
);
419 hashTypeReleaseIterator(hi
);
420 setDeferredMultiBulkLength(c
,replylen
,count
);
423 void hkeysCommand(redisClient
*c
) {
424 genericHgetallCommand(c
,REDIS_HASH_KEY
);
427 void hvalsCommand(redisClient
*c
) {
428 genericHgetallCommand(c
,REDIS_HASH_VALUE
);
431 void hgetallCommand(redisClient
*c
) {
432 genericHgetallCommand(c
,REDIS_HASH_KEY
|REDIS_HASH_VALUE
);
435 void hexistsCommand(redisClient
*c
) {
437 if ((o
= lookupKeyReadOrReply(c
,c
->argv
[1],shared
.czero
)) == NULL
||
438 checkType(c
,o
,REDIS_HASH
)) return;
440 addReply(c
, hashTypeExists(o
,c
->argv
[2]) ? shared
.cone
: shared
.czero
);