]>
git.saurik.com Git - redis.git/blob - src/db.c
536dc6d866170c9d44563e82d8824e88c3e19244
   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
); 
  97     redisAssertWithInfo(NULL
,key
,retval 
== REDIS_OK
); 
 100 /* Overwrite an existing key with a new value. Incrementing the reference 
 101  * count of the new value is up to the caller. 
 102  * This function does not modify the expire time of the existing key. 
 104  * The program is aborted if the key was not already present. */ 
 105 void dbOverwrite(redisDb 
*db
, robj 
*key
, robj 
*val
) { 
 106     struct dictEntry 
*de 
= dictFind(db
->dict
,key
->ptr
); 
 108     redisAssertWithInfo(NULL
,key
,de 
!= NULL
); 
 109     dictReplace(db
->dict
, key
->ptr
, val
); 
 112 /* High level Set operation. This function can be used in order to set 
 113  * a key, whatever it was existing or not, to a new object. 
 115  * 1) The ref count of the value object is incremented. 
 116  * 2) clients WATCHing for the destination key notified. 
 117  * 3) The expire time of the key is reset (the key is made persistent). */ 
 118 void setKey(redisDb 
*db
, robj 
*key
, robj 
*val
) { 
 119     if (lookupKeyWrite(db
,key
) == NULL
) { 
 122         dbOverwrite(db
,key
,val
); 
 125     removeExpire(db
,key
); 
 126     signalModifiedKey(db
,key
); 
 129 int dbExists(redisDb 
*db
, robj 
*key
) { 
 130     if (dictFind(db
->dict
,key
->ptr
) != NULL
) 
 132     if (recover(db
, key
) != NULL
) 
 137 /* Return a random key, in form of a Redis object. 
 138  * If there are no keys, NULL is returned. 
 140  * The function makes sure to return keys not already expired. */ 
 141 robj 
*dbRandomKey(redisDb 
*db
) { 
 142     struct dictEntry 
*de
; 
 148         de 
= dictGetRandomKey(db
->dict
); 
 149         if (de 
== NULL
) return NULL
; 
 151         key 
= dictGetKey(de
); 
 152         keyobj 
= createStringObject(key
,sdslen(key
)); 
 153         if (dictFind(db
->expires
,key
)) { 
 154             if (expireIfNeeded(db
,keyobj
)) { 
 155                 decrRefCount(keyobj
); 
 156                 continue; /* search for another key. This expired. */ 
 163 /* Delete a key, value, and associated expiration entry if any, from the DB */ 
 164 int dbDelete(redisDb 
*db
, robj 
*key
) { 
 165     /* Deleting an entry from the expires dict will not free the sds of 
 166      * the key, because it is shared with the main dictionary. */ 
 167     if (dictSize(db
->expires
) > 0) dictDelete(db
->expires
,key
->ptr
); 
 168     if (dictDelete(db
->dict
,key
->ptr
) == DICT_OK
) { 
 175 long long emptyDb() { 
 177     long long removed 
= 0; 
 179     for (j 
= 0; j 
< server
.dbnum
; j
++) { 
 180         removed 
+= dictSize(server
.db
[j
].dict
); 
 181         dictEmpty(server
.db
[j
].dict
); 
 182         dictEmpty(server
.db
[j
].expires
); 
 187 int selectDb(redisClient 
*c
, int id
) { 
 188     if (id 
< 0 || id 
>= server
.dbnum
) 
 190     c
->db 
= &server
.db
[id
]; 
 194 /*----------------------------------------------------------------------------- 
 195  * Hooks for key space changes. 
 197  * Every time a key in the database is modified the function 
 198  * signalModifiedKey() is called. 
 200  * Every time a DB is flushed the function signalFlushDb() is called. 
 201  *----------------------------------------------------------------------------*/ 
 203 void signalModifiedKey(redisDb 
*db
, robj 
*key
) { 
 204     touchWatchedKey(db
,key
); 
 207 void signalFlushedDb(int dbid
) { 
 208     touchWatchedKeysOnFlush(dbid
); 
 211 /*----------------------------------------------------------------------------- 
 212  * Type agnostic commands operating on the key space 
 213  *----------------------------------------------------------------------------*/ 
 215 void flushdbCommand(redisClient 
*c
) { 
 216     server
.dirty 
+= dictSize(c
->db
->dict
); 
 217     signalFlushedDb(c
->db
->id
); 
 218     dictEmpty(c
->db
->dict
); 
 219     dictEmpty(c
->db
->expires
); 
 220     addReply(c
,shared
.ok
); 
 223 void flushallCommand(redisClient 
*c
) { 
 225     server
.dirty 
+= emptyDb(); 
 226     addReply(c
,shared
.ok
); 
 227     if (server
.rdb_child_pid 
!= -1) { 
 228         kill(server
.rdb_child_pid
,SIGKILL
); 
 229         rdbRemoveTempFile(server
.rdb_child_pid
); 
 231     if (server
.saveparamslen 
> 0) { 
 232         /* Normally rdbSave() will reset dirty, but we don't want this here 
 233          * as otherwise FLUSHALL will not be replicated nor put into the AOF. */ 
 234         int saved_dirty 
= server
.dirty
; 
 235         rdbSave(server
.rdb_filename
); 
 236         server
.dirty 
= saved_dirty
; 
 241 void delCommand(redisClient 
*c
) { 
 244     for (j 
= 1; j 
< c
->argc
; j
++) { 
 245         if (dbDelete(c
->db
,c
->argv
[j
])) { 
 246             signalModifiedKey(c
->db
,c
->argv
[j
]); 
 251     addReplyLongLong(c
,deleted
); 
 254 void existsCommand(redisClient 
*c
) { 
 255     expireIfNeeded(c
->db
,c
->argv
[1]); 
 256     if (dbExists(c
->db
,c
->argv
[1])) { 
 257         addReply(c
, shared
.cone
); 
 259         addReply(c
, shared
.czero
); 
 263 void selectCommand(redisClient 
*c
) { 
 266     if (getLongFromObjectOrReply(c
, c
->argv
[1], &id
, 
 267         "invalid DB index") != REDIS_OK
) 
 270     if (selectDb(c
,id
) == REDIS_ERR
) { 
 271         addReplyError(c
,"invalid DB index"); 
 273         addReply(c
,shared
.ok
); 
 277 void randomkeyCommand(redisClient 
*c
) { 
 280     if ((key 
= dbRandomKey(c
->db
)) == NULL
) { 
 281         addReply(c
,shared
.nullbulk
); 
 289 void keysCommand(redisClient 
*c
) { 
 292     sds pattern 
= c
->argv
[1]->ptr
; 
 293     int plen 
= sdslen(pattern
), allkeys
; 
 294     unsigned long numkeys 
= 0; 
 295     void *replylen 
= addDeferredMultiBulkLength(c
); 
 297     di 
= dictGetSafeIterator(c
->db
->dict
); 
 298     allkeys 
= (pattern
[0] == '*' && pattern
[1] == '\0'); 
 299     while((de 
= dictNext(di
)) != NULL
) { 
 300         sds key 
= dictGetKey(de
); 
 303         if (allkeys 
|| stringmatchlen(pattern
,plen
,key
,sdslen(key
),0)) { 
 304             keyobj 
= createStringObject(key
,sdslen(key
)); 
 305             if (expireIfNeeded(c
->db
,keyobj
) == 0) { 
 306                 addReplyBulk(c
,keyobj
); 
 309             decrRefCount(keyobj
); 
 312     dictReleaseIterator(di
); 
 313     setDeferredMultiBulkLength(c
,replylen
,numkeys
); 
 316 void dbsizeCommand(redisClient 
*c
) { 
 317     addReplyLongLong(c
,dictSize(c
->db
->dict
)); 
 320 void lastsaveCommand(redisClient 
*c
) { 
 321     addReplyLongLong(c
,server
.lastsave
); 
 324 void typeCommand(redisClient 
*c
) { 
 328     o 
= lookupKeyRead(c
->db
,c
->argv
[1]); 
 333         case REDIS_STRING
: type 
= "string"; break; 
 334         case REDIS_LIST
: type 
= "list"; break; 
 335         case REDIS_SET
: type 
= "set"; break; 
 336         case REDIS_ZSET
: type 
= "zset"; break; 
 337         case REDIS_HASH
: type 
= "hash"; break; 
 338         default: type 
= "unknown"; break; 
 341     addReplyStatus(c
,type
); 
 344 void shutdownCommand(redisClient 
*c
) { 
 348         addReply(c
,shared
.syntaxerr
); 
 350     } else if (c
->argc 
== 2) { 
 351         if (!strcasecmp(c
->argv
[1]->ptr
,"nosave")) { 
 352             flags 
|= REDIS_SHUTDOWN_NOSAVE
; 
 353         } else if (!strcasecmp(c
->argv
[1]->ptr
,"save")) { 
 354             flags 
|= REDIS_SHUTDOWN_SAVE
; 
 356             addReply(c
,shared
.syntaxerr
); 
 360     if (prepareForShutdown(flags
) == REDIS_OK
) exit(0); 
 361     addReplyError(c
,"Errors trying to SHUTDOWN. Check logs."); 
 364 void renameGenericCommand(redisClient 
*c
, int nx
) { 
 368     /* To use the same key as src and dst is probably an error */ 
 369     if (sdscmp(c
->argv
[1]->ptr
,c
->argv
[2]->ptr
) == 0) { 
 370         addReply(c
,shared
.sameobjecterr
); 
 374     if ((o 
= lookupKeyWriteOrReply(c
,c
->argv
[1],shared
.nokeyerr
)) == NULL
) 
 378     expire 
= getExpire(c
->db
,c
->argv
[1]); 
 379     if (lookupKeyWrite(c
->db
,c
->argv
[2]) != NULL
) { 
 382             addReply(c
,shared
.czero
); 
 385         /* Overwrite: delete the old key before creating the new one with the same name. */ 
 386         dbDelete(c
->db
,c
->argv
[2]); 
 388     dbAdd(c
->db
,c
->argv
[2],o
); 
 389     if (expire 
!= -1) setExpire(c
->db
,c
->argv
[2],expire
); 
 390     dbDelete(c
->db
,c
->argv
[1]); 
 391     signalModifiedKey(c
->db
,c
->argv
[1]); 
 392     signalModifiedKey(c
->db
,c
->argv
[2]); 
 394     addReply(c
,nx 
? shared
.cone 
: shared
.ok
); 
 397 void renameCommand(redisClient 
*c
) { 
 398     renameGenericCommand(c
,0); 
 401 void renamenxCommand(redisClient 
*c
) { 
 402     renameGenericCommand(c
,1); 
 405 void moveCommand(redisClient 
*c
) { 
 410     /* Obtain source and target DB pointers */ 
 413     if (selectDb(c
,atoi(c
->argv
[2]->ptr
)) == REDIS_ERR
) { 
 414         addReply(c
,shared
.outofrangeerr
); 
 418     selectDb(c
,srcid
); /* Back to the source DB */ 
 420     /* If the user is moving using as target the same 
 421      * DB as the source DB it is probably an error. */ 
 423         addReply(c
,shared
.sameobjecterr
); 
 427     /* Check if the element exists and get a reference */ 
 428     o 
= lookupKeyWrite(c
->db
,c
->argv
[1]); 
 430         addReply(c
,shared
.czero
); 
 434     /* Return zero if the key already exists in the target DB */ 
 435     if (lookupKeyWrite(dst
,c
->argv
[1]) != NULL
) { 
 436         addReply(c
,shared
.czero
); 
 439     dbAdd(dst
,c
->argv
[1],o
); 
 442     /* OK! key moved, free the entry in the source DB */ 
 443     dbDelete(src
,c
->argv
[1]); 
 445     addReply(c
,shared
.cone
); 
 448 /*----------------------------------------------------------------------------- 
 450  *----------------------------------------------------------------------------*/ 
 452 int removeExpire(redisDb 
*db
, robj 
*key
) { 
 453     /* An expire may only be removed if there is a corresponding entry in the 
 454      * main dict. Otherwise, the key will never be freed. */ 
 455     redisAssertWithInfo(NULL
,key
,dictFind(db
->dict
,key
->ptr
) != NULL
); 
 456     return dictDelete(db
->expires
,key
->ptr
) == DICT_OK
; 
 459 void setExpire(redisDb 
*db
, robj 
*key
, long long when
) { 
 462     /* Reuse the sds from the main dict in the expire dict */ 
 463     kde 
= dictFind(db
->dict
,key
->ptr
); 
 464     redisAssertWithInfo(NULL
,key
,kde 
!= NULL
); 
 465     de 
= dictReplaceRaw(db
->expires
,dictGetKey(kde
)); 
 466     dictSetSignedIntegerVal(de
,when
); 
 469 /* Return the expire time of the specified key, or -1 if no expire 
 470  * is associated with this key (i.e. the key is non volatile) */ 
 471 long long getExpire(redisDb 
*db
, robj 
*key
) { 
 474     /* No expire? return ASAP */ 
 475     if (dictSize(db
->expires
) == 0 || 
 476        (de 
= dictFind(db
->expires
,key
->ptr
)) == NULL
) return -1; 
 478     /* The entry was found in the expire dict, this means it should also 
 479      * be present in the main dict (safety check). */ 
 480     redisAssertWithInfo(NULL
,key
,dictFind(db
->dict
,key
->ptr
) != NULL
); 
 481     return dictGetSignedIntegerVal(de
); 
 484 /* Propagate expires into slaves and the AOF file. 
 485  * When a key expires in the master, a DEL operation for this key is sent 
 486  * to all the slaves and the AOF file if enabled. 
 488  * This way the key expiry is centralized in one place, and since both 
 489  * AOF and the master->slave link guarantee operation ordering, everything 
 490  * will be consistent even if we allow write operations against expiring 
 492 void propagateExpire(redisDb 
*db
, robj 
*key
) { 
 495     argv
[0] = shared
.del
; 
 497     incrRefCount(argv
[0]); 
 498     incrRefCount(argv
[1]); 
 500     if (server
.aof_state 
!= REDIS_AOF_OFF
) 
 501         feedAppendOnlyFile(server
.delCommand
,db
->id
,argv
,2); 
 502     if (listLength(server
.slaves
)) 
 503         replicationFeedSlaves(server
.slaves
,db
->id
,argv
,2); 
 505     decrRefCount(argv
[0]); 
 506     decrRefCount(argv
[1]); 
 509 int expireIfNeeded(redisDb 
*db
, robj 
*key
) { 
 510     long long when 
= getExpire(db
,key
); 
 512     if (when 
< 0) return 0; /* No expire for this key */ 
 514     /* Don't expire anything while loading. It will be done later. */ 
 515     if (server
.loading
) return 0; 
 517     /* If we are running in the context of a slave, return ASAP: 
 518      * the slave key expiration is controlled by the master that will 
 519      * send us synthesized DEL operations for expired keys. 
 521      * Still we try to return the right information to the caller,  
 522      * that is, 0 if we think the key should be still valid, 1 if 
 523      * we think the key is expired at this time. */ 
 524     if (server
.masterhost 
!= NULL
) { 
 525         return mstime() > when
; 
 528     /* Return when this key has not expired */ 
 529     if (mstime() <= when
) return 0; 
 532     server
.stat_expiredkeys
++; 
 533     propagateExpire(db
,key
); 
 534     return dbDelete(db
,key
); 
 537 /*----------------------------------------------------------------------------- 
 539  *----------------------------------------------------------------------------*/ 
 541 /* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT 
 542  * and PEXPIREAT. Because the commad second argument may be relative or absolute 
 543  * the "basetime" argument is used to signal what the base time is (either 0 
 544  * for *AT variants of the command, or the current time for relative expires). 
 546  * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for 
 547  * the argv[2] parameter. The basetime is always specified in milliesconds. */ 
 548 void expireGenericCommand(redisClient 
*c
, long long basetime
, int unit
) { 
 550     robj 
*key 
= c
->argv
[1], *param 
= c
->argv
[2]; 
 551     long long when
; /* unix time in milliseconds when the key will expire. */ 
 553     if (getLongLongFromObjectOrReply(c
, param
, &when
, NULL
) != REDIS_OK
) 
 556     if (unit 
== UNIT_SECONDS
) when 
*= 1000; 
 559     de 
= dictFind(c
->db
->dict
,key
->ptr
); 
 561         addReply(c
,shared
.czero
); 
 564     /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past 
 565      * should never be executed as a DEL when load the AOF or in the context 
 566      * of a slave instance. 
 568      * Instead we take the other branch of the IF statement setting an expire 
 569      * (possibly in the past) and wait for an explicit DEL from the master. */ 
 570     if (when 
<= mstime() && !server
.loading 
&& !server
.masterhost
) { 
 573         redisAssertWithInfo(c
,key
,dbDelete(c
->db
,key
)); 
 576         /* Replicate/AOF this as an explicit DEL. */ 
 577         aux 
= createStringObject("DEL",3); 
 578         rewriteClientCommandVector(c
,2,aux
,key
); 
 580         signalModifiedKey(c
->db
,key
); 
 581         addReply(c
, shared
.cone
); 
 584         setExpire(c
->db
,key
,when
); 
 585         addReply(c
,shared
.cone
); 
 586         signalModifiedKey(c
->db
,key
); 
 592 void expireCommand(redisClient 
*c
) { 
 593     expireGenericCommand(c
,mstime(),UNIT_SECONDS
); 
 596 void expireatCommand(redisClient 
*c
) { 
 597     expireGenericCommand(c
,0,UNIT_SECONDS
); 
 600 void pexpireCommand(redisClient 
*c
) { 
 601     expireGenericCommand(c
,mstime(),UNIT_MILLISECONDS
); 
 604 void pexpireatCommand(redisClient 
*c
) { 
 605     expireGenericCommand(c
,0,UNIT_MILLISECONDS
); 
 608 void ttlGenericCommand(redisClient 
*c
, int output_ms
) { 
 609     long long expire
, ttl 
= -1; 
 611     expire 
= getExpire(c
->db
,c
->argv
[1]); 
 613         ttl 
= expire
-mstime(); 
 614         if (ttl 
< 0) ttl 
= -1; 
 617         addReplyLongLong(c
,-1); 
 619         addReplyLongLong(c
,output_ms 
? ttl 
: ((ttl
+500)/1000)); 
 623 void ttlCommand(redisClient 
*c
) { 
 624     ttlGenericCommand(c
, 0); 
 627 void pttlCommand(redisClient 
*c
) { 
 628     ttlGenericCommand(c
, 1); 
 631 void persistCommand(redisClient 
*c
) { 
 634     de 
= dictFind(c
->db
->dict
,c
->argv
[1]->ptr
); 
 636         addReply(c
,shared
.czero
); 
 638         if (removeExpire(c
->db
,c
->argv
[1])) { 
 639             addReply(c
,shared
.cone
); 
 642             addReply(c
,shared
.czero
); 
 647 /* ----------------------------------------------------------------------------- 
 648  * API to get key arguments from commands 
 649  * ---------------------------------------------------------------------------*/ 
 651 int *getKeysUsingCommandTable(struct redisCommand 
*cmd
,robj 
**argv
, int argc
, int *numkeys
) { 
 652     int j
, i 
= 0, last
, *keys
; 
 655     if (cmd
->firstkey 
== 0) { 
 660     if (last 
< 0) last 
= argc
+last
; 
 661     keys 
= zmalloc(sizeof(int)*((last 
- cmd
->firstkey
)+1)); 
 662     for (j 
= cmd
->firstkey
; j 
<= last
; j 
+= cmd
->keystep
) { 
 663         redisAssert(j 
< argc
); 
 670 int *getKeysFromCommand(struct redisCommand 
*cmd
,robj 
**argv
, int argc
, int *numkeys
, int flags
) { 
 671     if (cmd
->getkeys_proc
) { 
 672         return cmd
->getkeys_proc(cmd
,argv
,argc
,numkeys
,flags
); 
 674         return getKeysUsingCommandTable(cmd
,argv
,argc
,numkeys
); 
 678 void getKeysFreeResult(int *result
) { 
 682 int *noPreloadGetKeys(struct redisCommand 
*cmd
,robj 
**argv
, int argc
, int *numkeys
, int flags
) { 
 683     if (flags 
& REDIS_GETKEYS_PRELOAD
) { 
 687         return getKeysUsingCommandTable(cmd
,argv
,argc
,numkeys
); 
 691 int *renameGetKeys(struct redisCommand 
*cmd
,robj 
**argv
, int argc
, int *numkeys
, int flags
) { 
 692     if (flags 
& REDIS_GETKEYS_PRELOAD
) { 
 693         int *keys 
= zmalloc(sizeof(int)); 
 698         return getKeysUsingCommandTable(cmd
,argv
,argc
,numkeys
); 
 702 int *zunionInterGetKeys(struct redisCommand 
*cmd
,robj 
**argv
, int argc
, int *numkeys
, int flags
) { 
 705     REDIS_NOTUSED(flags
); 
 707     num 
= atoi(argv
[2]->ptr
); 
 708     /* Sanity check. Don't return any key if the command is going to 
 709      * reply with syntax error. */ 
 710     if (num 
> (argc
-3)) { 
 714     keys 
= zmalloc(sizeof(int)*num
); 
 715     for (i 
= 0; i 
< num
; i
++) keys
[i
] = 3+i
;