]> git.saurik.com Git - redis.git/blob - src/t_hash.c
Merge remote branch 'pietern/testverbosity'
[redis.git] / src / t_hash.c
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 = dictGetEntryVal(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 redisAssert(NULL);
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 * This increases the refcount of the field object by 1. */
189 robj *hashTypeCurrent(hashTypeIterator *hi, int what) {
190 robj *o;
191 if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
192 if (what & REDIS_HASH_KEY) {
193 o = createStringObject((char*)hi->zk,hi->zklen);
194 } else {
195 o = createStringObject((char*)hi->zv,hi->zvlen);
196 }
197 } else {
198 if (what & REDIS_HASH_KEY) {
199 o = dictGetEntryKey(hi->de);
200 } else {
201 o = dictGetEntryVal(hi->de);
202 }
203 incrRefCount(o);
204 }
205 return o;
206 }
207
208 robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) {
209 robj *o = lookupKeyWrite(c->db,key);
210 if (o == NULL) {
211 o = createHashObject();
212 dbAdd(c->db,key,o);
213 } else {
214 if (o->type != REDIS_HASH) {
215 addReply(c,shared.wrongtypeerr);
216 return NULL;
217 }
218 }
219 return o;
220 }
221
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);
226
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;
231
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);
237 }
238 o->encoding = REDIS_ENCODING_HT;
239 o->ptr = dict;
240 zfree(zm);
241 }
242
243 /*-----------------------------------------------------------------------------
244 * Hash type commands
245 *----------------------------------------------------------------------------*/
246
247 void hsetCommand(redisClient *c) {
248 int update;
249 robj *o;
250
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]);
257 server.dirty++;
258 }
259
260 void hsetnxCommand(redisClient *c) {
261 robj *o;
262 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
263 hashTypeTryConversion(o,c->argv,2,3);
264
265 if (hashTypeExists(o, c->argv[2])) {
266 addReply(c, shared.czero);
267 } else {
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]);
272 server.dirty++;
273 }
274 }
275
276 void hmsetCommand(redisClient *c) {
277 int i;
278 robj *o;
279
280 if ((c->argc % 2) == 1) {
281 addReplyError(c,"wrong number of arguments for HMSET");
282 return;
283 }
284
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]);
290 }
291 addReply(c, shared.ok);
292 touchWatchedKey(c->db,c->argv[1]);
293 server.dirty++;
294 }
295
296 void hincrbyCommand(redisClient *c) {
297 long long value, incr;
298 robj *o, *current, *new;
299
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);
306 return;
307 }
308 decrRefCount(current);
309 } else {
310 value = 0;
311 }
312
313 value += incr;
314 new = createStringObjectFromLongLong(value);
315 hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
316 hashTypeSet(o,c->argv[2],new);
317 decrRefCount(new);
318 addReplyLongLong(c,value);
319 touchWatchedKey(c->db,c->argv[1]);
320 server.dirty++;
321 }
322
323 void hgetCommand(redisClient *c) {
324 robj *o, *value;
325 unsigned char *v;
326 unsigned int vlen;
327 int encoding;
328
329 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
330 checkType(c,o,REDIS_HASH)) return;
331
332 if ((encoding = hashTypeGet(o,c->argv[2],&value,&v,&vlen)) != -1) {
333 if (encoding == REDIS_ENCODING_HT)
334 addReplyBulk(c,value);
335 else
336 addReplyBulkCBuffer(c,v,vlen);
337 } else {
338 addReply(c,shared.nullbulk);
339 }
340 }
341
342 void hmgetCommand(redisClient *c) {
343 int i, encoding;
344 robj *o, *value;
345 unsigned char *v;
346 unsigned int vlen;
347
348 o = lookupKeyRead(c->db,c->argv[1]);
349 if (o != NULL && o->type != REDIS_HASH) {
350 addReply(c,shared.wrongtypeerr);
351 return;
352 }
353
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++) {
359 if (o != NULL &&
360 (encoding = hashTypeGet(o,c->argv[i],&value,&v,&vlen)) != -1) {
361 if (encoding == REDIS_ENCODING_HT)
362 addReplyBulk(c,value);
363 else
364 addReplyBulkCBuffer(c,v,vlen);
365 } else {
366 addReply(c,shared.nullbulk);
367 }
368 }
369 }
370
371 void hdelCommand(redisClient *c) {
372 robj *o;
373 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
374 checkType(c,o,REDIS_HASH)) return;
375
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]);
380 server.dirty++;
381 } else {
382 addReply(c,shared.czero);
383 }
384 }
385
386 void hlenCommand(redisClient *c) {
387 robj *o;
388 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
389 checkType(c,o,REDIS_HASH)) return;
390
391 addReplyLongLong(c,hashTypeLength(o));
392 }
393
394 void genericHgetallCommand(redisClient *c, int flags) {
395 robj *o, *obj;
396 unsigned long count = 0;
397 hashTypeIterator *hi;
398 void *replylen = NULL;
399
400 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
401 || checkType(c,o,REDIS_HASH)) return;
402
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);
408 addReplyBulk(c,obj);
409 decrRefCount(obj);
410 count++;
411 }
412 if (flags & REDIS_HASH_VALUE) {
413 obj = hashTypeCurrent(hi,REDIS_HASH_VALUE);
414 addReplyBulk(c,obj);
415 decrRefCount(obj);
416 count++;
417 }
418 }
419 hashTypeReleaseIterator(hi);
420 setDeferredMultiBulkLength(c,replylen,count);
421 }
422
423 void hkeysCommand(redisClient *c) {
424 genericHgetallCommand(c,REDIS_HASH_KEY);
425 }
426
427 void hvalsCommand(redisClient *c) {
428 genericHgetallCommand(c,REDIS_HASH_VALUE);
429 }
430
431 void hgetallCommand(redisClient *c) {
432 genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
433 }
434
435 void hexistsCommand(redisClient *c) {
436 robj *o;
437 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
438 checkType(c,o,REDIS_HASH)) return;
439
440 addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
441 }