]>
Commit | Line | Data |
---|---|---|
e2641e09 | 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 | ||
3d24304f | 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 | { | |
e2641e09 | 48 | if (o->encoding == REDIS_ENCODING_ZIPMAP) { |
3d24304f | 49 | int found; |
50 | ||
e2641e09 | 51 | key = getDecodedObject(key); |
3d24304f | 52 | found = zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),v,vlen); |
e2641e09 | 53 | decrRefCount(key); |
3d24304f | 54 | if (!found) return -1; |
e2641e09 | 55 | } else { |
56 | dictEntry *de = dictFind(o->ptr,key); | |
3d24304f | 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; | |
e2641e09 | 83 | } |
e2641e09 | 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); | |
5b4bff9c | 256 | touchWatchedKey(c->db,c->argv[1]); |
e2641e09 | 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); | |
5b4bff9c | 271 | touchWatchedKey(c->db,c->argv[1]); |
e2641e09 | 272 | server.dirty++; |
273 | } | |
274 | } | |
275 | ||
276 | void hmsetCommand(redisClient *c) { | |
277 | int i; | |
278 | robj *o; | |
279 | ||
280 | if ((c->argc % 2) == 1) { | |
3ab20376 | 281 | addReplyError(c,"wrong number of arguments for HMSET"); |
e2641e09 | 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); | |
5b4bff9c | 292 | touchWatchedKey(c->db,c->argv[1]); |
e2641e09 | 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; | |
3d24304f | 302 | if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { |
e2641e09 | 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); | |
5b4bff9c | 319 | touchWatchedKey(c->db,c->argv[1]); |
e2641e09 | 320 | server.dirty++; |
321 | } | |
322 | ||
323 | void hgetCommand(redisClient *c) { | |
324 | robj *o, *value; | |
3d24304f | 325 | unsigned char *v; |
326 | unsigned int vlen; | |
327 | int encoding; | |
328 | ||
e2641e09 | 329 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || |
330 | checkType(c,o,REDIS_HASH)) return; | |
331 | ||
3d24304f | 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); | |
e2641e09 | 337 | } else { |
338 | addReply(c,shared.nullbulk); | |
339 | } | |
340 | } | |
341 | ||
342 | void hmgetCommand(redisClient *c) { | |
3d24304f | 343 | int i, encoding; |
e2641e09 | 344 | robj *o, *value; |
3d24304f | 345 | unsigned char *v; |
346 | unsigned int vlen; | |
347 | ||
e2641e09 | 348 | o = lookupKeyRead(c->db,c->argv[1]); |
349 | if (o != NULL && o->type != REDIS_HASH) { | |
350 | addReply(c,shared.wrongtypeerr); | |
e584d82f | 351 | return; |
e2641e09 | 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. */ | |
0537e7bf | 357 | addReplyMultiBulkLen(c,c->argc-2); |
e2641e09 | 358 | for (i = 2; i < c->argc; i++) { |
3d24304f | 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); | |
e2641e09 | 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); | |
5b4bff9c | 379 | touchWatchedKey(c->db,c->argv[1]); |
e2641e09 | 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 | ||
b70d3555 | 391 | addReplyLongLong(c,hashTypeLength(o)); |
e2641e09 | 392 | } |
393 | ||
394 | void genericHgetallCommand(redisClient *c, int flags) { | |
b301c1fc | 395 | robj *o, *obj; |
e2641e09 | 396 | unsigned long count = 0; |
397 | hashTypeIterator *hi; | |
b301c1fc | 398 | void *replylen = NULL; |
e2641e09 | 399 | |
400 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL | |
401 | || checkType(c,o,REDIS_HASH)) return; | |
402 | ||
b301c1fc | 403 | replylen = addDeferredMultiBulkLength(c); |
e2641e09 | 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); | |
b301c1fc | 420 | setDeferredMultiBulkLength(c,replylen,count); |
e2641e09 | 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 | } |