Back to home page

Redis cross reference

 
 

    


0001 /*
0002  * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
0003  * All rights reserved.
0004  *
0005  * Redistribution and use in source and binary forms, with or without
0006  * modification, are permitted provided that the following conditions are met:
0007  *
0008  *   * Redistributions of source code must retain the above copyright notice,
0009  *     this list of conditions and the following disclaimer.
0010  *   * Redistributions in binary form must reproduce the above copyright
0011  *     notice, this list of conditions and the following disclaimer in the
0012  *     documentation and/or other materials provided with the distribution.
0013  *   * Neither the name of Redis nor the names of its contributors may be used
0014  *     to endorse or promote products derived from this software without
0015  *     specific prior written permission.
0016  *
0017  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0018  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0019  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0020  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
0021  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
0022  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
0023  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0024  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
0025  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0026  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0027  * POSSIBILITY OF SUCH DAMAGE.
0028  */
0029 
0030 #include "redis.h"
0031 
0032 #include <signal.h>
0033 #include <ctype.h>
0034 
0035 void SlotToKeyAdd(robj *key);
0036 void SlotToKeyDel(robj *key);
0037 
0038 /*-----------------------------------------------------------------------------
0039  * C-level DB API
0040  *----------------------------------------------------------------------------*/
0041 
0042 robj *lookupKey(redisDb *db, robj *key) {
0043     dictEntry *de = dictFind(db->dict,key->ptr);
0044     if (de) {
0045         robj *val = dictGetVal(de);
0046 
0047         /* Update the access time for the ageing algorithm.
0048          * Don't do it if we have a saving child, as this will trigger
0049          * a copy on write madness. */
0050         if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
0051             val->lru = server.lruclock;
0052         return val;
0053     } else {
0054         return NULL;
0055     }
0056 }
0057 
0058 robj *lookupKeyRead(redisDb *db, robj *key) {
0059     robj *val;
0060 
0061     expireIfNeeded(db,key);
0062     val = lookupKey(db,key);
0063     if (val == NULL)
0064         server.stat_keyspace_misses++;
0065     else
0066         server.stat_keyspace_hits++;
0067     return val;
0068 }
0069 
0070 robj *lookupKeyWrite(redisDb *db, robj *key) {
0071     expireIfNeeded(db,key);
0072     return lookupKey(db,key);
0073 }
0074 
0075 robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) {
0076     robj *o = lookupKeyRead(c->db, key);
0077     if (!o) addReply(c,reply);
0078     return o;
0079 }
0080 
0081 robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) {
0082     robj *o = lookupKeyWrite(c->db, key);
0083     if (!o) addReply(c,reply);
0084     return o;
0085 }
0086 
0087 /* Add the key to the DB. It's up to the caller to increment the reference
0088  * counter of the value if needed.
0089  *
0090  * The program is aborted if the key already exists. */
0091 void dbAdd(redisDb *db, robj *key, robj *val) {
0092     sds copy = sdsdup(key->ptr);
0093     int retval = dictAdd(db->dict, copy, val);
0094 
0095     redisAssertWithInfo(NULL,key,retval == REDIS_OK);
0096  }
0097 
0098 /* Overwrite an existing key with a new value. Incrementing the reference
0099  * count of the new value is up to the caller.
0100  * This function does not modify the expire time of the existing key.
0101  *
0102  * The program is aborted if the key was not already present. */
0103 void dbOverwrite(redisDb *db, robj *key, robj *val) {
0104     struct dictEntry *de = dictFind(db->dict,key->ptr);
0105     
0106     redisAssertWithInfo(NULL,key,de != NULL);
0107     dictReplace(db->dict, key->ptr, val);
0108 }
0109 
0110 /* High level Set operation. This function can be used in order to set
0111  * a key, whatever it was existing or not, to a new object.
0112  *
0113  * 1) The ref count of the value object is incremented.
0114  * 2) clients WATCHing for the destination key notified.
0115  * 3) The expire time of the key is reset (the key is made persistent). */
0116 void setKey(redisDb *db, robj *key, robj *val) {
0117     if (lookupKeyWrite(db,key) == NULL) {
0118         dbAdd(db,key,val);
0119     } else {
0120         dbOverwrite(db,key,val);
0121     }
0122     incrRefCount(val);
0123     removeExpire(db,key);
0124     signalModifiedKey(db,key);
0125 }
0126 
0127 int dbExists(redisDb *db, robj *key) {
0128     return dictFind(db->dict,key->ptr) != NULL;
0129 }
0130 
0131 /* Return a random key, in form of a Redis object.
0132  * If there are no keys, NULL is returned.
0133  *
0134  * The function makes sure to return keys not already expired. */
0135 robj *dbRandomKey(redisDb *db) {
0136     struct dictEntry *de;
0137 
0138     while(1) {
0139         sds key;
0140         robj *keyobj;
0141 
0142         de = dictGetRandomKey(db->dict);
0143         if (de == NULL) return NULL;
0144 
0145         key = dictGetKey(de);
0146         keyobj = createStringObject(key,sdslen(key));
0147         if (dictFind(db->expires,key)) {
0148             if (expireIfNeeded(db,keyobj)) {
0149                 decrRefCount(keyobj);
0150                 continue; /* search for another key. This expired. */
0151             }
0152         }
0153         return keyobj;
0154     }
0155 }
0156 
0157 /* Delete a key, value, and associated expiration entry if any, from the DB */
0158 int dbDelete(redisDb *db, robj *key) {
0159     /* Deleting an entry from the expires dict will not free the sds of
0160      * the key, because it is shared with the main dictionary. */
0161     if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
0162     if (dictDelete(db->dict,key->ptr) == DICT_OK) {
0163         return 1;
0164     } else {
0165         return 0;
0166     }
0167 }
0168 
0169 long long emptyDb() {
0170     int j;
0171     long long removed = 0;
0172 
0173     for (j = 0; j < server.dbnum; j++) {
0174         removed += dictSize(server.db[j].dict);
0175         dictEmpty(server.db[j].dict);
0176         dictEmpty(server.db[j].expires);
0177     }
0178     return removed;
0179 }
0180 
0181 int selectDb(redisClient *c, int id) {
0182     if (id < 0 || id >= server.dbnum)
0183         return REDIS_ERR;
0184     c->db = &server.db[id];
0185     return REDIS_OK;
0186 }
0187 
0188 /*-----------------------------------------------------------------------------
0189  * Hooks for key space changes.
0190  *
0191  * Every time a key in the database is modified the function
0192  * signalModifiedKey() is called.
0193  *
0194  * Every time a DB is flushed the function signalFlushDb() is called.
0195  *----------------------------------------------------------------------------*/
0196 
0197 void signalModifiedKey(redisDb *db, robj *key) {
0198     touchWatchedKey(db,key);
0199 }
0200 
0201 void signalFlushedDb(int dbid) {
0202     touchWatchedKeysOnFlush(dbid);
0203 }
0204 
0205 /*-----------------------------------------------------------------------------
0206  * Type agnostic commands operating on the key space
0207  *----------------------------------------------------------------------------*/
0208 
0209 void flushdbCommand(redisClient *c) {
0210     server.dirty += dictSize(c->db->dict);
0211     signalFlushedDb(c->db->id);
0212     dictEmpty(c->db->dict);
0213     dictEmpty(c->db->expires);
0214     addReply(c,shared.ok);
0215 }
0216 
0217 void flushallCommand(redisClient *c) {
0218     signalFlushedDb(-1);
0219     server.dirty += emptyDb();
0220     addReply(c,shared.ok);
0221     if (server.rdb_child_pid != -1) {
0222         kill(server.rdb_child_pid,SIGUSR1);
0223         rdbRemoveTempFile(server.rdb_child_pid);
0224     }
0225     if (server.saveparamslen > 0) {
0226         /* Normally rdbSave() will reset dirty, but we don't want this here
0227          * as otherwise FLUSHALL will not be replicated nor put into the AOF. */
0228         int saved_dirty = server.dirty;
0229         rdbSave(server.rdb_filename);
0230         server.dirty = saved_dirty;
0231     }
0232     server.dirty++;
0233 }
0234 
0235 void delCommand(redisClient *c) {
0236     int deleted = 0, j;
0237 
0238     for (j = 1; j < c->argc; j++) {
0239         if (dbDelete(c->db,c->argv[j])) {
0240             signalModifiedKey(c->db,c->argv[j]);
0241             server.dirty++;
0242             deleted++;
0243         }
0244     }
0245     addReplyLongLong(c,deleted);
0246 }
0247 
0248 void existsCommand(redisClient *c) {
0249     expireIfNeeded(c->db,c->argv[1]);
0250     if (dbExists(c->db,c->argv[1])) {
0251         addReply(c, shared.cone);
0252     } else {
0253         addReply(c, shared.czero);
0254     }
0255 }
0256 
0257 void selectCommand(redisClient *c) {
0258     long id;
0259 
0260     if (getLongFromObjectOrReply(c, c->argv[1], &id,
0261         "invalid DB index") != REDIS_OK)
0262         return;
0263 
0264     if (selectDb(c,id) == REDIS_ERR) {
0265         addReplyError(c,"invalid DB index");
0266     } else {
0267         addReply(c,shared.ok);
0268     }
0269 }
0270 
0271 void randomkeyCommand(redisClient *c) {
0272     robj *key;
0273 
0274     if ((key = dbRandomKey(c->db)) == NULL) {
0275         addReply(c,shared.nullbulk);
0276         return;
0277     }
0278 
0279     addReplyBulk(c,key);
0280     decrRefCount(key);
0281 }
0282 
0283 void keysCommand(redisClient *c) {
0284     dictIterator *di;
0285     dictEntry *de;
0286     sds pattern = c->argv[1]->ptr;
0287     int plen = sdslen(pattern), allkeys;
0288     unsigned long numkeys = 0;
0289     void *replylen = addDeferredMultiBulkLength(c);
0290 
0291     di = dictGetSafeIterator(c->db->dict);
0292     allkeys = (pattern[0] == '*' && pattern[1] == '\0');
0293     while((de = dictNext(di)) != NULL) {
0294         sds key = dictGetKey(de);
0295         robj *keyobj;
0296 
0297         if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
0298             keyobj = createStringObject(key,sdslen(key));
0299             if (expireIfNeeded(c->db,keyobj) == 0) {
0300                 addReplyBulk(c,keyobj);
0301                 numkeys++;
0302             }
0303             decrRefCount(keyobj);
0304         }
0305     }
0306     dictReleaseIterator(di);
0307     setDeferredMultiBulkLength(c,replylen,numkeys);
0308 }
0309 
0310 void dbsizeCommand(redisClient *c) {
0311     addReplyLongLong(c,dictSize(c->db->dict));
0312 }
0313 
0314 void lastsaveCommand(redisClient *c) {
0315     addReplyLongLong(c,server.lastsave);
0316 }
0317 
0318 void typeCommand(redisClient *c) {
0319     robj *o;
0320     char *type;
0321 
0322     o = lookupKeyRead(c->db,c->argv[1]);
0323     if (o == NULL) {
0324         type = "none";
0325     } else {
0326         switch(o->type) {
0327         case REDIS_STRING: type = "string"; break;
0328         case REDIS_LIST: type = "list"; break;
0329         case REDIS_SET: type = "set"; break;
0330         case REDIS_ZSET: type = "zset"; break;
0331         case REDIS_HASH: type = "hash"; break;
0332         default: type = "unknown"; break;
0333         }
0334     }
0335     addReplyStatus(c,type);
0336 }
0337 
0338 void shutdownCommand(redisClient *c) {
0339     int flags = 0;
0340 
0341     if (c->argc > 2) {
0342         addReply(c,shared.syntaxerr);
0343         return;
0344     } else if (c->argc == 2) {
0345         if (!strcasecmp(c->argv[1]->ptr,"nosave")) {
0346             flags |= REDIS_SHUTDOWN_NOSAVE;
0347         } else if (!strcasecmp(c->argv[1]->ptr,"save")) {
0348             flags |= REDIS_SHUTDOWN_SAVE;
0349         } else {
0350             addReply(c,shared.syntaxerr);
0351             return;
0352         }
0353     }
0354     if (prepareForShutdown(flags) == REDIS_OK) exit(0);
0355     addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
0356 }
0357 
0358 void renameGenericCommand(redisClient *c, int nx) {
0359     robj *o;
0360     long long expire;
0361 
0362     /* To use the same key as src and dst is probably an error */
0363     if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) {
0364         addReply(c,shared.sameobjecterr);
0365         return;
0366     }
0367 
0368     if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL)
0369         return;
0370 
0371     incrRefCount(o);
0372     expire = getExpire(c->db,c->argv[1]);
0373     if (lookupKeyWrite(c->db,c->argv[2]) != NULL) {
0374         if (nx) {
0375             decrRefCount(o);
0376             addReply(c,shared.czero);
0377             return;
0378         }
0379         /* Overwrite: delete the old key before creating the new one with the same name. */
0380         dbDelete(c->db,c->argv[2]);
0381     }
0382     dbAdd(c->db,c->argv[2],o);
0383     if (expire != -1) setExpire(c->db,c->argv[2],expire);
0384     dbDelete(c->db,c->argv[1]);
0385     signalModifiedKey(c->db,c->argv[1]);
0386     signalModifiedKey(c->db,c->argv[2]);
0387     server.dirty++;
0388     addReply(c,nx ? shared.cone : shared.ok);
0389 }
0390 
0391 void renameCommand(redisClient *c) {
0392     renameGenericCommand(c,0);
0393 }
0394 
0395 void renamenxCommand(redisClient *c) {
0396     renameGenericCommand(c,1);
0397 }
0398 
0399 void moveCommand(redisClient *c) {
0400     robj *o;
0401     redisDb *src, *dst;
0402     int srcid;
0403 
0404     /* Obtain source and target DB pointers */
0405     src = c->db;
0406     srcid = c->db->id;
0407     if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) {
0408         addReply(c,shared.outofrangeerr);
0409         return;
0410     }
0411     dst = c->db;
0412     selectDb(c,srcid); /* Back to the source DB */
0413 
0414     /* If the user is moving using as target the same
0415      * DB as the source DB it is probably an error. */
0416     if (src == dst) {
0417         addReply(c,shared.sameobjecterr);
0418         return;
0419     }
0420 
0421     /* Check if the element exists and get a reference */
0422     o = lookupKeyWrite(c->db,c->argv[1]);
0423     if (!o) {
0424         addReply(c,shared.czero);
0425         return;
0426     }
0427 
0428     /* Return zero if the key already exists in the target DB */
0429     if (lookupKeyWrite(dst,c->argv[1]) != NULL) {
0430         addReply(c,shared.czero);
0431         return;
0432     }
0433     dbAdd(dst,c->argv[1],o);
0434     incrRefCount(o);
0435 
0436     /* OK! key moved, free the entry in the source DB */
0437     dbDelete(src,c->argv[1]);
0438     server.dirty++;
0439     addReply(c,shared.cone);
0440 }
0441 
0442 /*-----------------------------------------------------------------------------
0443  * Expires API
0444  *----------------------------------------------------------------------------*/
0445 
0446 int removeExpire(redisDb *db, robj *key) {
0447     /* An expire may only be removed if there is a corresponding entry in the
0448      * main dict. Otherwise, the key will never be freed. */
0449     redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
0450     return dictDelete(db->expires,key->ptr) == DICT_OK;
0451 }
0452 
0453 void setExpire(redisDb *db, robj *key, long long when) {
0454     dictEntry *kde, *de;
0455 
0456     /* Reuse the sds from the main dict in the expire dict */
0457     kde = dictFind(db->dict,key->ptr);
0458     redisAssertWithInfo(NULL,key,kde != NULL);
0459     de = dictReplaceRaw(db->expires,dictGetKey(kde));
0460     dictSetSignedIntegerVal(de,when);
0461 }
0462 
0463 /* Return the expire time of the specified key, or -1 if no expire
0464  * is associated with this key (i.e. the key is non volatile) */
0465 long long getExpire(redisDb *db, robj *key) {
0466     dictEntry *de;
0467 
0468     /* No expire? return ASAP */
0469     if (dictSize(db->expires) == 0 ||
0470        (de = dictFind(db->expires,key->ptr)) == NULL) return -1;
0471 
0472     /* The entry was found in the expire dict, this means it should also
0473      * be present in the main dict (safety check). */
0474     redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
0475     return dictGetSignedIntegerVal(de);
0476 }
0477 
0478 /* Propagate expires into slaves and the AOF file.
0479  * When a key expires in the master, a DEL operation for this key is sent
0480  * to all the slaves and the AOF file if enabled.
0481  *
0482  * This way the key expiry is centralized in one place, and since both
0483  * AOF and the master->slave link guarantee operation ordering, everything
0484  * will be consistent even if we allow write operations against expiring
0485  * keys. */
0486 void propagateExpire(redisDb *db, robj *key) {
0487     robj *argv[2];
0488 
0489     argv[0] = shared.del;
0490     argv[1] = key;
0491     incrRefCount(argv[0]);
0492     incrRefCount(argv[1]);
0493 
0494     if (server.aof_state != REDIS_AOF_OFF)
0495         feedAppendOnlyFile(server.delCommand,db->id,argv,2);
0496     if (listLength(server.slaves))
0497         replicationFeedSlaves(server.slaves,db->id,argv,2);
0498 
0499     decrRefCount(argv[0]);
0500     decrRefCount(argv[1]);
0501 }
0502 
0503 int expireIfNeeded(redisDb *db, robj *key) {
0504     long long when = getExpire(db,key);
0505 
0506     if (when < 0) return 0; /* No expire for this key */
0507 
0508     /* Don't expire anything while loading. It will be done later. */
0509     if (server.loading) return 0;
0510 
0511     /* If we are running in the context of a slave, return ASAP:
0512      * the slave key expiration is controlled by the master that will
0513      * send us synthesized DEL operations for expired keys.
0514      *
0515      * Still we try to return the right information to the caller, 
0516      * that is, 0 if we think the key should be still valid, 1 if
0517      * we think the key is expired at this time. */
0518     if (server.masterhost != NULL) {
0519         return mstime() > when;
0520     }
0521 
0522     /* Return when this key has not expired */
0523     if (mstime() <= when) return 0;
0524 
0525     /* Delete the key */
0526     server.stat_expiredkeys++;
0527     propagateExpire(db,key);
0528     return dbDelete(db,key);
0529 }
0530 
0531 /*-----------------------------------------------------------------------------
0532  * Expires Commands
0533  *----------------------------------------------------------------------------*/
0534 
0535 /* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT
0536  * and PEXPIREAT. Because the commad second argument may be relative or absolute
0537  * the "basetime" argument is used to signal what the base time is (either 0
0538  * for *AT variants of the command, or the current time for relative expires).
0539  *
0540  * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
0541  * the argv[2] parameter. The basetime is always specified in milliseconds. */
0542 void expireGenericCommand(redisClient *c, long long basetime, int unit) {
0543     robj *key = c->argv[1], *param = c->argv[2];
0544     long long when; /* unix time in milliseconds when the key will expire. */
0545 
0546     if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK)
0547         return;
0548 
0549     if (unit == UNIT_SECONDS) when *= 1000;
0550     when += basetime;
0551 
0552     /* No key, return zero. */
0553     if (lookupKeyRead(c->db,key) == NULL) {
0554         addReply(c,shared.czero);
0555         return;
0556     }
0557 
0558     /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
0559      * should never be executed as a DEL when load the AOF or in the context
0560      * of a slave instance.
0561      *
0562      * Instead we take the other branch of the IF statement setting an expire
0563      * (possibly in the past) and wait for an explicit DEL from the master. */
0564     if (when <= mstime() && !server.loading && !server.masterhost) {
0565         robj *aux;
0566 
0567         redisAssertWithInfo(c,key,dbDelete(c->db,key));
0568         server.dirty++;
0569 
0570         /* Replicate/AOF this as an explicit DEL. */
0571         aux = createStringObject("DEL",3);
0572         rewriteClientCommandVector(c,2,aux,key);
0573         decrRefCount(aux);
0574         signalModifiedKey(c->db,key);
0575         addReply(c, shared.cone);
0576         return;
0577     } else {
0578         setExpire(c->db,key,when);
0579         addReply(c,shared.cone);
0580         signalModifiedKey(c->db,key);
0581         server.dirty++;
0582         return;
0583     }
0584 }
0585 
0586 void expireCommand(redisClient *c) {
0587     expireGenericCommand(c,mstime(),UNIT_SECONDS);
0588 }
0589 
0590 void expireatCommand(redisClient *c) {
0591     expireGenericCommand(c,0,UNIT_SECONDS);
0592 }
0593 
0594 void pexpireCommand(redisClient *c) {
0595     expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
0596 }
0597 
0598 void pexpireatCommand(redisClient *c) {
0599     expireGenericCommand(c,0,UNIT_MILLISECONDS);
0600 }
0601 
0602 void ttlGenericCommand(redisClient *c, int output_ms) {
0603     long long expire, ttl = -1;
0604 
0605     expire = getExpire(c->db,c->argv[1]);
0606     if (expire != -1) {
0607         ttl = expire-mstime();
0608         if (ttl < 0) ttl = -1;
0609     }
0610     if (ttl == -1) {
0611         addReplyLongLong(c,-1);
0612     } else {
0613         addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000));
0614     }
0615 }
0616 
0617 void ttlCommand(redisClient *c) {
0618     ttlGenericCommand(c, 0);
0619 }
0620 
0621 void pttlCommand(redisClient *c) {
0622     ttlGenericCommand(c, 1);
0623 }
0624 
0625 void persistCommand(redisClient *c) {
0626     dictEntry *de;
0627 
0628     de = dictFind(c->db->dict,c->argv[1]->ptr);
0629     if (de == NULL) {
0630         addReply(c,shared.czero);
0631     } else {
0632         if (removeExpire(c->db,c->argv[1])) {
0633             addReply(c,shared.cone);
0634             server.dirty++;
0635         } else {
0636             addReply(c,shared.czero);
0637         }
0638     }
0639 }
0640 
0641 /* -----------------------------------------------------------------------------
0642  * API to get key arguments from commands
0643  * ---------------------------------------------------------------------------*/
0644 
0645 int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) {
0646     int j, i = 0, last, *keys;
0647     REDIS_NOTUSED(argv);
0648 
0649     if (cmd->firstkey == 0) {
0650         *numkeys = 0;
0651         return NULL;
0652     }
0653     last = cmd->lastkey;
0654     if (last < 0) last = argc+last;
0655     keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1));
0656     for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
0657         redisAssert(j < argc);
0658         keys[i++] = j;
0659     }
0660     *numkeys = i;
0661     return keys;
0662 }
0663 
0664 int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
0665     if (cmd->getkeys_proc) {
0666         return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags);
0667     } else {
0668         return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
0669     }
0670 }
0671 
0672 void getKeysFreeResult(int *result) {
0673     zfree(result);
0674 }
0675 
0676 int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
0677     if (flags & REDIS_GETKEYS_PRELOAD) {
0678         *numkeys = 0;
0679         return NULL;
0680     } else {
0681         return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
0682     }
0683 }
0684 
0685 int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
0686     if (flags & REDIS_GETKEYS_PRELOAD) {
0687         int *keys = zmalloc(sizeof(int));
0688         *numkeys = 1;
0689         keys[0] = 1;
0690         return keys;
0691     } else {
0692         return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
0693     }
0694 }
0695 
0696 int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
0697     int i, num, *keys;
0698     REDIS_NOTUSED(cmd);
0699     REDIS_NOTUSED(flags);
0700 
0701     num = atoi(argv[2]->ptr);
0702     /* Sanity check. Don't return any key if the command is going to
0703      * reply with syntax error. */
0704     if (num > (argc-3)) {
0705         *numkeys = 0;
0706         return NULL;
0707     }
0708     keys = zmalloc(sizeof(int)*num);
0709     for (i = 0; i < num; i++) keys[i] = 3+i;
0710     *numkeys = num;
0711     return keys;
0712 }