]> git.saurik.com Git - redis.git/blob - src/t_hash.c
Update default configuration
[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;
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 value += incr;
526 new = createStringObjectFromLongLong(value);
527 hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
528 hashTypeSet(o,c->argv[2],new);
529 decrRefCount(new);
530 addReplyLongLong(c,value);
531 signalModifiedKey(c->db,c->argv[1]);
532 server.dirty++;
533 }
534
535 void hincrbyfloatCommand(redisClient *c) {
536 double long value, incr;
537 robj *o, *current, *new;
538
539 if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
540 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
541 if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
542 if (getLongDoubleFromObjectOrReply(c,current,&value,
543 "hash value is not a valid float") != REDIS_OK) {
544 decrRefCount(current);
545 return;
546 }
547 decrRefCount(current);
548 } else {
549 value = 0;
550 }
551
552 value += incr;
553 new = createStringObjectFromLongDouble(value);
554 hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
555 hashTypeSet(o,c->argv[2],new);
556 addReplyBulk(c,new);
557 decrRefCount(new);
558 signalModifiedKey(c->db,c->argv[1]);
559 server.dirty++;
560 }
561
562 static void addHashFieldToReply(redisClient *c, robj *o, robj *field) {
563 int ret;
564
565 if (o == NULL) {
566 addReply(c, shared.nullbulk);
567 return;
568 }
569
570 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
571 unsigned char *vstr = NULL;
572 unsigned int vlen = UINT_MAX;
573 long long vll = LLONG_MAX;
574
575 ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
576 if (ret < 0) {
577 addReply(c, shared.nullbulk);
578 } else {
579 if (vstr) {
580 addReplyBulkCBuffer(c, vstr, vlen);
581 } else {
582 addReplyBulkLongLong(c, vll);
583 }
584 }
585
586 } else if (o->encoding == REDIS_ENCODING_HT) {
587 robj *value;
588
589 ret = hashTypeGetFromHashTable(o, field, &value);
590 if (ret < 0) {
591 addReply(c, shared.nullbulk);
592 } else {
593 addReplyBulk(c, value);
594 }
595
596 } else {
597 redisPanic("Unknown hash encoding");
598 }
599 }
600
601 void hgetCommand(redisClient *c) {
602 robj *o;
603
604 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
605 checkType(c,o,REDIS_HASH)) return;
606
607 addHashFieldToReply(c, o, c->argv[2]);
608 }
609
610 void hmgetCommand(redisClient *c) {
611 robj *o;
612 int i;
613
614 /* Don't abort when the key cannot be found. Non-existing keys are empty
615 * hashes, where HMGET should respond with a series of null bulks. */
616 o = lookupKeyRead(c->db, c->argv[1]);
617 if (o != NULL && o->type != REDIS_HASH) {
618 addReply(c, shared.wrongtypeerr);
619 return;
620 }
621
622 addReplyMultiBulkLen(c, c->argc-2);
623 for (i = 2; i < c->argc; i++) {
624 addHashFieldToReply(c, o, c->argv[i]);
625 }
626 }
627
628 void hdelCommand(redisClient *c) {
629 robj *o;
630 int j, deleted = 0;
631
632 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
633 checkType(c,o,REDIS_HASH)) return;
634
635 for (j = 2; j < c->argc; j++) {
636 if (hashTypeDelete(o,c->argv[j])) {
637 deleted++;
638 if (hashTypeLength(o) == 0) {
639 dbDelete(c->db,c->argv[1]);
640 break;
641 }
642 }
643 }
644 if (deleted) {
645 signalModifiedKey(c->db,c->argv[1]);
646 server.dirty += deleted;
647 }
648 addReplyLongLong(c,deleted);
649 }
650
651 void hlenCommand(redisClient *c) {
652 robj *o;
653 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
654 checkType(c,o,REDIS_HASH)) return;
655
656 addReplyLongLong(c,hashTypeLength(o));
657 }
658
659 static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) {
660 if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
661 unsigned char *vstr = NULL;
662 unsigned int vlen = UINT_MAX;
663 long long vll = LLONG_MAX;
664
665 hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
666 if (vstr) {
667 addReplyBulkCBuffer(c, vstr, vlen);
668 } else {
669 addReplyBulkLongLong(c, vll);
670 }
671
672 } else if (hi->encoding == REDIS_ENCODING_HT) {
673 robj *value;
674
675 hashTypeCurrentFromHashTable(hi, what, &value);
676 addReplyBulk(c, value);
677
678 } else {
679 redisPanic("Unknown hash encoding");
680 }
681 }
682
683 void genericHgetallCommand(redisClient *c, int flags) {
684 robj *o;
685 hashTypeIterator *hi;
686 int multiplier = 0;
687 int length, count = 0;
688
689 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
690 || checkType(c,o,REDIS_HASH)) return;
691
692 if (flags & REDIS_HASH_KEY) multiplier++;
693 if (flags & REDIS_HASH_VALUE) multiplier++;
694
695 length = hashTypeLength(o) * multiplier;
696 addReplyMultiBulkLen(c, length);
697
698 hi = hashTypeInitIterator(o);
699 while (hashTypeNext(hi) != REDIS_ERR) {
700 if (flags & REDIS_HASH_KEY) {
701 addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY);
702 count++;
703 }
704 if (flags & REDIS_HASH_VALUE) {
705 addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE);
706 count++;
707 }
708 }
709
710 hashTypeReleaseIterator(hi);
711 redisAssert(count == length);
712 }
713
714 void hkeysCommand(redisClient *c) {
715 genericHgetallCommand(c,REDIS_HASH_KEY);
716 }
717
718 void hvalsCommand(redisClient *c) {
719 genericHgetallCommand(c,REDIS_HASH_VALUE);
720 }
721
722 void hgetallCommand(redisClient *c) {
723 genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
724 }
725
726 void hexistsCommand(redisClient *c) {
727 robj *o;
728 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
729 checkType(c,o,REDIS_HASH)) return;
730
731 addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
732 }