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 return dbDelete(db,key); 547 } 548 549 /*----------------------------------------------------------------------------- 550 * Expires Commands 551 *----------------------------------------------------------------------------*/ 552 553 /* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT 554 * and PEXPIREAT. Because the commad second argument may be relative or absolute 555 * the "basetime" argument is used to signal what the base time is (either 0 556 * for *AT variants of the command, or the current time for relative expires). 557 * 558 * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for 559 * the argv[2] parameter. The basetime is always specified in milliseconds. */ 560 void expireGenericCommand(redisClient *c, long long basetime, int unit) { 561 dictEntry *de; 562 robj *key = c->argv[1], *param = c->argv[2]; 563 long long when; /* unix time in milliseconds when the key will expire. */ 564 565 if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK) 566 return; 567 568 if (unit == UNIT_SECONDS) when *= 1000; 569 when += basetime; 570 571 de = dictFind(c->db->dict,key->ptr); 572 if (de == NULL) { 573 addReply(c,shared.czero); 574 return; 575 } 576 /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past 577 * should never be executed as a DEL when load the AOF or in the context 578 * of a slave instance. 579 * 580 * Instead we take the other branch of the IF statement setting an expire 581 * (possibly in the past) and wait for an explicit DEL from the master. */ 582 if (when <= mstime() && !server.loading && !server.masterhost) { 583 robj *aux; 584 585 redisAssertWithInfo(c,key,dbDelete(c->db,key)); 586 server.dirty++; 587 588 /* Replicate/AOF this as an explicit DEL. */ 589 aux = createStringObject("DEL",3); 590 rewriteClientCommandVector(c,2,aux,key); 591 decrRefCount(aux); 592 signalModifiedKey(c->db,key); 593 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id); 594 addReply(c, shared.cone); 595 return; 596 } else { 597 setExpire(c->db,key,when); 598 addReply(c,shared.cone); 599 signalModifiedKey(c->db,key); 600 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"expire",key,c->db->id); 601 server.dirty++; 602 return; 603 } 604 } 605 606 void expireCommand(redisClient *c) { 607 expireGenericCommand(c,mstime(),UNIT_SECONDS); 608 } 609 610 void expireatCommand(redisClient *c) { 611 expireGenericCommand(c,0,UNIT_SECONDS); 612 } 613 614 void pexpireCommand(redisClient *c) { 615 expireGenericCommand(c,mstime(),UNIT_MILLISECONDS); 616 } 617 618 void pexpireatCommand(redisClient *c) { 619 expireGenericCommand(c,0,UNIT_MILLISECONDS); 620 } 621 622 void ttlGenericCommand(redisClient *c, int output_ms) { 623 long long expire, ttl = -1; 624 625 expire = getExpire(c->db,c->argv[1]); 626 /* If the key does not exist at all, return -2 */ 627 if (expire == -1 && lookupKeyRead(c->db,c->argv[1]) == NULL) { 628 addReplyLongLong(c,-2); 629 return; 630 } 631 /* The key exists. Return -1 if it has no expire, or the actual 632 * TTL value otherwise. */ 633 if (expire != -1) { 634 ttl = expire-mstime(); 635 if (ttl < 0) ttl = -1; 636 } 637 if (ttl == -1) { 638 addReplyLongLong(c,-1); 639 } else { 640 addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000)); 641 } 642 } 643 644 void ttlCommand(redisClient *c) { 645 ttlGenericCommand(c, 0); 646 } 647 648 void pttlCommand(redisClient *c) { 649 ttlGenericCommand(c, 1); 650 } 651 652 void persistCommand(redisClient *c) { 653 dictEntry *de; 654 655 de = dictFind(c->db->dict,c->argv[1]->ptr); 656 if (de == NULL) { 657 addReply(c,shared.czero); 658 } else { 659 if (removeExpire(c->db,c->argv[1])) { 660 addReply(c,shared.cone); 661 server.dirty++; 662 } else { 663 addReply(c,shared.czero); 664 } 665 } 666 } 667 668 /* ----------------------------------------------------------------------------- 669 * API to get key arguments from commands 670 * ---------------------------------------------------------------------------*/ 671 672 int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) { 673 int j, i = 0, last, *keys; 674 REDIS_NOTUSED(argv); 675 676 if (cmd->firstkey == 0) { 677 *numkeys = 0; 678 return NULL; 679 } 680 last = cmd->lastkey; 681 if (last < 0) last = argc+last; 682 keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1)); 683 for (j = cmd->firstkey; j <= last; j += cmd->keystep) { 684 redisAssert(j < argc); 685 keys[i++] = j; 686 } 687 *numkeys = i; 688 return keys; 689 } 690 691 int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 692 if (cmd->getkeys_proc) { 693 return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags); 694 } else { 695 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 696 } 697 } 698 699 void getKeysFreeResult(int *result) { 700 zfree(result); 701 } 702 703 int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 704 if (flags & REDIS_GETKEYS_PRELOAD) { 705 *numkeys = 0; 706 return NULL; 707 } else { 708 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 709 } 710 } 711 712 int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 713 if (flags & REDIS_GETKEYS_PRELOAD) { 714 int *keys = zmalloc(sizeof(int)); 715 *numkeys = 1; 716 keys[0] = 1; 717 return keys; 718 } else { 719 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 720 } 721 } 722 723 int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 724 int i, num, *keys; 725 REDIS_NOTUSED(cmd); 726 REDIS_NOTUSED(flags); 727 728 num = atoi(argv[2]->ptr); 729 /* Sanity check. Don't return any key if the command is going to 730 * reply with syntax error. */ 731 if (num > (argc-3)) { 732 *numkeys = 0; 733 return NULL; 734 } 735 keys = zmalloc(sizeof(int)*num); 736 for (i = 0; i < num; i++) keys[i] = 3+i; 737 *numkeys = num; 738 return keys; 739 } 740 741 /* Slot to Key API. This is used by Redis Cluster in order to obtain in 742 * a fast way a key that belongs to a specified hash slot. This is useful 743 * while rehashing the cluster. */ 744 void SlotToKeyAdd(robj *key) { 745 unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); 746 747 zslInsert(server.cluster.slots_to_keys,hashslot,key); 748 incrRefCount(key); 749 } 750 751 void SlotToKeyDel(robj *key) { 752 unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); 753 754 zslDelete(server.cluster.slots_to_keys,hashslot,key); 755 } 756 757 unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) { 758 zskiplistNode *n; 759 zrangespec range; 760 int j = 0; 761 762 range.min = range.max = hashslot; 763 range.minex = range.maxex = 0; 764 765 n = zslFirstInRange(server.cluster.slots_to_keys, range); 766 while(n && n->score == hashslot && count--) { 767 keys[j++] = n->obj; 768 n = n->level[0].forward; 769 } 770 return j; 771 } 772