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