Commit | Line | Data |
---|---|---|
e2641e09 | 1 | #include "redis.h" |
e2641e09 | 2 | #include <math.h> |
3 | ||
4 | /*----------------------------------------------------------------------------- | |
5 | * Hash type API | |
6 | *----------------------------------------------------------------------------*/ | |
7 | ||
8 | /* Check the length of a number of objects to see if we need to convert a | |
ebd85e9a | 9 | * ziplist to a real hash. Note that we only check string encoded objects |
e2641e09 | 10 | * as their string length can be queried in constant time. */ |
ebd85e9a | 11 | void hashTypeTryConversion(robj *o, robj **argv, int start, int end) { |
e2641e09 | 12 | int i; |
ebd85e9a PN |
13 | |
14 | if (o->encoding != REDIS_ENCODING_ZIPLIST) return; | |
e2641e09 | 15 | |
16 | for (i = start; i <= end; i++) { | |
17 | if (argv[i]->encoding == REDIS_ENCODING_RAW && | |
ebd85e9a | 18 | sdslen(argv[i]->ptr) > server.hash_max_ziplist_value) |
e2641e09 | 19 | { |
ebd85e9a PN |
20 | hashTypeConvert(o, REDIS_ENCODING_HT); |
21 | break; | |
e2641e09 | 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 | ||
ebd85e9a PN |
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, | |
37 | unsigned char **vstr, | |
38 | unsigned int *vlen, | |
39 | long long *vll) | |
3d24304f | 40 | { |
ebd85e9a PN |
41 | unsigned char *zl, *fptr = NULL, *vptr = NULL; |
42 | int ret; | |
3d24304f | 43 | |
ebd85e9a PN |
44 | redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); |
45 | ||
46 | field = getDecodedObject(field); | |
47 | ||
48 | zl = o->ptr; | |
49 | fptr = ziplistIndex(zl, ZIPLIST_HEAD); | |
fe458402 PN |
50 | if (fptr != NULL) { |
51 | fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); | |
52 | if (fptr != NULL) { | |
53 | /* Grab pointer to the value (fptr points to the field) */ | |
54 | vptr = ziplistNext(zl, fptr); | |
55 | redisAssert(vptr != NULL); | |
ebd85e9a | 56 | } |
3d24304f | 57 | } |
ebd85e9a PN |
58 | |
59 | decrRefCount(field); | |
60 | ||
fe458402 | 61 | if (vptr != NULL) { |
ebd85e9a PN |
62 | ret = ziplistGet(vptr, vstr, vlen, vll); |
63 | redisAssert(ret); | |
64 | return 0; | |
65 | } | |
66 | ||
67 | return -1; | |
3d24304f | 68 | } |
69 | ||
ebd85e9a PN |
70 | /* Get the value from a hash table encoded hash, identified by field. |
71 | * Returns -1 when the field cannot be found. */ | |
72 | int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) { | |
73 | dictEntry *de; | |
74 | ||
75 | redisAssert(o->encoding == REDIS_ENCODING_HT); | |
76 | ||
77 | de = dictFind(o->ptr, field); | |
c0caa1cf | 78 | if (de == NULL) return -1; |
ebd85e9a PN |
79 | *value = dictGetVal(de); |
80 | return 0; | |
81 | } | |
82 | ||
83 | /* Higher level function of hashTypeGet*() that always returns a Redis | |
3d24304f | 84 | * object (either new or with refcount incremented), so that the caller |
85 | * can retain a reference or call decrRefCount after the usage. | |
86 | * | |
87 | * The lower level function can prevent copy on write so it is | |
88 | * the preferred way of doing read operations. */ | |
ebd85e9a PN |
89 | robj *hashTypeGetObject(robj *o, robj *field) { |
90 | robj *value = NULL; | |
91 | ||
92 | if (o->encoding == REDIS_ENCODING_ZIPLIST) { | |
93 | unsigned char *vstr = NULL; | |
94 | unsigned int vlen = UINT_MAX; | |
95 | long long vll = LLONG_MAX; | |
96 | ||
97 | if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) { | |
98 | if (vstr) { | |
99 | value = createStringObject((char*)vstr, vlen); | |
100 | } else { | |
101 | value = createStringObjectFromLongLong(vll); | |
102 | } | |
103 | } | |
104 | ||
105 | } else if (o->encoding == REDIS_ENCODING_HT) { | |
106 | robj *aux; | |
107 | ||
108 | if (hashTypeGetFromHashTable(o, field, &aux) == 0) { | |
109 | incrRefCount(aux); | |
110 | value = aux; | |
e2641e09 | 111 | } |
e2641e09 | 112 | } else { |
ebd85e9a PN |
113 | redisPanic("Unknown hash encoding"); |
114 | } | |
ebd85e9a PN |
115 | return value; |
116 | } | |
117 | ||
118 | /* Test if the specified field exists in the given hash. Returns 1 if the field | |
119 | * exists, and 0 when it doesn't. */ | |
120 | int hashTypeExists(robj *o, robj *field) { | |
121 | if (o->encoding == REDIS_ENCODING_ZIPLIST) { | |
122 | unsigned char *vstr = NULL; | |
123 | unsigned int vlen = UINT_MAX; | |
124 | long long vll = LLONG_MAX; | |
125 | ||
753bb3dc | 126 | if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1; |
ebd85e9a PN |
127 | } else if (o->encoding == REDIS_ENCODING_HT) { |
128 | robj *aux; | |
129 | ||
753bb3dc | 130 | if (hashTypeGetFromHashTable(o, field, &aux) == 0) return 1; |
ebd85e9a PN |
131 | } else { |
132 | redisPanic("Unknown hash encoding"); | |
e2641e09 | 133 | } |
134 | return 0; | |
135 | } | |
136 | ||
137 | /* Add an element, discard the old if the key already exists. | |
138 | * Return 0 on insert and 1 on update. */ | |
ebd85e9a | 139 | int hashTypeSet(robj *o, robj *field, robj *value) { |
e2641e09 | 140 | int update = 0; |
ebd85e9a PN |
141 | |
142 | if (o->encoding == REDIS_ENCODING_ZIPLIST) { | |
143 | unsigned char *zl, *fptr, *vptr; | |
144 | ||
145 | field = getDecodedObject(field); | |
e2641e09 | 146 | value = getDecodedObject(value); |
ebd85e9a PN |
147 | |
148 | zl = o->ptr; | |
149 | fptr = ziplistIndex(zl, ZIPLIST_HEAD); | |
fe458402 PN |
150 | if (fptr != NULL) { |
151 | fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); | |
152 | if (fptr != NULL) { | |
153 | /* Grab pointer to the value (fptr points to the field) */ | |
154 | vptr = ziplistNext(zl, fptr); | |
155 | redisAssert(vptr != NULL); | |
ebd85e9a | 156 | update = 1; |
ebd85e9a | 157 | |
fe458402 PN |
158 | /* Delete value */ |
159 | zl = ziplistDelete(zl, &vptr); | |
160 | ||
161 | /* Insert new value */ | |
162 | zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr)); | |
163 | } | |
164 | } | |
ebd85e9a | 165 | |
fe458402 PN |
166 | if (!update) { |
167 | /* Push new field/value pair onto the tail of the ziplist */ | |
168 | zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL); | |
169 | zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL); | |
ebd85e9a PN |
170 | } |
171 | ||
ebd85e9a PN |
172 | o->ptr = zl; |
173 | ||
174 | decrRefCount(field); | |
e2641e09 | 175 | decrRefCount(value); |
176 | ||
ebd85e9a PN |
177 | /* Check if the ziplist needs to be converted to a hash table */ |
178 | if (hashTypeLength(o) > server.hash_max_ziplist_entries) { | |
179 | hashTypeConvert(o, REDIS_ENCODING_HT); | |
180 | } | |
181 | ||
182 | } else if (o->encoding == REDIS_ENCODING_HT) { | |
183 | if (dictReplace(o->ptr, field, value)) { /* Insert */ | |
184 | incrRefCount(field); | |
185 | } else { /* Update */ | |
e2641e09 | 186 | update = 1; |
187 | } | |
ebd85e9a | 188 | |
e2641e09 | 189 | incrRefCount(value); |
ebd85e9a PN |
190 | |
191 | } else { | |
192 | redisPanic("Unknown hash encoding"); | |
e2641e09 | 193 | } |
ebd85e9a | 194 | |
e2641e09 | 195 | return update; |
196 | } | |
197 | ||
198 | /* Delete an element from a hash. | |
199 | * Return 1 on deleted and 0 on not found. */ | |
ebd85e9a | 200 | int hashTypeDelete(robj *o, robj *field) { |
e2641e09 | 201 | int deleted = 0; |
ebd85e9a PN |
202 | |
203 | if (o->encoding == REDIS_ENCODING_ZIPLIST) { | |
fe458402 | 204 | unsigned char *zl, *fptr; |
ebd85e9a PN |
205 | |
206 | field = getDecodedObject(field); | |
207 | ||
208 | zl = o->ptr; | |
209 | fptr = ziplistIndex(zl, ZIPLIST_HEAD); | |
fe458402 PN |
210 | if (fptr != NULL) { |
211 | fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); | |
212 | if (fptr != NULL) { | |
ebd85e9a PN |
213 | zl = ziplistDelete(zl,&fptr); |
214 | zl = ziplistDelete(zl,&fptr); | |
215 | o->ptr = zl; | |
216 | deleted = 1; | |
ebd85e9a | 217 | } |
ebd85e9a PN |
218 | } |
219 | ||
220 | decrRefCount(field); | |
221 | ||
222 | } else if (o->encoding == REDIS_ENCODING_HT) { | |
223 | if (dictDelete((dict*)o->ptr, field) == REDIS_OK) { | |
224 | deleted = 1; | |
225 | ||
226 | /* Always check if the dictionary needs a resize after a delete. */ | |
227 | if (htNeedsResize(o->ptr)) dictResize(o->ptr); | |
228 | } | |
229 | ||
e2641e09 | 230 | } else { |
ebd85e9a | 231 | redisPanic("Unknown hash encoding"); |
e2641e09 | 232 | } |
ebd85e9a | 233 | |
e2641e09 | 234 | return deleted; |
235 | } | |
236 | ||
237 | /* Return the number of elements in a hash. */ | |
238 | unsigned long hashTypeLength(robj *o) { | |
ebd85e9a PN |
239 | unsigned long length = ULONG_MAX; |
240 | ||
241 | if (o->encoding == REDIS_ENCODING_ZIPLIST) { | |
242 | length = ziplistLen(o->ptr) / 2; | |
243 | } else if (o->encoding == REDIS_ENCODING_HT) { | |
244 | length = dictSize((dict*)o->ptr); | |
245 | } else { | |
246 | redisPanic("Unknown hash encoding"); | |
247 | } | |
248 | ||
249 | return length; | |
e2641e09 | 250 | } |
251 | ||
252 | hashTypeIterator *hashTypeInitIterator(robj *subject) { | |
253 | hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator)); | |
ebd85e9a | 254 | hi->subject = subject; |
e2641e09 | 255 | hi->encoding = subject->encoding; |
ebd85e9a PN |
256 | |
257 | if (hi->encoding == REDIS_ENCODING_ZIPLIST) { | |
258 | hi->fptr = NULL; | |
259 | hi->vptr = NULL; | |
e2641e09 | 260 | } else if (hi->encoding == REDIS_ENCODING_HT) { |
261 | hi->di = dictGetIterator(subject->ptr); | |
262 | } else { | |
ebd85e9a | 263 | redisPanic("Unknown hash encoding"); |
e2641e09 | 264 | } |
ebd85e9a | 265 | |
e2641e09 | 266 | return hi; |
267 | } | |
268 | ||
269 | void hashTypeReleaseIterator(hashTypeIterator *hi) { | |
270 | if (hi->encoding == REDIS_ENCODING_HT) { | |
271 | dictReleaseIterator(hi->di); | |
272 | } | |
ebd85e9a | 273 | |
e2641e09 | 274 | zfree(hi); |
275 | } | |
276 | ||
277 | /* Move to the next entry in the hash. Return REDIS_OK when the next entry | |
278 | * could be found and REDIS_ERR when the iterator reaches the end. */ | |
279 | int hashTypeNext(hashTypeIterator *hi) { | |
ebd85e9a PN |
280 | if (hi->encoding == REDIS_ENCODING_ZIPLIST) { |
281 | unsigned char *zl; | |
282 | unsigned char *fptr, *vptr; | |
283 | ||
284 | zl = hi->subject->ptr; | |
285 | fptr = hi->fptr; | |
286 | vptr = hi->vptr; | |
287 | ||
288 | if (fptr == NULL) { | |
289 | /* Initialize cursor */ | |
290 | redisAssert(vptr == NULL); | |
291 | fptr = ziplistIndex(zl, 0); | |
292 | } else { | |
293 | /* Advance cursor */ | |
294 | redisAssert(vptr != NULL); | |
295 | fptr = ziplistNext(zl, vptr); | |
296 | } | |
753bb3dc | 297 | if (fptr == NULL) return REDIS_ERR; |
ebd85e9a PN |
298 | |
299 | /* Grab pointer to the value (fptr points to the field) */ | |
300 | vptr = ziplistNext(zl, fptr); | |
301 | redisAssert(vptr != NULL); | |
302 | ||
303 | /* fptr, vptr now point to the first or next pair */ | |
304 | hi->fptr = fptr; | |
305 | hi->vptr = vptr; | |
ebd85e9a | 306 | } else if (hi->encoding == REDIS_ENCODING_HT) { |
753bb3dc | 307 | if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR; |
e2641e09 | 308 | } else { |
ebd85e9a | 309 | redisPanic("Unknown hash encoding"); |
e2641e09 | 310 | } |
311 | return REDIS_OK; | |
312 | } | |
313 | ||
ebd85e9a PN |
314 | /* Get the field or value at iterator cursor, for an iterator on a hash value |
315 | * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */ | |
316 | void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what, | |
317 | unsigned char **vstr, | |
318 | unsigned int *vlen, | |
319 | long long *vll) | |
320 | { | |
321 | int ret; | |
322 | ||
323 | redisAssert(hi->encoding == REDIS_ENCODING_ZIPLIST); | |
324 | ||
325 | if (what & REDIS_HASH_KEY) { | |
326 | ret = ziplistGet(hi->fptr, vstr, vlen, vll); | |
327 | redisAssert(ret); | |
328 | } else { | |
329 | ret = ziplistGet(hi->vptr, vstr, vlen, vll); | |
330 | redisAssert(ret); | |
331 | } | |
332 | } | |
333 | ||
334 | /* Get the field or value at iterator cursor, for an iterator on a hash value | |
335 | * encoded as a ziplist. Prototype is similar to `hashTypeGetFromHashTable`. */ | |
336 | void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst) { | |
337 | redisAssert(hi->encoding == REDIS_ENCODING_HT); | |
338 | ||
339 | if (what & REDIS_HASH_KEY) { | |
340 | *dst = dictGetKey(hi->de); | |
e2641e09 | 341 | } else { |
ebd85e9a | 342 | *dst = dictGetVal(hi->de); |
8c304be3 | 343 | } |
8c304be3 | 344 | } |
345 | ||
ebd85e9a PN |
346 | /* A non copy-on-write friendly but higher level version of hashTypeCurrent*() |
347 | * that returns an object with incremented refcount (or a new object). It is up | |
348 | * to the caller to decrRefCount() the object if no reference is retained. */ | |
8c304be3 | 349 | robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) { |
ebd85e9a PN |
350 | robj *dst; |
351 | ||
352 | if (hi->encoding == REDIS_ENCODING_ZIPLIST) { | |
353 | unsigned char *vstr = NULL; | |
354 | unsigned int vlen = UINT_MAX; | |
355 | long long vll = LLONG_MAX; | |
356 | ||
357 | hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); | |
358 | if (vstr) { | |
359 | dst = createStringObject((char*)vstr, vlen); | |
360 | } else { | |
361 | dst = createStringObjectFromLongLong(vll); | |
362 | } | |
363 | ||
364 | } else if (hi->encoding == REDIS_ENCODING_HT) { | |
365 | hashTypeCurrentFromHashTable(hi, what, &dst); | |
366 | incrRefCount(dst); | |
367 | ||
8c304be3 | 368 | } else { |
ebd85e9a | 369 | redisPanic("Unknown hash encoding"); |
e2641e09 | 370 | } |
ebd85e9a PN |
371 | |
372 | return dst; | |
e2641e09 | 373 | } |
374 | ||
375 | robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) { | |
376 | robj *o = lookupKeyWrite(c->db,key); | |
377 | if (o == NULL) { | |
378 | o = createHashObject(); | |
379 | dbAdd(c->db,key,o); | |
380 | } else { | |
381 | if (o->type != REDIS_HASH) { | |
382 | addReply(c,shared.wrongtypeerr); | |
383 | return NULL; | |
384 | } | |
385 | } | |
386 | return o; | |
387 | } | |
388 | ||
ebd85e9a PN |
389 | void hashTypeConvertZiplist(robj *o, int enc) { |
390 | redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); | |
391 | ||
392 | if (enc == REDIS_ENCODING_ZIPLIST) { | |
393 | /* Nothing to do... */ | |
394 | ||
395 | } else if (enc == REDIS_ENCODING_HT) { | |
396 | hashTypeIterator *hi; | |
397 | dict *dict; | |
398 | int ret; | |
e2641e09 | 399 | |
ebd85e9a PN |
400 | hi = hashTypeInitIterator(o); |
401 | dict = dictCreate(&hashDictType, NULL); | |
e2641e09 | 402 | |
ebd85e9a PN |
403 | while (hashTypeNext(hi) != REDIS_ERR) { |
404 | robj *field, *value; | |
405 | ||
406 | field = hashTypeCurrentObject(hi, REDIS_HASH_KEY); | |
407 | field = tryObjectEncoding(field); | |
408 | value = hashTypeCurrentObject(hi, REDIS_HASH_VALUE); | |
409 | value = tryObjectEncoding(value); | |
410 | ret = dictAdd(dict, field, value); | |
411 | redisAssert(ret == DICT_OK); | |
412 | } | |
413 | ||
414 | hashTypeReleaseIterator(hi); | |
415 | zfree(o->ptr); | |
416 | ||
417 | o->encoding = REDIS_ENCODING_HT; | |
418 | o->ptr = dict; | |
419 | ||
420 | } else { | |
421 | redisPanic("Unknown hash encoding"); | |
422 | } | |
423 | } | |
424 | ||
425 | void hashTypeConvert(robj *o, int enc) { | |
426 | if (o->encoding == REDIS_ENCODING_ZIPLIST) { | |
427 | hashTypeConvertZiplist(o, enc); | |
428 | } else if (o->encoding == REDIS_ENCODING_HT) { | |
429 | redisPanic("Not implemented"); | |
430 | } else { | |
431 | redisPanic("Unknown hash encoding"); | |
e2641e09 | 432 | } |
e2641e09 | 433 | } |
434 | ||
435 | /*----------------------------------------------------------------------------- | |
436 | * Hash type commands | |
437 | *----------------------------------------------------------------------------*/ | |
438 | ||
439 | void hsetCommand(redisClient *c) { | |
440 | int update; | |
441 | robj *o; | |
442 | ||
443 | if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; | |
444 | hashTypeTryConversion(o,c->argv,2,3); | |
445 | hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); | |
446 | update = hashTypeSet(o,c->argv[2],c->argv[3]); | |
447 | addReply(c, update ? shared.czero : shared.cone); | |
cea8c5cd | 448 | signalModifiedKey(c->db,c->argv[1]); |
e2641e09 | 449 | server.dirty++; |
450 | } | |
451 | ||
452 | void hsetnxCommand(redisClient *c) { | |
453 | robj *o; | |
454 | if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; | |
455 | hashTypeTryConversion(o,c->argv,2,3); | |
456 | ||
457 | if (hashTypeExists(o, c->argv[2])) { | |
458 | addReply(c, shared.czero); | |
459 | } else { | |
460 | hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); | |
461 | hashTypeSet(o,c->argv[2],c->argv[3]); | |
462 | addReply(c, shared.cone); | |
cea8c5cd | 463 | signalModifiedKey(c->db,c->argv[1]); |
e2641e09 | 464 | server.dirty++; |
465 | } | |
466 | } | |
467 | ||
468 | void hmsetCommand(redisClient *c) { | |
469 | int i; | |
470 | robj *o; | |
471 | ||
472 | if ((c->argc % 2) == 1) { | |
3ab20376 | 473 | addReplyError(c,"wrong number of arguments for HMSET"); |
e2641e09 | 474 | return; |
475 | } | |
476 | ||
477 | if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; | |
478 | hashTypeTryConversion(o,c->argv,2,c->argc-1); | |
479 | for (i = 2; i < c->argc; i += 2) { | |
480 | hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]); | |
481 | hashTypeSet(o,c->argv[i],c->argv[i+1]); | |
482 | } | |
483 | addReply(c, shared.ok); | |
cea8c5cd | 484 | signalModifiedKey(c->db,c->argv[1]); |
e2641e09 | 485 | server.dirty++; |
486 | } | |
487 | ||
488 | void hincrbyCommand(redisClient *c) { | |
a400a9b2 | 489 | long long value, incr, oldvalue; |
e2641e09 | 490 | robj *o, *current, *new; |
491 | ||
492 | if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; | |
493 | if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; | |
3d24304f | 494 | if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { |
e2641e09 | 495 | if (getLongLongFromObjectOrReply(c,current,&value, |
496 | "hash value is not an integer") != REDIS_OK) { | |
497 | decrRefCount(current); | |
498 | return; | |
499 | } | |
500 | decrRefCount(current); | |
501 | } else { | |
502 | value = 0; | |
503 | } | |
504 | ||
a400a9b2 | 505 | oldvalue = value; |
7c96b467 | 506 | if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || |
507 | (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { | |
a400a9b2 | 508 | addReplyError(c,"increment or decrement would overflow"); |
509 | return; | |
510 | } | |
7c96b467 | 511 | value += incr; |
e2641e09 | 512 | new = createStringObjectFromLongLong(value); |
513 | hashTypeTryObjectEncoding(o,&c->argv[2],NULL); | |
514 | hashTypeSet(o,c->argv[2],new); | |
515 | decrRefCount(new); | |
516 | addReplyLongLong(c,value); | |
cea8c5cd | 517 | signalModifiedKey(c->db,c->argv[1]); |
e2641e09 | 518 | server.dirty++; |
519 | } | |
520 | ||
68bfe993 | 521 | void hincrbyfloatCommand(redisClient *c) { |
522 | double long value, incr; | |
523 | robj *o, *current, *new; | |
524 | ||
525 | if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; | |
526 | if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; | |
527 | if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { | |
528 | if (getLongDoubleFromObjectOrReply(c,current,&value, | |
529 | "hash value is not a valid float") != REDIS_OK) { | |
530 | decrRefCount(current); | |
531 | return; | |
532 | } | |
533 | decrRefCount(current); | |
534 | } else { | |
535 | value = 0; | |
536 | } | |
537 | ||
538 | value += incr; | |
539 | new = createStringObjectFromLongDouble(value); | |
540 | hashTypeTryObjectEncoding(o,&c->argv[2],NULL); | |
541 | hashTypeSet(o,c->argv[2],new); | |
542 | addReplyBulk(c,new); | |
543 | decrRefCount(new); | |
544 | signalModifiedKey(c->db,c->argv[1]); | |
545 | server.dirty++; | |
546 | } | |
547 | ||
ebd85e9a PN |
548 | static void addHashFieldToReply(redisClient *c, robj *o, robj *field) { |
549 | int ret; | |
550 | ||
551 | if (o == NULL) { | |
552 | addReply(c, shared.nullbulk); | |
553 | return; | |
554 | } | |
555 | ||
556 | if (o->encoding == REDIS_ENCODING_ZIPLIST) { | |
557 | unsigned char *vstr = NULL; | |
558 | unsigned int vlen = UINT_MAX; | |
559 | long long vll = LLONG_MAX; | |
560 | ||
561 | ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll); | |
562 | if (ret < 0) { | |
563 | addReply(c, shared.nullbulk); | |
564 | } else { | |
565 | if (vstr) { | |
566 | addReplyBulkCBuffer(c, vstr, vlen); | |
567 | } else { | |
568 | addReplyBulkLongLong(c, vll); | |
569 | } | |
570 | } | |
571 | ||
572 | } else if (o->encoding == REDIS_ENCODING_HT) { | |
573 | robj *value; | |
574 | ||
575 | ret = hashTypeGetFromHashTable(o, field, &value); | |
576 | if (ret < 0) { | |
577 | addReply(c, shared.nullbulk); | |
578 | } else { | |
579 | addReplyBulk(c, value); | |
580 | } | |
581 | ||
582 | } else { | |
583 | redisPanic("Unknown hash encoding"); | |
584 | } | |
585 | } | |
586 | ||
e2641e09 | 587 | void hgetCommand(redisClient *c) { |
ebd85e9a | 588 | robj *o; |
3d24304f | 589 | |
e2641e09 | 590 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || |
591 | checkType(c,o,REDIS_HASH)) return; | |
592 | ||
ebd85e9a | 593 | addHashFieldToReply(c, o, c->argv[2]); |
e2641e09 | 594 | } |
595 | ||
596 | void hmgetCommand(redisClient *c) { | |
ebd85e9a PN |
597 | robj *o; |
598 | int i; | |
3d24304f | 599 | |
ebd85e9a PN |
600 | /* Don't abort when the key cannot be found. Non-existing keys are empty |
601 | * hashes, where HMGET should respond with a series of null bulks. */ | |
602 | o = lookupKeyRead(c->db, c->argv[1]); | |
e2641e09 | 603 | if (o != NULL && o->type != REDIS_HASH) { |
ebd85e9a | 604 | addReply(c, shared.wrongtypeerr); |
e584d82f | 605 | return; |
e2641e09 | 606 | } |
607 | ||
ebd85e9a | 608 | addReplyMultiBulkLen(c, c->argc-2); |
e2641e09 | 609 | for (i = 2; i < c->argc; i++) { |
ebd85e9a | 610 | addHashFieldToReply(c, o, c->argv[i]); |
e2641e09 | 611 | } |
612 | } | |
613 | ||
614 | void hdelCommand(redisClient *c) { | |
615 | robj *o; | |
64a13a36 | 616 | int j, deleted = 0; |
617 | ||
e2641e09 | 618 | if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || |
619 | checkType(c,o,REDIS_HASH)) return; | |
620 | ||
64a13a36 | 621 | for (j = 2; j < c->argc; j++) { |
622 | if (hashTypeDelete(o,c->argv[j])) { | |
64a13a36 | 623 | deleted++; |
2d7162bb PN |
624 | if (hashTypeLength(o) == 0) { |
625 | dbDelete(c->db,c->argv[1]); | |
626 | break; | |
627 | } | |
64a13a36 | 628 | } |
629 | } | |
630 | if (deleted) { | |
cea8c5cd | 631 | signalModifiedKey(c->db,c->argv[1]); |
64a13a36 | 632 | server.dirty += deleted; |
e2641e09 | 633 | } |
64a13a36 | 634 | addReplyLongLong(c,deleted); |
e2641e09 | 635 | } |
636 | ||
637 | void hlenCommand(redisClient *c) { | |
638 | robj *o; | |
639 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || | |
640 | checkType(c,o,REDIS_HASH)) return; | |
641 | ||
b70d3555 | 642 | addReplyLongLong(c,hashTypeLength(o)); |
e2641e09 | 643 | } |
644 | ||
ebd85e9a PN |
645 | static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) { |
646 | if (hi->encoding == REDIS_ENCODING_ZIPLIST) { | |
647 | unsigned char *vstr = NULL; | |
648 | unsigned int vlen = UINT_MAX; | |
649 | long long vll = LLONG_MAX; | |
650 | ||
651 | hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); | |
652 | if (vstr) { | |
653 | addReplyBulkCBuffer(c, vstr, vlen); | |
654 | } else { | |
655 | addReplyBulkLongLong(c, vll); | |
656 | } | |
657 | ||
658 | } else if (hi->encoding == REDIS_ENCODING_HT) { | |
659 | robj *value; | |
660 | ||
661 | hashTypeCurrentFromHashTable(hi, what, &value); | |
662 | addReplyBulk(c, value); | |
663 | ||
664 | } else { | |
665 | redisPanic("Unknown hash encoding"); | |
666 | } | |
667 | } | |
668 | ||
e2641e09 | 669 | void genericHgetallCommand(redisClient *c, int flags) { |
8c304be3 | 670 | robj *o; |
e2641e09 | 671 | hashTypeIterator *hi; |
ebd85e9a PN |
672 | int multiplier = 0; |
673 | int length, count = 0; | |
e2641e09 | 674 | |
675 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL | |
676 | || checkType(c,o,REDIS_HASH)) return; | |
677 | ||
ebd85e9a PN |
678 | if (flags & REDIS_HASH_KEY) multiplier++; |
679 | if (flags & REDIS_HASH_VALUE) multiplier++; | |
680 | ||
681 | length = hashTypeLength(o) * multiplier; | |
682 | addReplyMultiBulkLen(c, length); | |
683 | ||
e2641e09 | 684 | hi = hashTypeInitIterator(o); |
685 | while (hashTypeNext(hi) != REDIS_ERR) { | |
686 | if (flags & REDIS_HASH_KEY) { | |
ebd85e9a | 687 | addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY); |
e2641e09 | 688 | count++; |
689 | } | |
690 | if (flags & REDIS_HASH_VALUE) { | |
ebd85e9a | 691 | addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE); |
e2641e09 | 692 | count++; |
693 | } | |
694 | } | |
ebd85e9a | 695 | |
e2641e09 | 696 | hashTypeReleaseIterator(hi); |
ebd85e9a | 697 | redisAssert(count == length); |
e2641e09 | 698 | } |
699 | ||
700 | void hkeysCommand(redisClient *c) { | |
701 | genericHgetallCommand(c,REDIS_HASH_KEY); | |
702 | } | |
703 | ||
704 | void hvalsCommand(redisClient *c) { | |
705 | genericHgetallCommand(c,REDIS_HASH_VALUE); | |
706 | } | |
707 | ||
708 | void hgetallCommand(redisClient *c) { | |
709 | genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE); | |
710 | } | |
711 | ||
712 | void hexistsCommand(redisClient *c) { | |
713 | robj *o; | |
714 | if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || | |
715 | checkType(c,o,REDIS_HASH)) return; | |
716 | ||
717 | addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero); | |
718 | } |