1 /* 2 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * * Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * * Neither the name of Redis nor the names of its contributors may be used 14 * to endorse or promote products derived from this software without 15 * specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include "redis.h" 31 #include <math.h> 32 33 /*----------------------------------------------------------------------------- 34 * Hash type API 35 *----------------------------------------------------------------------------*/ 36 37 /* Check the length of a number of objects to see if we need to convert a 38 * ziplist to a real hash. Note that we only check string encoded objects 39 * as their string length can be queried in constant time. */ 40 void hashTypeTryConversion(robj *o, robj **argv, int start, int end) { 41 int i; 42 43 if (o->encoding != REDIS_ENCODING_ZIPLIST) return; 44 45 for (i = start; i <= end; i++) { 46 if (argv[i]->encoding == REDIS_ENCODING_RAW && 47 sdslen(argv[i]->ptr) > server.hash_max_ziplist_value) 48 { 49 hashTypeConvert(o, REDIS_ENCODING_HT); 50 break; 51 } 52 } 53 } 54 55 /* Encode given objects in-place when the hash uses a dict. */ 56 void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) { 57 if (subject->encoding == REDIS_ENCODING_HT) { 58 if (o1) *o1 = tryObjectEncoding(*o1); 59 if (o2) *o2 = tryObjectEncoding(*o2); 60 } 61 } 62 63 /* Get the value from a ziplist encoded hash, identified by field. 64 * Returns -1 when the field cannot be found. */ 65 int hashTypeGetFromZiplist(robj *o, robj *field, 66 unsigned char **vstr, 67 unsigned int *vlen, 68 long long *vll) 69 { 70 unsigned char *zl, *fptr = NULL, *vptr = NULL; 71 int ret; 72 73 redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); 74 75 field = getDecodedObject(field); 76 77 zl = o->ptr; 78 fptr = ziplistIndex(zl, ZIPLIST_HEAD); 79 if (fptr != NULL) { 80 fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); 81 if (fptr != NULL) { 82 /* Grab pointer to the value (fptr points to the field) */ 83 vptr = ziplistNext(zl, fptr); 84 redisAssert(vptr != NULL); 85 } 86 } 87 88 decrRefCount(field); 89 90 if (vptr != NULL) { 91 ret = ziplistGet(vptr, vstr, vlen, vll); 92 redisAssert(ret); 93 return 0; 94 } 95 96 return -1; 97 } 98 99 /* Get the value from a hash table encoded hash, identified by field. 100 * Returns -1 when the field cannot be found. */ 101 int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) { 102 dictEntry *de; 103 104 redisAssert(o->encoding == REDIS_ENCODING_HT); 105 106 de = dictFind(o->ptr, field); 107 if (de == NULL) return -1; 108 *value = dictGetVal(de); 109 return 0; 110 } 111 112 /* Higher level function of hashTypeGet*() that always returns a Redis 113 * object (either new or with refcount incremented), so that the caller 114 * can retain a reference or call decrRefCount after the usage. 115 * 116 * The lower level function can prevent copy on write so it is 117 * the preferred way of doing read operations. */ 118 robj *hashTypeGetObject(robj *o, robj *field) { 119 robj *value = NULL; 120 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) { 127 if (vstr) { 128 value = createStringObject((char*)vstr, vlen); 129 } else { 130 value = createStringObjectFromLongLong(vll); 131 } 132 } 133 134 } else if (o->encoding == REDIS_ENCODING_HT) { 135 robj *aux; 136 137 if (hashTypeGetFromHashTable(o, field, &aux) == 0) { 138 incrRefCount(aux); 139 value = aux; 140 } 141 } else { 142 redisPanic("Unknown hash encoding"); 143 } 144 return value; 145 } 146 147 /* Test if the specified field exists in the given hash. Returns 1 if the field 148 * exists, and 0 when it doesn't. */ 149 int hashTypeExists(robj *o, robj *field) { 150 if (o->encoding == REDIS_ENCODING_ZIPLIST) { 151 unsigned char *vstr = NULL; 152 unsigned int vlen = UINT_MAX; 153 long long vll = LLONG_MAX; 154 155 if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1; 156 } else if (o->encoding == REDIS_ENCODING_HT) { 157 robj *aux; 158 159 if (hashTypeGetFromHashTable(o, field, &aux) == 0) return 1; 160 } else { 161 redisPanic("Unknown hash encoding"); 162 } 163 return 0; 164 } 165 166 /* Add an element, discard the old if the key already exists. 167 * Return 0 on insert and 1 on update. 168 * This function will take care of incrementing the reference count of the 169 * retained fields and value objects. */ 170 int hashTypeSet(robj *o, robj *field, robj *value) { 171 int update = 0; 172 173 if (o->encoding == REDIS_ENCODING_ZIPLIST) { 174 unsigned char *zl, *fptr, *vptr; 175 176 field = getDecodedObject(field); 177 value = getDecodedObject(value); 178 179 zl = o->ptr; 180 fptr = ziplistIndex(zl, ZIPLIST_HEAD); 181 if (fptr != NULL) { 182 fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); 183 if (fptr != NULL) { 184 /* Grab pointer to the value (fptr points to the field) */ 185 vptr = ziplistNext(zl, fptr); 186 redisAssert(vptr != NULL); 187 update = 1; 188 189 /* Delete value */ 190 zl = ziplistDelete(zl, &vptr); 191 192 /* Insert new value */ 193 zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr)); 194 } 195 } 196 197 if (!update) { 198 /* Push new field/value pair onto the tail of the ziplist */ 199 zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL); 200 zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL); 201 } 202 o->ptr = zl; 203 decrRefCount(field); 204 decrRefCount(value); 205 206 /* Check if the ziplist needs to be converted to a hash table */ 207 if (hashTypeLength(o) > server.hash_max_ziplist_entries) 208 hashTypeConvert(o, REDIS_ENCODING_HT); 209 } else if (o->encoding == REDIS_ENCODING_HT) { 210 if (dictReplace(o->ptr, field, value)) { /* Insert */ 211 incrRefCount(field); 212 } else { /* Update */ 213 update = 1; 214 } 215 incrRefCount(value); 216 } else { 217 redisPanic("Unknown hash encoding"); 218 } 219 return update; 220 } 221 222 /* Delete an element from a hash. 223 * Return 1 on deleted and 0 on not found. */ 224 int hashTypeDelete(robj *o, robj *field) { 225 int deleted = 0; 226 227 if (o->encoding == REDIS_ENCODING_ZIPLIST) { 228 unsigned char *zl, *fptr; 229 230 field = getDecodedObject(field); 231 232 zl = o->ptr; 233 fptr = ziplistIndex(zl, ZIPLIST_HEAD); 234 if (fptr != NULL) { 235 fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); 236 if (fptr != NULL) { 237 zl = ziplistDelete(zl,&fptr); 238 zl = ziplistDelete(zl,&fptr); 239 o->ptr = zl; 240 deleted = 1; 241 } 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 if (fptr == NULL) return REDIS_ERR; 322 323 /* Grab pointer to the value (fptr points to the field) */ 324 vptr = ziplistNext(zl, fptr); 325 redisAssert(vptr != NULL); 326 327 /* fptr, vptr now point to the first or next pair */ 328 hi->fptr = fptr; 329 hi->vptr = vptr; 330 } else if (hi->encoding == REDIS_ENCODING_HT) { 331 if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR; 332 } else { 333 redisPanic("Unknown hash encoding"); 334 } 335 return REDIS_OK; 336 } 337 338 /* Get the field or value at iterator cursor, for an iterator on a hash value 339 * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */ 340 void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what, 341 unsigned char **vstr, 342 unsigned int *vlen, 343 long long *vll) 344 { 345 int ret; 346 347 redisAssert(hi->encoding == REDIS_ENCODING_ZIPLIST); 348 349 if (what & REDIS_HASH_KEY) { 350 ret = ziplistGet(hi->fptr, vstr, vlen, vll); 351 redisAssert(ret); 352 } else { 353 ret = ziplistGet(hi->vptr, vstr, vlen, vll); 354 redisAssert(ret); 355 } 356 } 357 358 /* Get the field or value at iterator cursor, for an iterator on a hash value 359 * encoded as a ziplist. Prototype is similar to `hashTypeGetFromHashTable`. */ 360 void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst) { 361 redisAssert(hi->encoding == REDIS_ENCODING_HT); 362 363 if (what & REDIS_HASH_KEY) { 364 *dst = dictGetKey(hi->de); 365 } else { 366 *dst = dictGetVal(hi->de); 367 } 368 } 369 370 /* A non copy-on-write friendly but higher level version of hashTypeCurrent*() 371 * that returns an object with incremented refcount (or a new object). It is up 372 * to the caller to decrRefCount() the object if no reference is retained. */ 373 robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) { 374 robj *dst; 375 376 if (hi->encoding == REDIS_ENCODING_ZIPLIST) { 377 unsigned char *vstr = NULL; 378 unsigned int vlen = UINT_MAX; 379 long long vll = LLONG_MAX; 380 381 hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); 382 if (vstr) { 383 dst = createStringObject((char*)vstr, vlen); 384 } else { 385 dst = createStringObjectFromLongLong(vll); 386 } 387 } else if (hi->encoding == REDIS_ENCODING_HT) { 388 hashTypeCurrentFromHashTable(hi, what, &dst); 389 incrRefCount(dst); 390 } else { 391 redisPanic("Unknown hash encoding"); 392 } 393 return dst; 394 } 395 396 robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) { 397 robj *o = lookupKeyWrite(c->db,key); 398 if (o == NULL) { 399 o = createHashObject(); 400 dbAdd(c->db,key,o); 401 } else { 402 if (o->type != REDIS_HASH) { 403 addReply(c,shared.wrongtypeerr); 404 return NULL; 405 } 406 } 407 return o; 408 } 409 410 void hashTypeConvertZiplist(robj *o, int enc) { 411 redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); 412 413 if (enc == REDIS_ENCODING_ZIPLIST) { 414 /* Nothing to do... */ 415 416 } else if (enc == REDIS_ENCODING_HT) { 417 hashTypeIterator *hi; 418 dict *dict; 419 int ret; 420 421 hi = hashTypeInitIterator(o); 422 dict = dictCreate(&hashDictType, NULL); 423 424 while (hashTypeNext(hi) != REDIS_ERR) { 425 robj *field, *value; 426 427 field = hashTypeCurrentObject(hi, REDIS_HASH_KEY); 428 field = tryObjectEncoding(field); 429 value = hashTypeCurrentObject(hi, REDIS_HASH_VALUE); 430 value = tryObjectEncoding(value); 431 ret = dictAdd(dict, field, value); 432 if (ret != DICT_OK) { 433 redisLogHexDump(REDIS_WARNING,"ziplist with dup elements dump", 434 o->ptr,ziplistBlobLen(o->ptr)); 435 redisAssert(ret == DICT_OK); 436 } 437 } 438 439 hashTypeReleaseIterator(hi); 440 zfree(o->ptr); 441 442 o->encoding = REDIS_ENCODING_HT; 443 o->ptr = dict; 444 445 } else { 446 redisPanic("Unknown hash encoding"); 447 } 448 } 449 450 void hashTypeConvert(robj *o, int enc) { 451 if (o->encoding == REDIS_ENCODING_ZIPLIST) { 452 hashTypeConvertZiplist(o, enc); 453 } else if (o->encoding == REDIS_ENCODING_HT) { 454 redisPanic("Not implemented"); 455 } else { 456 redisPanic("Unknown hash encoding"); 457 } 458 } 459 460 /*----------------------------------------------------------------------------- 461 * Hash type commands 462 *----------------------------------------------------------------------------*/ 463 464 void hsetCommand(redisClient *c) { 465 int update; 466 robj *o; 467 468 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 469 hashTypeTryConversion(o,c->argv,2,3); 470 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); 471 update = hashTypeSet(o,c->argv[2],c->argv[3]); 472 addReply(c, update ? shared.czero : shared.cone); 473 signalModifiedKey(c->db,c->argv[1]); 474 notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id); 475 server.dirty++; 476 } 477 478 void hsetnxCommand(redisClient *c) { 479 robj *o; 480 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 481 hashTypeTryConversion(o,c->argv,2,3); 482 483 if (hashTypeExists(o, c->argv[2])) { 484 addReply(c, shared.czero); 485 } else { 486 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); 487 hashTypeSet(o,c->argv[2],c->argv[3]); 488 addReply(c, shared.cone); 489 signalModifiedKey(c->db,c->argv[1]); 490 notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id); 491 server.dirty++; 492 } 493 } 494 495 void hmsetCommand(redisClient *c) { 496 int i; 497 robj *o; 498 499 if ((c->argc % 2) == 1) { 500 addReplyError(c,"wrong number of arguments for HMSET"); 501 return; 502 } 503 504 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 505 hashTypeTryConversion(o,c->argv,2,c->argc-1); 506 for (i = 2; i < c->argc; i += 2) { 507 hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]); 508 hashTypeSet(o,c->argv[i],c->argv[i+1]); 509 } 510 addReply(c, shared.ok); 511 signalModifiedKey(c->db,c->argv[1]); 512 notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id); 513 server.dirty++; 514 } 515 516 void hincrbyCommand(redisClient *c) { 517 long long value, incr, oldvalue; 518 robj *o, *current, *new; 519 520 if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; 521 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 522 if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { 523 if (getLongLongFromObjectOrReply(c,current,&value, 524 "hash value is not an integer") != REDIS_OK) { 525 decrRefCount(current); 526 return; 527 } 528 decrRefCount(current); 529 } else { 530 value = 0; 531 } 532 533 oldvalue = value; 534 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || 535 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { 536 addReplyError(c,"increment or decrement would overflow"); 537 return; 538 } 539 value += incr; 540 new = createStringObjectFromLongLong(value); 541 hashTypeTryObjectEncoding(o,&c->argv[2],NULL); 542 hashTypeSet(o,c->argv[2],new); 543 decrRefCount(new); 544 addReplyLongLong(c,value); 545 signalModifiedKey(c->db,c->argv[1]); 546 notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hincrby",c->argv[1],c->db->id); 547 server.dirty++; 548 } 549 550 void hincrbyfloatCommand(redisClient *c) { 551 double long value, incr; 552 robj *o, *current, *new, *aux; 553 554 if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; 555 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 556 if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { 557 if (getLongDoubleFromObjectOrReply(c,current,&value, 558 "hash value is not a valid float") != REDIS_OK) { 559 decrRefCount(current); 560 return; 561 } 562 decrRefCount(current); 563 } else { 564 value = 0; 565 } 566 567 value += incr; 568 new = createStringObjectFromLongDouble(value); 569 hashTypeTryObjectEncoding(o,&c->argv[2],NULL); 570 hashTypeSet(o,c->argv[2],new); 571 addReplyBulk(c,new); 572 signalModifiedKey(c->db,c->argv[1]); 573 notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id); 574 server.dirty++; 575 576 /* Always replicate HINCRBYFLOAT as an HSET command with the final value 577 * in order to make sure that differences in float pricision or formatting 578 * will not create differences in replicas or after an AOF restart. */ 579 aux = createStringObject("HSET",4); 580 rewriteClientCommandArgument(c,0,aux); 581 decrRefCount(aux); 582 rewriteClientCommandArgument(c,3,new); 583 decrRefCount(new); 584 } 585 586 static void addHashFieldToReply(redisClient *c, robj *o, robj *field) { 587 int ret; 588 589 if (o == NULL) { 590 addReply(c, shared.nullbulk); 591 return; 592 } 593 594 if (o->encoding == REDIS_ENCODING_ZIPLIST) { 595 unsigned char *vstr = NULL; 596 unsigned int vlen = UINT_MAX; 597 long long vll = LLONG_MAX; 598 599 ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll); 600 if (ret < 0) { 601 addReply(c, shared.nullbulk); 602 } else { 603 if (vstr) { 604 addReplyBulkCBuffer(c, vstr, vlen); 605 } else { 606 addReplyBulkLongLong(c, vll); 607 } 608 } 609 610 } else if (o->encoding == REDIS_ENCODING_HT) { 611 robj *value; 612 613 ret = hashTypeGetFromHashTable(o, field, &value); 614 if (ret < 0) { 615 addReply(c, shared.nullbulk); 616 } else { 617 addReplyBulk(c, value); 618 } 619 620 } else { 621 redisPanic("Unknown hash encoding"); 622 } 623 } 624 625 void hgetCommand(redisClient *c) { 626 robj *o; 627 628 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || 629 checkType(c,o,REDIS_HASH)) return; 630 631 addHashFieldToReply(c, o, c->argv[2]); 632 } 633 634 void hmgetCommand(redisClient *c) { 635 robj *o; 636 int i; 637 638 /* Don't abort when the key cannot be found. Non-existing keys are empty 639 * hashes, where HMGET should respond with a series of null bulks. */ 640 o = lookupKeyRead(c->db, c->argv[1]); 641 if (o != NULL && o->type != REDIS_HASH) { 642 addReply(c, shared.wrongtypeerr); 643 return; 644 } 645 646 addReplyMultiBulkLen(c, c->argc-2); 647 for (i = 2; i < c->argc; i++) { 648 addHashFieldToReply(c, o, c->argv[i]); 649 } 650 } 651 652 void hdelCommand(redisClient *c) { 653 robj *o; 654 int j, deleted = 0, keyremoved = 0; 655 656 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || 657 checkType(c,o,REDIS_HASH)) return; 658 659 for (j = 2; j < c->argc; j++) { 660 if (hashTypeDelete(o,c->argv[j])) { 661 deleted++; 662 if (hashTypeLength(o) == 0) { 663 dbDelete(c->db,c->argv[1]); 664 keyremoved = 1; 665 break; 666 } 667 } 668 } 669 if (deleted) { 670 signalModifiedKey(c->db,c->argv[1]); 671 notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hdel",c->argv[1],c->db->id); 672 if (keyremoved) 673 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1], 674 c->db->id); 675 server.dirty += deleted; 676 } 677 addReplyLongLong(c,deleted); 678 } 679 680 void hlenCommand(redisClient *c) { 681 robj *o; 682 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 683 checkType(c,o,REDIS_HASH)) return; 684 685 addReplyLongLong(c,hashTypeLength(o)); 686 } 687 688 static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) { 689 if (hi->encoding == REDIS_ENCODING_ZIPLIST) { 690 unsigned char *vstr = NULL; 691 unsigned int vlen = UINT_MAX; 692 long long vll = LLONG_MAX; 693 694 hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); 695 if (vstr) { 696 addReplyBulkCBuffer(c, vstr, vlen); 697 } else { 698 addReplyBulkLongLong(c, vll); 699 } 700 701 } else if (hi->encoding == REDIS_ENCODING_HT) { 702 robj *value; 703 704 hashTypeCurrentFromHashTable(hi, what, &value); 705 addReplyBulk(c, value); 706 707 } else { 708 redisPanic("Unknown hash encoding"); 709 } 710 } 711 712 void genericHgetallCommand(redisClient *c, int flags) { 713 robj *o; 714 hashTypeIterator *hi; 715 int multiplier = 0; 716 int length, count = 0; 717 718 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL 719 || checkType(c,o,REDIS_HASH)) return; 720 721 if (flags & REDIS_HASH_KEY) multiplier++; 722 if (flags & REDIS_HASH_VALUE) multiplier++; 723 724 length = hashTypeLength(o) * multiplier; 725 addReplyMultiBulkLen(c, length); 726 727 hi = hashTypeInitIterator(o); 728 while (hashTypeNext(hi) != REDIS_ERR) { 729 if (flags & REDIS_HASH_KEY) { 730 addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY); 731 count++; 732 } 733 if (flags & REDIS_HASH_VALUE) { 734 addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE); 735 count++; 736 } 737 } 738 739 hashTypeReleaseIterator(hi); 740 redisAssert(count == length); 741 } 742 743 void hkeysCommand(redisClient *c) { 744 genericHgetallCommand(c,REDIS_HASH_KEY); 745 } 746 747 void hvalsCommand(redisClient *c) { 748 genericHgetallCommand(c,REDIS_HASH_VALUE); 749 } 750 751 void hgetallCommand(redisClient *c) { 752 genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE); 753 } 754 755 void hexistsCommand(redisClient *c) { 756 robj *o; 757 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 758 checkType(c,o,REDIS_HASH)) return; 759 760 addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero); 761 } 762