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