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