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