]>
git.saurik.com Git - redis.git/blob - src/db.c
067a024716b69091c3edbef2848bb3bf05d156e9
   2  * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com> 
   5  * Redistribution and use in source and binary forms, with or without 
   6  * modification, are permitted provided that the following conditions are met: 
   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. 
  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. 
  35 void SlotToKeyAdd(robj 
*key
); 
  36 void SlotToKeyDel(robj 
*key
); 
  38 /*----------------------------------------------------------------------------- 
  40  *----------------------------------------------------------------------------*/ 
  42 robj 
*lookupKey(redisDb 
*db
, robj 
*key
) { 
  43     dictEntry 
*de 
= dictFind(db
->dict
,key
->ptr
); 
  45         robj 
*val 
= dictGetVal(de
); 
  47         /* Update the access time for the aging 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
; 
  54         return recover(db
, key
); 
  58 robj 
*lookupKeyRead(redisDb 
*db
, robj 
*key
) { 
  61     expireIfNeeded(db
,key
); 
  62     val 
= lookupKey(db
,key
); 
  64         server
.stat_keyspace_misses
++; 
  66         server
.stat_keyspace_hits
++; 
  70 robj 
*lookupKeyWrite(redisDb 
*db
, robj 
*key
) { 
  71     expireIfNeeded(db
,key
); 
  72     robj 
*val 
= lookupKey(db
,key
); 
  73     if (val
) val
->archived 
= 0; 
  77 robj 
*lookupKeyReadOrReply(redisClient 
*c
, robj 
*key
, robj 
*reply
) { 
  78     robj 
*o 
= lookupKeyRead(c
->db
, key
); 
  79     if (!o
) addReply(c
,reply
); 
  83 robj 
*lookupKeyWriteOrReply(redisClient 
*c
, robj 
*key
, robj 
*reply
) { 
  84     robj 
*o 
= lookupKeyWrite(c
->db
, key
); 
  85     if (!o
) addReply(c
,reply
); 
  89 /* Add the key to the DB. It's up to the caller to increment the reference 
  90  * counte of the value if needed. 
  92  * The program is aborted if the key already exists. */ 
  93 void dbAdd(redisDb 
*db
, robj 
*key
, robj 
*val
) { 
  94     sds copy 
= sdsdup(key
->ptr
); 
  95     int retval 
= dictAdd(db
->dict
, copy
, val
); 
  98     redisAssertWithInfo(NULL
,key
,retval 
== REDIS_OK
); 
 101 /* Overwrite an existing key with a new value. Incrementing the reference 
 102  * count of the new value is up to the caller. 
 103  * This function does not modify the expire time of the existing key. 
 105  * The program is aborted if the key was not already present. */ 
 106 void dbOverwrite(redisDb 
*db
, robj 
*key
, robj 
*val
) { 
 107     struct dictEntry 
*de 
= dictFind(db
->dict
,key
->ptr
); 
 109     redisAssertWithInfo(NULL
,key
,de 
!= NULL
); 
 110     dictReplace(db
->dict
, key
->ptr
, val
); 
 113 /* High level Set operation. This function can be used in order to set 
 114  * a key, whatever it was existing or not, to a new object. 
 116  * 1) The ref count of the value object is incremented. 
 117  * 2) clients WATCHing for the destination key notified. 
 118  * 3) The expire time of the key is reset (the key is made persistent). */ 
 119 void setKey(redisDb 
*db
, robj 
*key
, robj 
*val
) { 
 120     if (lookupKeyWrite(db
,key
) == NULL
) { 
 123         dbOverwrite(db
,key
,val
); 
 126     removeExpire(db
,key
); 
 127     signalModifiedKey(db
,key
); 
 130 int dbExists(redisDb 
*db
, robj 
*key
) { 
 131     if (dictFind(db
->dict
,key
->ptr
) != NULL
) 
 133     if (recover(db
, key
) != NULL
) 
 138 /* Return a random key, in form of a Redis object. 
 139  * If there are no keys, NULL is returned. 
 141  * The function makes sure to return keys not already expired. */ 
 142 robj 
*dbRandomKey(redisDb 
*db
) { 
 143     struct dictEntry 
*de
; 
 149         de 
= dictGetRandomKey(db
->dict
); 
 150         if (de 
== NULL
) return NULL
; 
 152         key 
= dictGetKey(de
); 
 153         keyobj 
= createStringObject(key
,sdslen(key
)); 
 154         if (dictFind(db
->expires
,key
)) { 
 155             if (expireIfNeeded(db
,keyobj
)) { 
 156                 decrRefCount(keyobj
); 
 157                 continue; /* search for another key. This expired. */ 
 164 /* Delete a key, value, and associated expiration entry if any, from the DB */ 
 165 int dbDeleteSoft(redisDb 
*db
, robj 
*key
) { 
 166     /* Deleting an entry from the expires dict will not free the sds of 
 167      * the key, because it is shared with the main dictionary. */ 
 168     if (dictSize(db
->expires
) > 0) dictDelete(db
->expires
,key
->ptr
); 
 169     if (dictDelete(db
->dict
,key
->ptr
) == DICT_OK
) { 
 176 /* Delete a key, value, and associated expiration entry if any, even archived */ 
 177 int dbDelete(redisDb 
*db
, robj 
*key
) { 
 179     return dbDeleteSoft(db
, key
); 
 182 long long emptyDb() { 
 184     long long removed 
= 0; 
 186     for (j 
= 0; j 
< server
.dbnum
; j
++) { 
 187         removed 
+= dictSize(server
.db
[j
].dict
); 
 188         dictEmpty(server
.db
[j
].dict
); 
 189         dictEmpty(server
.db
[j
].expires
); 
 194 int selectDb(redisClient 
*c
, int id
) { 
 195     if (id 
< 0 || id 
>= server
.dbnum
) 
 197     c
->db 
= &server
.db
[id
]; 
 201 /*----------------------------------------------------------------------------- 
 202  * Hooks for key space changes. 
 204  * Every time a key in the database is modified the function 
 205  * signalModifiedKey() is called. 
 207  * Every time a DB is flushed the function signalFlushDb() is called. 
 208  *----------------------------------------------------------------------------*/ 
 210 void signalModifiedKey(redisDb 
*db
, robj 
*key
) { 
 211     touchWatchedKey(db
,key
); 
 214 void signalFlushedDb(int dbid
) { 
 215     touchWatchedKeysOnFlush(dbid
); 
 218 /*----------------------------------------------------------------------------- 
 219  * Type agnostic commands operating on the key space 
 220  *----------------------------------------------------------------------------*/ 
 222 void flushdbCommand(redisClient 
*c
) { 
 223     server
.dirty 
+= dictSize(c
->db
->dict
); 
 224     signalFlushedDb(c
->db
->id
); 
 225     dictEmpty(c
->db
->dict
); 
 226     dictEmpty(c
->db
->expires
); 
 227     addReply(c
,shared
.ok
); 
 230 void flushallCommand(redisClient 
*c
) { 
 232     server
.dirty 
+= emptyDb(); 
 233     addReply(c
,shared
.ok
); 
 234     if (server
.rdb_child_pid 
!= -1) { 
 235         kill(server
.rdb_child_pid
,SIGKILL
); 
 236         rdbRemoveTempFile(server
.rdb_child_pid
); 
 238     if (server
.saveparamslen 
> 0) { 
 239         /* Normally rdbSave() will reset dirty, but we don't want this here 
 240          * as otherwise FLUSHALL will not be replicated nor put into the AOF. */ 
 241         int saved_dirty 
= server
.dirty
; 
 242         rdbSave(server
.rdb_filename
); 
 243         server
.dirty 
= saved_dirty
; 
 248 void delCommand(redisClient 
*c
) { 
 251     for (j 
= 1; j 
< c
->argc
; j
++) { 
 252         if (dbDelete(c
->db
,c
->argv
[j
])) { 
 253             signalModifiedKey(c
->db
,c
->argv
[j
]); 
 258     addReplyLongLong(c
,deleted
); 
 261 void existsCommand(redisClient 
*c
) { 
 262     expireIfNeeded(c
->db
,c
->argv
[1]); 
 263     if (dbExists(c
->db
,c
->argv
[1])) { 
 264         addReply(c
, shared
.cone
); 
 266         addReply(c
, shared
.czero
); 
 270 void selectCommand(redisClient 
*c
) { 
 273     if (getLongFromObjectOrReply(c
, c
->argv
[1], &id
, 
 274         "invalid DB index") != REDIS_OK
) 
 277     if (selectDb(c
,id
) == REDIS_ERR
) { 
 278         addReplyError(c
,"invalid DB index"); 
 280         addReply(c
,shared
.ok
); 
 284 void randomkeyCommand(redisClient 
*c
) { 
 287     if ((key 
= dbRandomKey(c
->db
)) == NULL
) { 
 288         addReply(c
,shared
.nullbulk
); 
 296 void keysCommand(redisClient 
*c
) { 
 299     sds pattern 
= c
->argv
[1]->ptr
; 
 300     int plen 
= sdslen(pattern
), allkeys
; 
 301     unsigned long numkeys 
= 0; 
 302     void *replylen 
= addDeferredMultiBulkLength(c
); 
 304     di 
= dictGetSafeIterator(c
->db
->dict
); 
 305     allkeys 
= (pattern
[0] == '*' && pattern
[1] == '\0'); 
 306     while((de 
= dictNext(di
)) != NULL
) { 
 307         sds key 
= dictGetKey(de
); 
 310         if (allkeys 
|| stringmatchlen(pattern
,plen
,key
,sdslen(key
),0)) { 
 311             keyobj 
= createStringObject(key
,sdslen(key
)); 
 312             if (expireIfNeeded(c
->db
,keyobj
) == 0) { 
 313                 addReplyBulk(c
,keyobj
); 
 316             decrRefCount(keyobj
); 
 319     dictReleaseIterator(di
); 
 320     setDeferredMultiBulkLength(c
,replylen
,numkeys
); 
 323 void dbsizeCommand(redisClient 
*c
) { 
 324     addReplyLongLong(c
,dictSize(c
->db
->dict
)); 
 327 void lastsaveCommand(redisClient 
*c
) { 
 328     addReplyLongLong(c
,server
.lastsave
); 
 331 void typeCommand(redisClient 
*c
) { 
 335     o 
= lookupKeyRead(c
->db
,c
->argv
[1]); 
 340         case REDIS_STRING
: type 
= "string"; break; 
 341         case REDIS_LIST
: type 
= "list"; break; 
 342         case REDIS_SET
: type 
= "set"; break; 
 343         case REDIS_ZSET
: type 
= "zset"; break; 
 344         case REDIS_HASH
: type 
= "hash"; break; 
 345         default: type 
= "unknown"; break; 
 348     addReplyStatus(c
,type
); 
 351 void shutdownCommand(redisClient 
*c
) { 
 355         addReply(c
,shared
.syntaxerr
); 
 357     } else if (c
->argc 
== 2) { 
 358         if (!strcasecmp(c
->argv
[1]->ptr
,"nosave")) { 
 359             flags 
|= REDIS_SHUTDOWN_NOSAVE
; 
 360         } else if (!strcasecmp(c
->argv
[1]->ptr
,"save")) { 
 361             flags 
|= REDIS_SHUTDOWN_SAVE
; 
 363             addReply(c
,shared
.syntaxerr
); 
 367     if (prepareForShutdown(flags
) == REDIS_OK
) exit(0); 
 368     addReplyError(c
,"Errors trying to SHUTDOWN. Check logs."); 
 371 void renameGenericCommand(redisClient 
*c
, int nx
) { 
 375     /* To use the same key as src and dst is probably an error */ 
 376     if (sdscmp(c
->argv
[1]->ptr
,c
->argv
[2]->ptr
) == 0) { 
 377         addReply(c
,shared
.sameobjecterr
); 
 381     if ((o 
= lookupKeyWriteOrReply(c
,c
->argv
[1],shared
.nokeyerr
)) == NULL
) 
 385     expire 
= getExpire(c
->db
,c
->argv
[1]); 
 386     if (lookupKeyWrite(c
->db
,c
->argv
[2]) != NULL
) { 
 389             addReply(c
,shared
.czero
); 
 392         /* Overwrite: delete the old key before creating the new one with the same name. */ 
 393         dbDelete(c
->db
,c
->argv
[2]); 
 395     dbAdd(c
->db
,c
->argv
[2],o
); 
 396     if (expire 
!= -1) setExpire(c
->db
,c
->argv
[2],expire
); 
 397     dbDelete(c
->db
,c
->argv
[1]); 
 398     signalModifiedKey(c
->db
,c
->argv
[1]); 
 399     signalModifiedKey(c
->db
,c
->argv
[2]); 
 401     addReply(c
,nx 
? shared
.cone 
: shared
.ok
); 
 404 void renameCommand(redisClient 
*c
) { 
 405     renameGenericCommand(c
,0); 
 408 void renamenxCommand(redisClient 
*c
) { 
 409     renameGenericCommand(c
,1); 
 412 void moveCommand(redisClient 
*c
) { 
 417     /* Obtain source and target DB pointers */ 
 420     if (selectDb(c
,atoi(c
->argv
[2]->ptr
)) == REDIS_ERR
) { 
 421         addReply(c
,shared
.outofrangeerr
); 
 425     selectDb(c
,srcid
); /* Back to the source DB */ 
 427     /* If the user is moving using as target the same 
 428      * DB as the source DB it is probably an error. */ 
 430         addReply(c
,shared
.sameobjecterr
); 
 434     /* Check if the element exists and get a reference */ 
 435     o 
= lookupKeyWrite(c
->db
,c
->argv
[1]); 
 437         addReply(c
,shared
.czero
); 
 441     /* Return zero if the key already exists in the target DB */ 
 442     if (lookupKeyWrite(dst
,c
->argv
[1]) != NULL
) { 
 443         addReply(c
,shared
.czero
); 
 446     dbAdd(dst
,c
->argv
[1],o
); 
 449     /* OK! key moved, free the entry in the source DB */ 
 450     dbDelete(src
,c
->argv
[1]); 
 452     addReply(c
,shared
.cone
); 
 455 /*----------------------------------------------------------------------------- 
 457  *----------------------------------------------------------------------------*/ 
 459 int removeExpire(redisDb 
*db
, robj 
*key
) { 
 460     /* An expire may only be removed if there is a corresponding entry in the 
 461      * main dict. Otherwise, the key will never be freed. */ 
 462     redisAssertWithInfo(NULL
,key
,dictFind(db
->dict
,key
->ptr
) != NULL
); 
 463     return dictDelete(db
->expires
,key
->ptr
) == DICT_OK
; 
 466 void setExpire(redisDb 
*db
, robj 
*key
, long long when
) { 
 469     /* Reuse the sds from the main dict in the expire dict */ 
 470     kde 
= dictFind(db
->dict
,key
->ptr
); 
 471     redisAssertWithInfo(NULL
,key
,kde 
!= NULL
); 
 472     de 
= dictReplaceRaw(db
->expires
,dictGetKey(kde
)); 
 473     dictSetSignedIntegerVal(de
,when
); 
 476 /* Return the expire time of the specified key, or -1 if no expire 
 477  * is associated with this key (i.e. the key is non volatile) */ 
 478 long long getExpire(redisDb 
*db
, robj 
*key
) { 
 481     /* No expire? return ASAP */ 
 482     if (dictSize(db
->expires
) == 0 || 
 483        (de 
= dictFind(db
->expires
,key
->ptr
)) == NULL
) return -1; 
 485     /* The entry was found in the expire dict, this means it should also 
 486      * be present in the main dict (safety check). */ 
 487     redisAssertWithInfo(NULL
,key
,dictFind(db
->dict
,key
->ptr
) != NULL
); 
 488     return dictGetSignedIntegerVal(de
); 
 491 /* Propagate expires into slaves and the AOF file. 
 492  * When a key expires in the master, a DEL operation for this key is sent 
 493  * to all the slaves and the AOF file if enabled. 
 495  * This way the key expiry is centralized in one place, and since both 
 496  * AOF and the master->slave link guarantee operation ordering, everything 
 497  * will be consistent even if we allow write operations against expiring 
 499 void propagateExpire(redisDb 
*db
, robj 
*key
) { 
 502     argv
[0] = shared
.del
; 
 504     incrRefCount(argv
[0]); 
 505     incrRefCount(argv
[1]); 
 507     if (server
.aof_state 
!= REDIS_AOF_OFF
) 
 508         feedAppendOnlyFile(server
.delCommand
,db
->id
,argv
,2); 
 509     if (listLength(server
.slaves
)) 
 510         replicationFeedSlaves(server
.slaves
,db
->id
,argv
,2); 
 512     decrRefCount(argv
[0]); 
 513     decrRefCount(argv
[1]); 
 516 int expireIfNeeded(redisDb 
*db
, robj 
*key
) { 
 517     long long when 
= getExpire(db
,key
); 
 519     if (when 
< 0) return 0; /* No expire for this key */ 
 521     /* Don't expire anything while loading. It will be done later. */ 
 522     if (server
.loading
) return 0; 
 524     /* If we are running in the context of a slave, return ASAP: 
 525      * the slave key expiration is controlled by the master that will 
 526      * send us synthesized DEL operations for expired keys. 
 528      * Still we try to return the right information to the caller,  
 529      * that is, 0 if we think the key should be still valid, 1 if 
 530      * we think the key is expired at this time. */ 
 531     if (server
.masterhost 
!= NULL
) { 
 532         return mstime() > when
; 
 535     /* Return when this key has not expired */ 
 536     if (mstime() <= when
) return 0; 
 539     server
.stat_expiredkeys
++; 
 540     propagateExpire(db
,key
); 
 541     return dbDelete(db
,key
); 
 544 /*----------------------------------------------------------------------------- 
 546  *----------------------------------------------------------------------------*/ 
 548 /* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT 
 549  * and PEXPIREAT. Because the commad second argument may be relative or absolute 
 550  * the "basetime" argument is used to signal what the base time is (either 0 
 551  * for *AT variants of the command, or the current time for relative expires). 
 553  * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for 
 554  * the argv[2] parameter. The basetime is always specified in milliesconds. */ 
 555 void expireGenericCommand(redisClient 
*c
, long long basetime
, int unit
) { 
 557     robj 
*key 
= c
->argv
[1], *param 
= c
->argv
[2]; 
 558     long long when
; /* unix time in milliseconds when the key will expire. */ 
 560     if (getLongLongFromObjectOrReply(c
, param
, &when
, NULL
) != REDIS_OK
) 
 563     if (unit 
== UNIT_SECONDS
) when 
*= 1000; 
 566     de 
= dictFind(c
->db
->dict
,key
->ptr
); 
 568         addReply(c
,shared
.czero
); 
 571     /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past 
 572      * should never be executed as a DEL when load the AOF or in the context 
 573      * of a slave instance. 
 575      * Instead we take the other branch of the IF statement setting an expire 
 576      * (possibly in the past) and wait for an explicit DEL from the master. */ 
 577     if (when 
<= mstime() && !server
.loading 
&& !server
.masterhost
) { 
 580         redisAssertWithInfo(c
,key
,dbDelete(c
->db
,key
)); 
 583         /* Replicate/AOF this as an explicit DEL. */ 
 584         aux 
= createStringObject("DEL",3); 
 585         rewriteClientCommandVector(c
,2,aux
,key
); 
 587         signalModifiedKey(c
->db
,key
); 
 588         addReply(c
, shared
.cone
); 
 591         setExpire(c
->db
,key
,when
); 
 592         addReply(c
,shared
.cone
); 
 593         signalModifiedKey(c
->db
,key
); 
 599 void expireCommand(redisClient 
*c
) { 
 600     expireGenericCommand(c
,mstime(),UNIT_SECONDS
); 
 603 void expireatCommand(redisClient 
*c
) { 
 604     expireGenericCommand(c
,0,UNIT_SECONDS
); 
 607 void pexpireCommand(redisClient 
*c
) { 
 608     expireGenericCommand(c
,mstime(),UNIT_MILLISECONDS
); 
 611 void pexpireatCommand(redisClient 
*c
) { 
 612     expireGenericCommand(c
,0,UNIT_MILLISECONDS
); 
 615 void ttlGenericCommand(redisClient 
*c
, int output_ms
) { 
 616     long long expire
, ttl 
= -1; 
 618     expire 
= getExpire(c
->db
,c
->argv
[1]); 
 620         ttl 
= expire
-mstime(); 
 621         if (ttl 
< 0) ttl 
= -1; 
 624         addReplyLongLong(c
,-1); 
 626         addReplyLongLong(c
,output_ms 
? ttl 
: ((ttl
+500)/1000)); 
 630 void ttlCommand(redisClient 
*c
) { 
 631     ttlGenericCommand(c
, 0); 
 634 void pttlCommand(redisClient 
*c
) { 
 635     ttlGenericCommand(c
, 1); 
 638 void persistCommand(redisClient 
*c
) { 
 641     de 
= dictFind(c
->db
->dict
,c
->argv
[1]->ptr
); 
 643         addReply(c
,shared
.czero
); 
 645         if (removeExpire(c
->db
,c
->argv
[1])) { 
 646             addReply(c
,shared
.cone
); 
 649             addReply(c
,shared
.czero
); 
 654 /* ----------------------------------------------------------------------------- 
 655  * API to get key arguments from commands 
 656  * ---------------------------------------------------------------------------*/ 
 658 int *getKeysUsingCommandTable(struct redisCommand 
*cmd
,robj 
**argv
, int argc
, int *numkeys
) { 
 659     int j
, i 
= 0, last
, *keys
; 
 662     if (cmd
->firstkey 
== 0) { 
 667     if (last 
< 0) last 
= argc
+last
; 
 668     keys 
= zmalloc(sizeof(int)*((last 
- cmd
->firstkey
)+1)); 
 669     for (j 
= cmd
->firstkey
; j 
<= last
; j 
+= cmd
->keystep
) { 
 670         redisAssert(j 
< argc
); 
 677 int *getKeysFromCommand(struct redisCommand 
*cmd
,robj 
**argv
, int argc
, int *numkeys
, int flags
) { 
 678     if (cmd
->getkeys_proc
) { 
 679         return cmd
->getkeys_proc(cmd
,argv
,argc
,numkeys
,flags
); 
 681         return getKeysUsingCommandTable(cmd
,argv
,argc
,numkeys
); 
 685 void getKeysFreeResult(int *result
) { 
 689 int *noPreloadGetKeys(struct redisCommand 
*cmd
,robj 
**argv
, int argc
, int *numkeys
, int flags
) { 
 690     if (flags 
& REDIS_GETKEYS_PRELOAD
) { 
 694         return getKeysUsingCommandTable(cmd
,argv
,argc
,numkeys
); 
 698 int *renameGetKeys(struct redisCommand 
*cmd
,robj 
**argv
, int argc
, int *numkeys
, int flags
) { 
 699     if (flags 
& REDIS_GETKEYS_PRELOAD
) { 
 700         int *keys 
= zmalloc(sizeof(int)); 
 705         return getKeysUsingCommandTable(cmd
,argv
,argc
,numkeys
); 
 709 int *zunionInterGetKeys(struct redisCommand 
*cmd
,robj 
**argv
, int argc
, int *numkeys
, int flags
) { 
 712     REDIS_NOTUSED(flags
); 
 714     num 
= atoi(argv
[2]->ptr
); 
 715     /* Sanity check. Don't return any key if the command is going to 
 716      * reply with syntax error. */ 
 717     if (num 
> (argc
-3)) { 
 721     keys 
= zmalloc(sizeof(int)*num
); 
 722     for (i 
= 0; i 
< num
; i
++) keys
[i
] = 3+i
;