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 32 #include <signal.h> 33 #include <ctype.h> 34 35 void slotToKeyAdd(robj *key); 36 void slotToKeyDel(robj *key); 37 void slotToKeyFlush(void); 38 39 /*----------------------------------------------------------------------------- 40 * C-level DB API 41 *----------------------------------------------------------------------------*/ 42 43 robj *lookupKey(redisDb *db, robj *key) { 44 dictEntry *de = dictFind(db->dict,key->ptr); 45 if (de) { 46 robj *val = dictGetVal(de); 47 48 /* Update the access time for the ageing algorithm. 49 * Don't do it if we have a saving child, as this will trigger 50 * a copy on write madness. */ 51 if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) 52 val->lru = server.lruclock; 53 return val; 54 } else { 55 return NULL; 56 } 57 } 58 59 robj *lookupKeyRead(redisDb *db, robj *key) { 60 robj *val; 61 62 expireIfNeeded(db,key); 63 val = lookupKey(db,key); 64 if (val == NULL) 65 server.stat_keyspace_misses++; 66 else 67 server.stat_keyspace_hits++; 68 return val; 69 } 70 71 robj *lookupKeyWrite(redisDb *db, robj *key) { 72 expireIfNeeded(db,key); 73 return lookupKey(db,key); 74 } 75 76 robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) { 77 robj *o = lookupKeyRead(c->db, key); 78 if (!o) addReply(c,reply); 79 return o; 80 } 81 82 robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) { 83 robj *o = lookupKeyWrite(c->db, key); 84 if (!o) addReply(c,reply); 85 return o; 86 } 87 88 /* Add the key to the DB. It's up to the caller to increment the reference 89 * counter of the value if needed. 90 * 91 * The program is aborted if the key already exists. */ 92 void dbAdd(redisDb *db, robj *key, robj *val) { 93 sds copy = sdsdup(key->ptr); 94 int retval = dictAdd(db->dict, copy, val); 95 96 redisAssertWithInfo(NULL,key,retval == REDIS_OK); 97 if (server.cluster_enabled) slotToKeyAdd(key); 98 } 99 100 /* Overwrite an existing key with a new value. Incrementing the reference 101 * count of the new value is up to the caller. 102 * This function does not modify the expire time of the existing key. 103 * 104 * The program is aborted if the key was not already present. */ 105 void dbOverwrite(redisDb *db, robj *key, robj *val) { 106 struct dictEntry *de = dictFind(db->dict,key->ptr); 107 108 redisAssertWithInfo(NULL,key,de != NULL); 109 dictReplace(db->dict, key->ptr, val); 110 } 111 112 /* High level Set operation. This function can be used in order to set 113 * a key, whatever it was existing or not, to a new object. 114 * 115 * 1) The ref count of the value object is incremented. 116 * 2) clients WATCHing for the destination key notified. 117 * 3) The expire time of the key is reset (the key is made persistent). */ 118 void setKey(redisDb *db, robj *key, robj *val) { 119 if (lookupKeyWrite(db,key) == NULL) { 120 dbAdd(db,key,val); 121 } else { 122 dbOverwrite(db,key,val); 123 } 124 incrRefCount(val); 125 removeExpire(db,key); 126 signalModifiedKey(db,key); 127 } 128 129 int dbExists(redisDb *db, robj *key) { 130 return dictFind(db->dict,key->ptr) != NULL; 131 } 132 133 /* Return a random key, in form of a Redis object. 134 * If there are no keys, NULL is returned. 135 * 136 * The function makes sure to return keys not already expired. */ 137 robj *dbRandomKey(redisDb *db) { 138 struct dictEntry *de; 139 140 while(1) { 141 sds key; 142 robj *keyobj; 143 144 de = dictGetRandomKey(db->dict); 145 if (de == NULL) return NULL; 146 147 key = dictGetKey(de); 148 keyobj = createStringObject(key,sdslen(key)); 149 if (dictFind(db->expires,key)) { 150 if (expireIfNeeded(db,keyobj)) { 151 decrRefCount(keyobj); 152 continue; /* search for another key. This expired. */ 153 } 154 } 155 return keyobj; 156 } 157 } 158 159 /* Delete a key, value, and associated expiration entry if any, from the DB */ 160 int dbDelete(redisDb *db, robj *key) { 161 /* Deleting an entry from the expires dict will not free the sds of 162 * the key, because it is shared with the main dictionary. */ 163 if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr); 164 if (dictDelete(db->dict,key->ptr) == DICT_OK) { 165 if (server.cluster_enabled) slotToKeyDel(key); 166 return 1; 167 } else { 168 return 0; 169 } 170 } 171 172 long long emptyDb() { 173 int j; 174 long long removed = 0; 175 176 for (j = 0; j < server.dbnum; j++) { 177 removed += dictSize(server.db[j].dict); 178 dictEmpty(server.db[j].dict); 179 dictEmpty(server.db[j].expires); 180 } 181 if (server.cluster_enabled) slotToKeyFlush(); 182 return removed; 183 } 184 185 int selectDb(redisClient *c, int id) { 186 if (id < 0 || id >= server.dbnum) 187 return REDIS_ERR; 188 c->db = &server.db[id]; 189 return REDIS_OK; 190 } 191 192 /*----------------------------------------------------------------------------- 193 * Hooks for key space changes. 194 * 195 * Every time a key in the database is modified the function 196 * signalModifiedKey() is called. 197 * 198 * Every time a DB is flushed the function signalFlushDb() is called. 199 *----------------------------------------------------------------------------*/ 200 201 void signalModifiedKey(redisDb *db, robj *key) { 202 touchWatchedKey(db,key); 203 } 204 205 void signalFlushedDb(int dbid) { 206 touchWatchedKeysOnFlush(dbid); 207 } 208 209 /*----------------------------------------------------------------------------- 210 * Type agnostic commands operating on the key space 211 *----------------------------------------------------------------------------*/ 212 213 void flushdbCommand(redisClient *c) { 214 server.dirty += dictSize(c->db->dict); 215 signalFlushedDb(c->db->id); 216 dictEmpty(c->db->dict); 217 dictEmpty(c->db->expires); 218 if (server.cluster_enabled) slotToKeyFlush(); 219 addReply(c,shared.ok); 220 } 221 222 void flushallCommand(redisClient *c) { 223 signalFlushedDb(-1); 224 server.dirty += emptyDb(); 225 addReply(c,shared.ok); 226 if (server.rdb_child_pid != -1) { 227 kill(server.rdb_child_pid,SIGUSR1); 228 rdbRemoveTempFile(server.rdb_child_pid); 229 } 230 if (server.saveparamslen > 0) { 231 /* Normally rdbSave() will reset dirty, but we don't want this here 232 * as otherwise FLUSHALL will not be replicated nor put into the AOF. */ 233 int saved_dirty = server.dirty; 234 rdbSave(server.rdb_filename); 235 server.dirty = saved_dirty; 236 } 237 server.dirty++; 238 } 239 240 void delCommand(redisClient *c) { 241 int deleted = 0, j; 242 243 for (j = 1; j < c->argc; j++) { 244 if (dbDelete(c->db,c->argv[j])) { 245 signalModifiedKey(c->db,c->argv[j]); 246 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC, 247 "del",c->argv[j],c->db->id); 248 server.dirty++; 249 deleted++; 250 } 251 } 252 addReplyLongLong(c,deleted); 253 } 254 255 void existsCommand(redisClient *c) { 256 expireIfNeeded(c->db,c->argv[1]); 257 if (dbExists(c->db,c->argv[1])) { 258 addReply(c, shared.cone); 259 } else { 260 addReply(c, shared.czero); 261 } 262 } 263 264 void selectCommand(redisClient *c) { 265 long id; 266 267 if (getLongFromObjectOrReply(c, c->argv[1], &id, 268 "invalid DB index") != REDIS_OK) 269 return; 270 271 if (server.cluster_enabled && id != 0) { 272 addReplyError(c,"SELECT is not allowed in cluster mode"); 273 return; 274 } 275 if (selectDb(c,id) == REDIS_ERR) { 276 addReplyError(c,"invalid DB index"); 277 } else { 278 addReply(c,shared.ok); 279 } 280 } 281 282 void randomkeyCommand(redisClient *c) { 283 robj *key; 284 285 if ((key = dbRandomKey(c->db)) == NULL) { 286 addReply(c,shared.nullbulk); 287 return; 288 } 289 290 addReplyBulk(c,key); 291 decrRefCount(key); 292 } 293 294 void keysCommand(redisClient *c) { 295 dictIterator *di; 296 dictEntry *de; 297 sds pattern = c->argv[1]->ptr; 298 int plen = sdslen(pattern), allkeys; 299 unsigned long numkeys = 0; 300 void *replylen = addDeferredMultiBulkLength(c); 301 302 di = dictGetSafeIterator(c->db->dict); 303 allkeys = (pattern[0] == '*' && pattern[1] == '\0'); 304 while((de = dictNext(di)) != NULL) { 305 sds key = dictGetKey(de); 306 robj *keyobj; 307 308 if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { 309 keyobj = createStringObject(key,sdslen(key)); 310 if (expireIfNeeded(c->db,keyobj) == 0) { 311 addReplyBulk(c,keyobj); 312 numkeys++; 313 } 314 decrRefCount(keyobj); 315 } 316 } 317 dictReleaseIterator(di); 318 setDeferredMultiBulkLength(c,replylen,numkeys); 319 } 320 321 void dbsizeCommand(redisClient *c) { 322 addReplyLongLong(c,dictSize(c->db->dict)); 323 } 324 325 void lastsaveCommand(redisClient *c) { 326 addReplyLongLong(c,server.lastsave); 327 } 328 329 void typeCommand(redisClient *c) { 330 robj *o; 331 char *type; 332 333 o = lookupKeyRead(c->db,c->argv[1]); 334 if (o == NULL) { 335 type = "none"; 336 } else { 337 switch(o->type) { 338 case REDIS_STRING: type = "string"; break; 339 case REDIS_LIST: type = "list"; break; 340 case REDIS_SET: type = "set"; break; 341 case REDIS_ZSET: type = "zset"; break; 342 case REDIS_HASH: type = "hash"; break; 343 default: type = "unknown"; break; 344 } 345 } 346 addReplyStatus(c,type); 347 } 348 349 void shutdownCommand(redisClient *c) { 350 int flags = 0; 351 352 if (c->argc > 2) { 353 addReply(c,shared.syntaxerr); 354 return; 355 } else if (c->argc == 2) { 356 if (!strcasecmp(c->argv[1]->ptr,"nosave")) { 357 flags |= REDIS_SHUTDOWN_NOSAVE; 358 } else if (!strcasecmp(c->argv[1]->ptr,"save")) { 359 flags |= REDIS_SHUTDOWN_SAVE; 360 } else { 361 addReply(c,shared.syntaxerr); 362 return; 363 } 364 } 365 if (prepareForShutdown(flags) == REDIS_OK) exit(0); 366 addReplyError(c,"Errors trying to SHUTDOWN. Check logs."); 367 } 368 369 void renameGenericCommand(redisClient *c, int nx) { 370 robj *o; 371 long long expire; 372 373 /* To use the same key as src and dst is probably an error */ 374 if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) { 375 addReply(c,shared.sameobjecterr); 376 return; 377 } 378 379 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) 380 return; 381 382 incrRefCount(o); 383 expire = getExpire(c->db,c->argv[1]); 384 if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { 385 if (nx) { 386 decrRefCount(o); 387 addReply(c,shared.czero); 388 return; 389 } 390 /* Overwrite: delete the old key before creating the new one 391 * with the same name. */ 392 dbDelete(c->db,c->argv[2]); 393 } 394 dbAdd(c->db,c->argv[2],o); 395 if (expire != -1) setExpire(c->db,c->argv[2],expire); 396 dbDelete(c->db,c->argv[1]); 397 signalModifiedKey(c->db,c->argv[1]); 398 signalModifiedKey(c->db,c->argv[2]); 399 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"rename_from", 400 c->argv[1],c->db->id); 401 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"rename_to", 402 c->argv[2],c->db->id); 403 server.dirty++; 404 addReply(c,nx ? shared.cone : shared.ok); 405 } 406 407 void renameCommand(redisClient *c) { 408 renameGenericCommand(c,0); 409 } 410 411 void renamenxCommand(redisClient *c) { 412 renameGenericCommand(c,1); 413 } 414 415 void moveCommand(redisClient *c) { 416 robj *o; 417 redisDb *src, *dst; 418 int srcid; 419 420 if (server.cluster_enabled) { 421 addReplyError(c,"MOVE is not allowed in cluster mode"); 422 return; 423 } 424 425 /* Obtain source and target DB pointers */ 426 src = c->db; 427 srcid = c->db->id; 428 if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { 429 addReply(c,shared.outofrangeerr); 430 return; 431 } 432 dst = c->db; 433 selectDb(c,srcid); /* Back to the source DB */ 434 435 /* If the user is moving using as target the same 436 * DB as the source DB it is probably an error. */ 437 if (src == dst) { 438 addReply(c,shared.sameobjecterr); 439 return; 440 } 441 442 /* Check if the element exists and get a reference */ 443 o = lookupKeyWrite(c->db,c->argv[1]); 444 if (!o) { 445 addReply(c,shared.czero); 446 return; 447 } 448 449 /* Return zero if the key already exists in the target DB */ 450 if (lookupKeyWrite(dst,c->argv[1]) != NULL) { 451 addReply(c,shared.czero); 452 return; 453 } 454 dbAdd(dst,c->argv[1],o); 455 incrRefCount(o); 456 457 /* OK! key moved, free the entry in the source DB */ 458 dbDelete(src,c->argv[1]); 459 server.dirty++; 460 addReply(c,shared.cone); 461 } 462 463 /*----------------------------------------------------------------------------- 464 * Expires API 465 *----------------------------------------------------------------------------*/ 466 467 int removeExpire(redisDb *db, robj *key) { 468 /* An expire may only be removed if there is a corresponding entry in the 469 * main dict. Otherwise, the key will never be freed. */ 470 redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); 471 return dictDelete(db->expires,key->ptr) == DICT_OK; 472 } 473 474 void setExpire(redisDb *db, robj *key, long long when) { 475 dictEntry *kde, *de; 476 477 /* Reuse the sds from the main dict in the expire dict */ 478 kde = dictFind(db->dict,key->ptr); 479 redisAssertWithInfo(NULL,key,kde != NULL); 480 de = dictReplaceRaw(db->expires,dictGetKey(kde)); 481 dictSetSignedIntegerVal(de,when); 482 } 483 484 /* Return the expire time of the specified key, or -1 if no expire 485 * is associated with this key (i.e. the key is non volatile) */ 486 long long getExpire(redisDb *db, robj *key) { 487 dictEntry *de; 488 489 /* No expire? return ASAP */ 490 if (dictSize(db->expires) == 0 || 491 (de = dictFind(db->expires,key->ptr)) == NULL) return -1; 492 493 /* The entry was found in the expire dict, this means it should also 494 * be present in the main dict (safety check). */ 495 redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); 496 return dictGetSignedIntegerVal(de); 497 } 498 499 /* Propagate expires into slaves and the AOF file. 500 * When a key expires in the master, a DEL operation for this key is sent 501 * to all the slaves and the AOF file if enabled. 502 * 503 * This way the key expiry is centralized in one place, and since both 504 * AOF and the master->slave link guarantee operation ordering, everything 505 * will be consistent even if we allow write operations against expiring 506 * keys. */ 507 void propagateExpire(redisDb *db, robj *key) { 508 robj *argv[2]; 509 510 argv[0] = shared.del; 511 argv[1] = key; 512 incrRefCount(argv[0]); 513 incrRefCount(argv[1]); 514 515 if (server.aof_state != REDIS_AOF_OFF) 516 feedAppendOnlyFile(server.delCommand,db->id,argv,2); 517 replicationFeedSlaves(server.slaves,db->id,argv,2); 518 519 decrRefCount(argv[0]); 520 decrRefCount(argv[1]); 521 } 522 523 int expireIfNeeded(redisDb *db, robj *key) { 524 long long when = getExpire(db,key); 525 526 if (when < 0) return 0; /* No expire for this key */ 527 528 /* Don't expire anything while loading. It will be done later. */ 529 if (server.loading) return 0; 530 531 /* If we are running in the context of a slave, return ASAP: 532 * the slave key expiration is controlled by the master that will 533 * send us synthesized DEL operations for expired keys. 534 * 535 * Still we try to return the right information to the caller, 536 * that is, 0 if we think the key should be still valid, 1 if 537 * we think the key is expired at this time. */ 538 if (server.masterhost != NULL) { 539 return mstime() > when; 540 } 541 542 /* Return when this key has not expired */ 543 if (mstime() <= when) return 0; 544 545 /* Delete the key */ 546 server.stat_expiredkeys++; 547 propagateExpire(db,key); 548 notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED, 549 "expired",key,db->id); 550 return dbDelete(db,key); 551 } 552 553 /*----------------------------------------------------------------------------- 554 * Expires Commands 555 *----------------------------------------------------------------------------*/ 556 557 /* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT 558 * and PEXPIREAT. Because the commad second argument may be relative or absolute 559 * the "basetime" argument is used to signal what the base time is (either 0 560 * for *AT variants of the command, or the current time for relative expires). 561 * 562 * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for 563 * the argv[2] parameter. The basetime is always specified in milliseconds. */ 564 void expireGenericCommand(redisClient *c, long long basetime, int unit) { 565 robj *key = c->argv[1], *param = c->argv[2]; 566 long long when; /* unix time in milliseconds when the key will expire. */ 567 568 if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK) 569 return; 570 571 if (unit == UNIT_SECONDS) when *= 1000; 572 when += basetime; 573 574 /* No key, return zero. */ 575 if (lookupKeyRead(c->db,key) == NULL) { 576 addReply(c,shared.czero); 577 return; 578 } 579 580 /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past 581 * should never be executed as a DEL when load the AOF or in the context 582 * of a slave instance. 583 * 584 * Instead we take the other branch of the IF statement setting an expire 585 * (possibly in the past) and wait for an explicit DEL from the master. */ 586 if (when <= mstime() && !server.loading && !server.masterhost) { 587 robj *aux; 588 589 redisAssertWithInfo(c,key,dbDelete(c->db,key)); 590 server.dirty++; 591 592 /* Replicate/AOF this as an explicit DEL. */ 593 aux = createStringObject("DEL",3); 594 rewriteClientCommandVector(c,2,aux,key); 595 decrRefCount(aux); 596 signalModifiedKey(c->db,key); 597 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id); 598 addReply(c, shared.cone); 599 return; 600 } else { 601 setExpire(c->db,key,when); 602 addReply(c,shared.cone); 603 signalModifiedKey(c->db,key); 604 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"expire",key,c->db->id); 605 server.dirty++; 606 return; 607 } 608 } 609 610 void expireCommand(redisClient *c) { 611 expireGenericCommand(c,mstime(),UNIT_SECONDS); 612 } 613 614 void expireatCommand(redisClient *c) { 615 expireGenericCommand(c,0,UNIT_SECONDS); 616 } 617 618 void pexpireCommand(redisClient *c) { 619 expireGenericCommand(c,mstime(),UNIT_MILLISECONDS); 620 } 621 622 void pexpireatCommand(redisClient *c) { 623 expireGenericCommand(c,0,UNIT_MILLISECONDS); 624 } 625 626 void ttlGenericCommand(redisClient *c, int output_ms) { 627 long long expire, ttl = -1; 628 629 /* If the key does not exist at all, return -2 */ 630 if (lookupKeyRead(c->db,c->argv[1]) == NULL) { 631 addReplyLongLong(c,-2); 632 return; 633 } 634 /* The key exists. Return -1 if it has no expire, or the actual 635 * TTL value otherwise. */ 636 expire = getExpire(c->db,c->argv[1]); 637 if (expire != -1) { 638 ttl = expire-mstime(); 639 if (ttl < 0) ttl = 0; 640 } 641 if (ttl == -1) { 642 addReplyLongLong(c,-1); 643 } else { 644 addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000)); 645 } 646 } 647 648 void ttlCommand(redisClient *c) { 649 ttlGenericCommand(c, 0); 650 } 651 652 void pttlCommand(redisClient *c) { 653 ttlGenericCommand(c, 1); 654 } 655 656 void persistCommand(redisClient *c) { 657 dictEntry *de; 658 659 de = dictFind(c->db->dict,c->argv[1]->ptr); 660 if (de == NULL) { 661 addReply(c,shared.czero); 662 } else { 663 if (removeExpire(c->db,c->argv[1])) { 664 addReply(c,shared.cone); 665 server.dirty++; 666 } else { 667 addReply(c,shared.czero); 668 } 669 } 670 } 671 672 /* ----------------------------------------------------------------------------- 673 * API to get key arguments from commands 674 * ---------------------------------------------------------------------------*/ 675 676 int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) { 677 int j, i = 0, last, *keys; 678 REDIS_NOTUSED(argv); 679 680 if (cmd->firstkey == 0) { 681 *numkeys = 0; 682 return NULL; 683 } 684 last = cmd->lastkey; 685 if (last < 0) last = argc+last; 686 keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1)); 687 for (j = cmd->firstkey; j <= last; j += cmd->keystep) { 688 redisAssert(j < argc); 689 keys[i++] = j; 690 } 691 *numkeys = i; 692 return keys; 693 } 694 695 int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 696 if (cmd->getkeys_proc) { 697 return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags); 698 } else { 699 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 700 } 701 } 702 703 void getKeysFreeResult(int *result) { 704 zfree(result); 705 } 706 707 int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 708 if (flags & REDIS_GETKEYS_PRELOAD) { 709 *numkeys = 0; 710 return NULL; 711 } else { 712 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 713 } 714 } 715 716 int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 717 if (flags & REDIS_GETKEYS_PRELOAD) { 718 int *keys = zmalloc(sizeof(int)); 719 *numkeys = 1; 720 keys[0] = 1; 721 return keys; 722 } else { 723 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 724 } 725 } 726 727 int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 728 int i, num, *keys; 729 REDIS_NOTUSED(cmd); 730 REDIS_NOTUSED(flags); 731 732 num = atoi(argv[2]->ptr); 733 /* Sanity check. Don't return any key if the command is going to 734 * reply with syntax error. */ 735 if (num > (argc-3)) { 736 *numkeys = 0; 737 return NULL; 738 } 739 keys = zmalloc(sizeof(int)*num); 740 for (i = 0; i < num; i++) keys[i] = 3+i; 741 *numkeys = num; 742 return keys; 743 } 744 745 /* Slot to Key API. This is used by Redis Cluster in order to obtain in 746 * a fast way a key that belongs to a specified hash slot. This is useful 747 * while rehashing the cluster. */ 748 void slotToKeyAdd(robj *key) { 749 unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); 750 751 zslInsert(server.cluster->slots_to_keys,hashslot,key); 752 incrRefCount(key); 753 } 754 755 void slotToKeyDel(robj *key) { 756 unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); 757 758 zslDelete(server.cluster->slots_to_keys,hashslot,key); 759 } 760 761 void slotToKeyFlush(void) { 762 zslFree(server.cluster->slots_to_keys); 763 server.cluster->slots_to_keys = zslCreate(); 764 } 765 766 unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) { 767 zskiplistNode *n; 768 zrangespec range; 769 int j = 0; 770 771 range.min = range.max = hashslot; 772 range.minex = range.maxex = 0; 773 774 n = zslFirstInRange(server.cluster->slots_to_keys, range); 775 while(n && n->score == hashslot && count--) { 776 keys[j++] = n->obj; 777 n = n->level[0].forward; 778 } 779 return j; 780 } 781 782 unsigned int countKeysInSlot(unsigned int hashslot) { 783 zskiplist *zsl = server.cluster->slots_to_keys; 784 zskiplistNode *zn; 785 zrangespec range; 786 int rank, count = 0; 787 788 range.min = range.max = hashslot; 789 range.minex = range.maxex = 0; 790 791 /* Find first element in range */ 792 zn = zslFirstInRange(zsl, range); 793 794 /* Use rank of first element, if any, to determine preliminary count */ 795 if (zn != NULL) { 796 rank = zslGetRank(zsl, zn->score, zn->obj); 797 count = (zsl->length - (rank - 1)); 798 799 /* Find last element in range */ 800 zn = zslLastInRange(zsl, range); 801 802 /* Use rank of last element, if any, to determine the actual count */ 803 if (zn != NULL) { 804 rank = zslGetRank(zsl, zn->score, zn->obj); 805 count -= (zsl->length - rank); 806 } 807 } 808 return count; 809 } 810