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 } 733