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