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