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