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 388 } else if (hi->encoding == REDIS_ENCODING_HT) { 389 hashTypeCurrentFromHashTable(hi, what, &dst); 390 incrRefCount(dst); 391 392 } else { 393 redisPanic("Unknown hash encoding"); 394 } 395 396 return dst; 397 } 398 399 robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) { 400 robj *o = lookupKeyWrite(c->db,key); 401 if (o == NULL) { 402 o = createHashObject(); 403 dbAdd(c->db,key,o); 404 } else { 405 if (o->type != REDIS_HASH) { 406 addReply(c,shared.wrongtypeerr); 407 return NULL; 408 } 409 } 410 return o; 411 } 412 413 void hashTypeConvertZiplist(robj *o, int enc) { 414 redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); 415 416 if (enc == REDIS_ENCODING_ZIPLIST) { 417 /* Nothing to do... */ 418 419 } else if (enc == REDIS_ENCODING_HT) { 420 hashTypeIterator *hi; 421 dict *dict; 422 int ret; 423 424 hi = hashTypeInitIterator(o); 425 dict = dictCreate(&hashDictType, NULL); 426 427 while (hashTypeNext(hi) != REDIS_ERR) { 428 robj *field, *value; 429 430 field = hashTypeCurrentObject(hi, REDIS_HASH_KEY); 431 field = tryObjectEncoding(field); 432 value = hashTypeCurrentObject(hi, REDIS_HASH_VALUE); 433 value = tryObjectEncoding(value); 434 ret = dictAdd(dict, field, value); 435 if (ret != DICT_OK) { 436 redisLogHexDump(REDIS_WARNING,"ziplist with dup elements dump", 437 o->ptr,ziplistBlobLen(o->ptr)); 438 redisAssert(ret == DICT_OK); 439 } 440 } 441 442 hashTypeReleaseIterator(hi); 443 zfree(o->ptr); 444 445 o->encoding = REDIS_ENCODING_HT; 446 o->ptr = dict; 447 448 } else { 449 redisPanic("Unknown hash encoding"); 450 } 451 } 452 453 void hashTypeConvert(robj *o, int enc) { 454 if (o->encoding == REDIS_ENCODING_ZIPLIST) { 455 hashTypeConvertZiplist(o, enc); 456 } else if (o->encoding == REDIS_ENCODING_HT) { 457 redisPanic("Not implemented"); 458 } else { 459 redisPanic("Unknown hash encoding"); 460 } 461 } 462 463 /*----------------------------------------------------------------------------- 464 * Hash type commands 465 *----------------------------------------------------------------------------*/ 466 467 void hsetCommand(redisClient *c) { 468 int update; 469 robj *o; 470 471 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 472 hashTypeTryConversion(o,c->argv,2,3); 473 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); 474 update = hashTypeSet(o,c->argv[2],c->argv[3]); 475 addReply(c, update ? shared.czero : shared.cone); 476 signalModifiedKey(c->db,c->argv[1]); 477 notifyKeyspaceEvent("hset",c->argv[1],c->db->id); 478 server.dirty++; 479 } 480 481 void hsetnxCommand(redisClient *c) { 482 robj *o; 483 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 484 hashTypeTryConversion(o,c->argv,2,3); 485 486 if (hashTypeExists(o, c->argv[2])) { 487 addReply(c, shared.czero); 488 } else { 489 hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); 490 hashTypeSet(o,c->argv[2],c->argv[3]); 491 addReply(c, shared.cone); 492 signalModifiedKey(c->db,c->argv[1]); 493 notifyKeyspaceEvent("hset",c->argv[1],c->db->id); 494 server.dirty++; 495 } 496 } 497 498 void hmsetCommand(redisClient *c) { 499 int i; 500 robj *o; 501 502 if ((c->argc % 2) == 1) { 503 addReplyError(c,"wrong number of arguments for HMSET"); 504 return; 505 } 506 507 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 508 hashTypeTryConversion(o,c->argv,2,c->argc-1); 509 for (i = 2; i < c->argc; i += 2) { 510 hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]); 511 hashTypeSet(o,c->argv[i],c->argv[i+1]); 512 } 513 addReply(c, shared.ok); 514 signalModifiedKey(c->db,c->argv[1]); 515 notifyKeyspaceEvent("hset",c->argv[1],c->db->id); 516 server.dirty++; 517 } 518 519 void hincrbyCommand(redisClient *c) { 520 long long value, incr, oldvalue; 521 robj *o, *current, *new; 522 523 if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; 524 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 525 if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { 526 if (getLongLongFromObjectOrReply(c,current,&value, 527 "hash value is not an integer") != REDIS_OK) { 528 decrRefCount(current); 529 return; 530 } 531 decrRefCount(current); 532 } else { 533 value = 0; 534 } 535 536 oldvalue = value; 537 if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || 538 (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { 539 addReplyError(c,"increment or decrement would overflow"); 540 return; 541 } 542 value += incr; 543 new = createStringObjectFromLongLong(value); 544 hashTypeTryObjectEncoding(o,&c->argv[2],NULL); 545 hashTypeSet(o,c->argv[2],new); 546 decrRefCount(new); 547 addReplyLongLong(c,value); 548 signalModifiedKey(c->db,c->argv[1]); 549 notifyKeyspaceEvent("hincrby",c->argv[1],c->db->id); 550 server.dirty++; 551 } 552 553 void hincrbyfloatCommand(redisClient *c) { 554 double long value, incr; 555 robj *o, *current, *new, *aux; 556 557 if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; 558 if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; 559 if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { 560 if (getLongDoubleFromObjectOrReply(c,current,&value, 561 "hash value is not a valid float") != REDIS_OK) { 562 decrRefCount(current); 563 return; 564 } 565 decrRefCount(current); 566 } else { 567 value = 0; 568 } 569 570 value += incr; 571 new = createStringObjectFromLongDouble(value); 572 hashTypeTryObjectEncoding(o,&c->argv[2],NULL); 573 hashTypeSet(o,c->argv[2],new); 574 addReplyBulk(c,new); 575 signalModifiedKey(c->db,c->argv[1]); 576 notifyKeyspaceEvent("hincrbyfloat",c->argv[1],c->db->id); 577 server.dirty++; 578 579 /* Always replicate HINCRBYFLOAT as an HSET command with the final value 580 * in order to make sure that differences in float pricision or formatting 581 * will not create differences in replicas or after an AOF restart. */ 582 aux = createStringObject("HSET",4); 583 rewriteClientCommandArgument(c,0,aux); 584 decrRefCount(aux); 585 rewriteClientCommandArgument(c,3,new); 586 decrRefCount(new); 587 } 588 589 static void addHashFieldToReply(redisClient *c, robj *o, robj *field) { 590 int ret; 591 592 if (o == NULL) { 593 addReply(c, shared.nullbulk); 594 return; 595 } 596 597 if (o->encoding == REDIS_ENCODING_ZIPLIST) { 598 unsigned char *vstr = NULL; 599 unsigned int vlen = UINT_MAX; 600 long long vll = LLONG_MAX; 601 602 ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll); 603 if (ret < 0) { 604 addReply(c, shared.nullbulk); 605 } else { 606 if (vstr) { 607 addReplyBulkCBuffer(c, vstr, vlen); 608 } else { 609 addReplyBulkLongLong(c, vll); 610 } 611 } 612 613 } else if (o->encoding == REDIS_ENCODING_HT) { 614 robj *value; 615 616 ret = hashTypeGetFromHashTable(o, field, &value); 617 if (ret < 0) { 618 addReply(c, shared.nullbulk); 619 } else { 620 addReplyBulk(c, value); 621 } 622 623 } else { 624 redisPanic("Unknown hash encoding"); 625 } 626 } 627 628 void hgetCommand(redisClient *c) { 629 robj *o; 630 631 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || 632 checkType(c,o,REDIS_HASH)) return; 633 634 addHashFieldToReply(c, o, c->argv[2]); 635 } 636 637 void hmgetCommand(redisClient *c) { 638 robj *o; 639 int i; 640 641 /* Don't abort when the key cannot be found. Non-existing keys are empty 642 * hashes, where HMGET should respond with a series of null bulks. */ 643 o = lookupKeyRead(c->db, c->argv[1]); 644 if (o != NULL && o->type != REDIS_HASH) { 645 addReply(c, shared.wrongtypeerr); 646 return; 647 } 648 649 addReplyMultiBulkLen(c, c->argc-2); 650 for (i = 2; i < c->argc; i++) { 651 addHashFieldToReply(c, o, c->argv[i]); 652 } 653 } 654 655 void hdelCommand(redisClient *c) { 656 robj *o; 657 int j, deleted = 0, keyremoved = 0; 658 659 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || 660 checkType(c,o,REDIS_HASH)) return; 661 662 for (j = 2; j < c->argc; j++) { 663 if (hashTypeDelete(o,c->argv[j])) { 664 deleted++; 665 if (hashTypeLength(o) == 0) { 666 dbDelete(c->db,c->argv[1]); 667 keyremoved = 1; 668 break; 669 } 670 } 671 } 672 if (deleted) { 673 signalModifiedKey(c->db,c->argv[1]); 674 notifyKeyspaceEvent("hdel",c->argv[1],c->db->id); 675 if (keyremoved) notifyKeyspaceEvent("del",c->argv[1],c->db->id); 676 server.dirty += deleted; 677 } 678 addReplyLongLong(c,deleted); 679 } 680 681 void hlenCommand(redisClient *c) { 682 robj *o; 683 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 684 checkType(c,o,REDIS_HASH)) return; 685 686 addReplyLongLong(c,hashTypeLength(o)); 687 } 688 689 static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) { 690 if (hi->encoding == REDIS_ENCODING_ZIPLIST) { 691 unsigned char *vstr = NULL; 692 unsigned int vlen = UINT_MAX; 693 long long vll = LLONG_MAX; 694 695 hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); 696 if (vstr) { 697 addReplyBulkCBuffer(c, vstr, vlen); 698 } else { 699 addReplyBulkLongLong(c, vll); 700 } 701 702 } else if (hi->encoding == REDIS_ENCODING_HT) { 703 robj *value; 704 705 hashTypeCurrentFromHashTable(hi, what, &value); 706 addReplyBulk(c, value); 707 708 } else { 709 redisPanic("Unknown hash encoding"); 710 } 711 } 712 713 void genericHgetallCommand(redisClient *c, int flags) { 714 robj *o; 715 hashTypeIterator *hi; 716 int multiplier = 0; 717 int length, count = 0; 718 719 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL 720 || checkType(c,o,REDIS_HASH)) return; 721 722 if (flags & REDIS_HASH_KEY) multiplier++; 723 if (flags & REDIS_HASH_VALUE) multiplier++; 724 725 length = hashTypeLength(o) * multiplier; 726 addReplyMultiBulkLen(c, length); 727 728 hi = hashTypeInitIterator(o); 729 while (hashTypeNext(hi) != REDIS_ERR) { 730 if (flags & REDIS_HASH_KEY) { 731 addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY); 732 count++; 733 } 734 if (flags & REDIS_HASH_VALUE) { 735 addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE); 736 count++; 737 } 738 } 739 740 hashTypeReleaseIterator(hi); 741 redisAssert(count == length); 742 } 743 744 void hkeysCommand(redisClient *c) { 745 genericHgetallCommand(c,REDIS_HASH_KEY); 746 } 747 748 void hvalsCommand(redisClient *c) { 749 genericHgetallCommand(c,REDIS_HASH_VALUE); 750 } 751 752 void hgetallCommand(redisClient *c) { 753 genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE); 754 } 755 756 void hexistsCommand(redisClient *c) { 757 robj *o; 758 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 759 checkType(c,o,REDIS_HASH)) return; 760 761 addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero); 762 } 763