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("del",c->argv[j],c->db->id); 244 server.dirty++; 245 deleted++; 246 } 247 } 248 addReplyLongLong(c,deleted); 249 } 250 251 void existsCommand(redisClient *c) { 252 expireIfNeeded(c->db,c->argv[1]); 253 if (dbExists(c->db,c->argv[1])) { 254 addReply(c, shared.cone); 255 } else { 256 addReply(c, shared.czero); 257 } 258 } 259 260 void selectCommand(redisClient *c) { 261 long id; 262 263 if (getLongFromObjectOrReply(c, c->argv[1], &id, 264 "invalid DB index") != REDIS_OK) 265 return; 266 267 if (server.cluster_enabled && id != 0) { 268 addReplyError(c,"SELECT is not allowed in cluster mode"); 269 return; 270 } 271 if (selectDb(c,id) == REDIS_ERR) { 272 addReplyError(c,"invalid DB index"); 273 } else { 274 addReply(c,shared.ok); 275 } 276 } 277 278 void randomkeyCommand(redisClient *c) { 279 robj *key; 280 281 if ((key = dbRandomKey(c->db)) == NULL) { 282 addReply(c,shared.nullbulk); 283 return; 284 } 285 286 addReplyBulk(c,key); 287 decrRefCount(key); 288 } 289 290 void keysCommand(redisClient *c) { 291 dictIterator *di; 292 dictEntry *de; 293 sds pattern = c->argv[1]->ptr; 294 int plen = sdslen(pattern), allkeys; 295 unsigned long numkeys = 0; 296 void *replylen = addDeferredMultiBulkLength(c); 297 298 di = dictGetSafeIterator(c->db->dict); 299 allkeys = (pattern[0] == '*' && pattern[1] == '\0'); 300 while((de = dictNext(di)) != NULL) { 301 sds key = dictGetKey(de); 302 robj *keyobj; 303 304 if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { 305 keyobj = createStringObject(key,sdslen(key)); 306 if (expireIfNeeded(c->db,keyobj) == 0) { 307 addReplyBulk(c,keyobj); 308 numkeys++; 309 } 310 decrRefCount(keyobj); 311 } 312 } 313 dictReleaseIterator(di); 314 setDeferredMultiBulkLength(c,replylen,numkeys); 315 } 316 317 void dbsizeCommand(redisClient *c) { 318 addReplyLongLong(c,dictSize(c->db->dict)); 319 } 320 321 void lastsaveCommand(redisClient *c) { 322 addReplyLongLong(c,server.lastsave); 323 } 324 325 void typeCommand(redisClient *c) { 326 robj *o; 327 char *type; 328 329 o = lookupKeyRead(c->db,c->argv[1]); 330 if (o == NULL) { 331 type = "none"; 332 } else { 333 switch(o->type) { 334 case REDIS_STRING: type = "string"; break; 335 case REDIS_LIST: type = "list"; break; 336 case REDIS_SET: type = "set"; break; 337 case REDIS_ZSET: type = "zset"; break; 338 case REDIS_HASH: type = "hash"; break; 339 default: type = "unknown"; break; 340 } 341 } 342 addReplyStatus(c,type); 343 } 344 345 void shutdownCommand(redisClient *c) { 346 int flags = 0; 347 348 if (c->argc > 2) { 349 addReply(c,shared.syntaxerr); 350 return; 351 } else if (c->argc == 2) { 352 if (!strcasecmp(c->argv[1]->ptr,"nosave")) { 353 flags |= REDIS_SHUTDOWN_NOSAVE; 354 } else if (!strcasecmp(c->argv[1]->ptr,"save")) { 355 flags |= REDIS_SHUTDOWN_SAVE; 356 } else { 357 addReply(c,shared.syntaxerr); 358 return; 359 } 360 } 361 if (prepareForShutdown(flags) == REDIS_OK) exit(0); 362 addReplyError(c,"Errors trying to SHUTDOWN. Check logs."); 363 } 364 365 void renameGenericCommand(redisClient *c, int nx) { 366 robj *o; 367 long long expire; 368 369 /* To use the same key as src and dst is probably an error */ 370 if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) { 371 addReply(c,shared.sameobjecterr); 372 return; 373 } 374 375 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) 376 return; 377 378 incrRefCount(o); 379 expire = getExpire(c->db,c->argv[1]); 380 if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { 381 if (nx) { 382 decrRefCount(o); 383 addReply(c,shared.czero); 384 return; 385 } 386 /* Overwrite: delete the old key before creating the new one 387 * with the same name. */ 388 dbDelete(c->db,c->argv[2]); 389 } 390 dbAdd(c->db,c->argv[2],o); 391 if (expire != -1) setExpire(c->db,c->argv[2],expire); 392 dbDelete(c->db,c->argv[1]); 393 signalModifiedKey(c->db,c->argv[1]); 394 signalModifiedKey(c->db,c->argv[2]); 395 notifyKeyspaceEvent("rename_from",c->argv[1],c->db->id); 396 notifyKeyspaceEvent("rename_to",c->argv[2],c->db->id); 397 server.dirty++; 398 addReply(c,nx ? shared.cone : shared.ok); 399 } 400 401 void renameCommand(redisClient *c) { 402 renameGenericCommand(c,0); 403 } 404 405 void renamenxCommand(redisClient *c) { 406 renameGenericCommand(c,1); 407 } 408 409 void moveCommand(redisClient *c) { 410 robj *o; 411 redisDb *src, *dst; 412 int srcid; 413 414 if (server.cluster_enabled) { 415 addReplyError(c,"MOVE is not allowed in cluster mode"); 416 return; 417 } 418 419 /* Obtain source and target DB pointers */ 420 src = c->db; 421 srcid = c->db->id; 422 if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { 423 addReply(c,shared.outofrangeerr); 424 return; 425 } 426 dst = c->db; 427 selectDb(c,srcid); /* Back to the source DB */ 428 429 /* If the user is moving using as target the same 430 * DB as the source DB it is probably an error. */ 431 if (src == dst) { 432 addReply(c,shared.sameobjecterr); 433 return; 434 } 435 436 /* Check if the element exists and get a reference */ 437 o = lookupKeyWrite(c->db,c->argv[1]); 438 if (!o) { 439 addReply(c,shared.czero); 440 return; 441 } 442 443 /* Return zero if the key already exists in the target DB */ 444 if (lookupKeyWrite(dst,c->argv[1]) != NULL) { 445 addReply(c,shared.czero); 446 return; 447 } 448 dbAdd(dst,c->argv[1],o); 449 incrRefCount(o); 450 451 /* OK! key moved, free the entry in the source DB */ 452 dbDelete(src,c->argv[1]); 453 server.dirty++; 454 addReply(c,shared.cone); 455 } 456 457 /*----------------------------------------------------------------------------- 458 * Expires API 459 *----------------------------------------------------------------------------*/ 460 461 int removeExpire(redisDb *db, robj *key) { 462 /* An expire may only be removed if there is a corresponding entry in the 463 * main dict. Otherwise, the key will never be freed. */ 464 redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); 465 return dictDelete(db->expires,key->ptr) == DICT_OK; 466 } 467 468 void setExpire(redisDb *db, robj *key, long long when) { 469 dictEntry *kde, *de; 470 471 /* Reuse the sds from the main dict in the expire dict */ 472 kde = dictFind(db->dict,key->ptr); 473 redisAssertWithInfo(NULL,key,kde != NULL); 474 de = dictReplaceRaw(db->expires,dictGetKey(kde)); 475 dictSetSignedIntegerVal(de,when); 476 } 477 478 /* Return the expire time of the specified key, or -1 if no expire 479 * is associated with this key (i.e. the key is non volatile) */ 480 long long getExpire(redisDb *db, robj *key) { 481 dictEntry *de; 482 483 /* No expire? return ASAP */ 484 if (dictSize(db->expires) == 0 || 485 (de = dictFind(db->expires,key->ptr)) == NULL) return -1; 486 487 /* The entry was found in the expire dict, this means it should also 488 * be present in the main dict (safety check). */ 489 redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); 490 return dictGetSignedIntegerVal(de); 491 } 492 493 /* Propagate expires into slaves and the AOF file. 494 * When a key expires in the master, a DEL operation for this key is sent 495 * to all the slaves and the AOF file if enabled. 496 * 497 * This way the key expiry is centralized in one place, and since both 498 * AOF and the master->slave link guarantee operation ordering, everything 499 * will be consistent even if we allow write operations against expiring 500 * keys. */ 501 void propagateExpire(redisDb *db, robj *key) { 502 robj *argv[2]; 503 504 argv[0] = shared.del; 505 argv[1] = key; 506 incrRefCount(argv[0]); 507 incrRefCount(argv[1]); 508 509 if (server.aof_state != REDIS_AOF_OFF) 510 feedAppendOnlyFile(server.delCommand,db->id,argv,2); 511 if (listLength(server.slaves)) 512 replicationFeedSlaves(server.slaves,db->id,argv,2); 513 514 decrRefCount(argv[0]); 515 decrRefCount(argv[1]); 516 } 517 518 int expireIfNeeded(redisDb *db, robj *key) { 519 long long when = getExpire(db,key); 520 521 if (when < 0) return 0; /* No expire for this key */ 522 523 /* Don't expire anything while loading. It will be done later. */ 524 if (server.loading) return 0; 525 526 /* If we are running in the context of a slave, return ASAP: 527 * the slave key expiration is controlled by the master that will 528 * send us synthesized DEL operations for expired keys. 529 * 530 * Still we try to return the right information to the caller, 531 * that is, 0 if we think the key should be still valid, 1 if 532 * we think the key is expired at this time. */ 533 if (server.masterhost != NULL) { 534 return mstime() > when; 535 } 536 537 /* Return when this key has not expired */ 538 if (mstime() <= when) return 0; 539 540 /* Delete the key */ 541 server.stat_expiredkeys++; 542 propagateExpire(db,key); 543 return dbDelete(db,key); 544 } 545 546 /*----------------------------------------------------------------------------- 547 * Expires Commands 548 *----------------------------------------------------------------------------*/ 549 550 /* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT 551 * and PEXPIREAT. Because the commad second argument may be relative or absolute 552 * the "basetime" argument is used to signal what the base time is (either 0 553 * for *AT variants of the command, or the current time for relative expires). 554 * 555 * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for 556 * the argv[2] parameter. The basetime is always specified in milliseconds. */ 557 void expireGenericCommand(redisClient *c, long long basetime, int unit) { 558 dictEntry *de; 559 robj *key = c->argv[1], *param = c->argv[2]; 560 long long when; /* unix time in milliseconds when the key will expire. */ 561 562 if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK) 563 return; 564 565 if (unit == UNIT_SECONDS) when *= 1000; 566 when += basetime; 567 568 de = dictFind(c->db->dict,key->ptr); 569 if (de == NULL) { 570 addReply(c,shared.czero); 571 return; 572 } 573 /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past 574 * should never be executed as a DEL when load the AOF or in the context 575 * of a slave instance. 576 * 577 * Instead we take the other branch of the IF statement setting an expire 578 * (possibly in the past) and wait for an explicit DEL from the master. */ 579 if (when <= mstime() && !server.loading && !server.masterhost) { 580 robj *aux; 581 582 redisAssertWithInfo(c,key,dbDelete(c->db,key)); 583 server.dirty++; 584 585 /* Replicate/AOF this as an explicit DEL. */ 586 aux = createStringObject("DEL",3); 587 rewriteClientCommandVector(c,2,aux,key); 588 decrRefCount(aux); 589 signalModifiedKey(c->db,key); 590 notifyKeyspaceEvent("del",key,c->db->id); 591 addReply(c, shared.cone); 592 return; 593 } else { 594 setExpire(c->db,key,when); 595 addReply(c,shared.cone); 596 signalModifiedKey(c->db,key); 597 notifyKeyspaceEvent("expire",key,c->db->id); 598 server.dirty++; 599 return; 600 } 601 } 602 603 void expireCommand(redisClient *c) { 604 expireGenericCommand(c,mstime(),UNIT_SECONDS); 605 } 606 607 void expireatCommand(redisClient *c) { 608 expireGenericCommand(c,0,UNIT_SECONDS); 609 } 610 611 void pexpireCommand(redisClient *c) { 612 expireGenericCommand(c,mstime(),UNIT_MILLISECONDS); 613 } 614 615 void pexpireatCommand(redisClient *c) { 616 expireGenericCommand(c,0,UNIT_MILLISECONDS); 617 } 618 619 void ttlGenericCommand(redisClient *c, int output_ms) { 620 long long expire, ttl = -1; 621 622 expire = getExpire(c->db,c->argv[1]); 623 /* If the key does not exist at all, return -2 */ 624 if (expire == -1 && lookupKeyRead(c->db,c->argv[1]) == NULL) { 625 addReplyLongLong(c,-2); 626 return; 627 } 628 /* The key exists. Return -1 if it has no expire, or the actual 629 * TTL value otherwise. */ 630 if (expire != -1) { 631 ttl = expire-mstime(); 632 if (ttl < 0) ttl = -1; 633 } 634 if (ttl == -1) { 635 addReplyLongLong(c,-1); 636 } else { 637 addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000)); 638 } 639 } 640 641 void ttlCommand(redisClient *c) { 642 ttlGenericCommand(c, 0); 643 } 644 645 void pttlCommand(redisClient *c) { 646 ttlGenericCommand(c, 1); 647 } 648 649 void persistCommand(redisClient *c) { 650 dictEntry *de; 651 652 de = dictFind(c->db->dict,c->argv[1]->ptr); 653 if (de == NULL) { 654 addReply(c,shared.czero); 655 } else { 656 if (removeExpire(c->db,c->argv[1])) { 657 addReply(c,shared.cone); 658 server.dirty++; 659 } else { 660 addReply(c,shared.czero); 661 } 662 } 663 } 664 665 /* ----------------------------------------------------------------------------- 666 * API to get key arguments from commands 667 * ---------------------------------------------------------------------------*/ 668 669 int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) { 670 int j, i = 0, last, *keys; 671 REDIS_NOTUSED(argv); 672 673 if (cmd->firstkey == 0) { 674 *numkeys = 0; 675 return NULL; 676 } 677 last = cmd->lastkey; 678 if (last < 0) last = argc+last; 679 keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1)); 680 for (j = cmd->firstkey; j <= last; j += cmd->keystep) { 681 redisAssert(j < argc); 682 keys[i++] = j; 683 } 684 *numkeys = i; 685 return keys; 686 } 687 688 int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 689 if (cmd->getkeys_proc) { 690 return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags); 691 } else { 692 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 693 } 694 } 695 696 void getKeysFreeResult(int *result) { 697 zfree(result); 698 } 699 700 int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 701 if (flags & REDIS_GETKEYS_PRELOAD) { 702 *numkeys = 0; 703 return NULL; 704 } else { 705 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 706 } 707 } 708 709 int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 710 if (flags & REDIS_GETKEYS_PRELOAD) { 711 int *keys = zmalloc(sizeof(int)); 712 *numkeys = 1; 713 keys[0] = 1; 714 return keys; 715 } else { 716 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 717 } 718 } 719 720 int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 721 int i, num, *keys; 722 REDIS_NOTUSED(cmd); 723 REDIS_NOTUSED(flags); 724 725 num = atoi(argv[2]->ptr); 726 /* Sanity check. Don't return any key if the command is going to 727 * reply with syntax error. */ 728 if (num > (argc-3)) { 729 *numkeys = 0; 730 return NULL; 731 } 732 keys = zmalloc(sizeof(int)*num); 733 for (i = 0; i < num; i++) keys[i] = 3+i; 734 *numkeys = num; 735 return keys; 736 } 737 738 /* Slot to Key API. This is used by Redis Cluster in order to obtain in 739 * a fast way a key that belongs to a specified hash slot. This is useful 740 * while rehashing the cluster. */ 741 void SlotToKeyAdd(robj *key) { 742 unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); 743 744 zslInsert(server.cluster.slots_to_keys,hashslot,key); 745 incrRefCount(key); 746 } 747 748 void SlotToKeyDel(robj *key) { 749 unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); 750 751 zslDelete(server.cluster.slots_to_keys,hashslot,key); 752 } 753 754 unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) { 755 zskiplistNode *n; 756 zrangespec range; 757 int j = 0; 758 759 range.min = range.max = hashslot; 760 range.minex = range.maxex = 0; 761 762 n = zslFirstInRange(server.cluster.slots_to_keys, range); 763 while(n && n->score == hashslot && count--) { 764 keys[j++] = n->obj; 765 n = n->level[0].forward; 766 } 767 return j; 768 } 769