X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/39191553755d5309e200789cedff50e031909d18..ffc6b7f864dcaa58b6c5d81d7e595050fe954dec:/redis.c diff --git a/redis.c b/redis.c index 8f524dd6..bb0e2004 100644 --- a/redis.c +++ b/redis.c @@ -27,7 +27,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#define REDIS_VERSION "1.3.4" +#define REDIS_VERSION "1.3.8" #include "fmacros.h" #include "config.h" @@ -75,6 +75,7 @@ #include "zmalloc.h" /* total memory usage aware version of malloc/free */ #include "lzf.h" /* LZF compression library */ #include "pqsort.h" /* Partial qsort for SORT+LIMIT */ +#include "zipmap.h" /* Error codes */ #define REDIS_OK 0 @@ -85,12 +86,12 @@ #define REDIS_MAXIDLETIME (60*5) /* default client timeout */ #define REDIS_IOBUF_LEN 1024 #define REDIS_LOADBUF_LEN 1024 -#define REDIS_STATIC_ARGS 4 +#define REDIS_STATIC_ARGS 8 #define REDIS_DEFAULT_DBNUM 16 #define REDIS_CONFIGLINE_MAX 1024 #define REDIS_OBJFREELIST_MAX 1000000 /* Max number of objects to cache */ #define REDIS_MAX_SYNC_TIME 60 /* Slave can't take more to sync */ -#define REDIS_EXPIRELOOKUPS_PER_CRON 100 /* try to expire 100 keys/second */ +#define REDIS_EXPIRELOOKUPS_PER_CRON 10 /* try to expire 10 keys/loop */ #define REDIS_MAX_WRITE_PER_EVENT (1024*64) #define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */ @@ -118,9 +119,17 @@ #define REDIS_ZSET 3 #define REDIS_HASH 4 -/* Objects encoding */ +/* Objects encoding. Some kind of objects like Strings and Hashes can be + * internally represented in multiple ways. The 'encoding' field of the object + * is set to one of this fields for this object. */ #define REDIS_ENCODING_RAW 0 /* Raw representation */ #define REDIS_ENCODING_INT 1 /* Encoded as integer */ +#define REDIS_ENCODING_ZIPMAP 2 /* Encoded as zipmap */ +#define REDIS_ENCODING_HT 3 /* Encoded as an hash table */ + +static char* strencoding[] = { + "raw", "int", "zipmap", "hashtable" +}; /* Object types only used for dumping to disk */ #define REDIS_EXPIRETIME 253 @@ -221,6 +230,10 @@ #define APPENDFSYNC_ALWAYS 1 #define APPENDFSYNC_EVERYSEC 2 +/* Hashes related defaults */ +#define REDIS_HASH_MAX_ZIPMAP_ENTRIES 64 +#define REDIS_HASH_MAX_ZIPMAP_VALUE 512 + /* We can print the stacktrace, so our assert is defined this way: */ #define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1))) static void _redisAssert(char *estr, char *file, int line); @@ -314,6 +327,8 @@ typedef struct redisClient { * is >= blockingto then the operation timed out. */ list *io_keys; /* Keys this client is waiting to be loaded from the * swap file in order to continue. */ + dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */ + list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */ } redisClient; struct saveparam { @@ -340,6 +355,7 @@ struct redisServer { time_t stat_starttime; /* server start time */ long long stat_numcommands; /* number of processed commands */ long long stat_numconnections; /* number of connections received */ + long long stat_expiredkeys; /* number of expired keys */ /* Configuration */ int verbosity; int glueoutputbuf; @@ -386,6 +402,9 @@ struct redisServer { off_t vm_page_size; off_t vm_pages; unsigned long long vm_max_memory; + /* Hashes config */ + size_t hash_max_zipmap_entries; + size_t hash_max_zipmap_value; /* Virtual memory state */ FILE *vm_fp; int vm_fd; @@ -418,15 +437,28 @@ struct redisServer { unsigned long long vm_stats_swapped_objects; unsigned long long vm_stats_swapouts; unsigned long long vm_stats_swapins; + /* Pubsub */ + dict *pubsub_channels; /* Map channels to list of subscribed clients */ + list *pubsub_patterns; /* A list of pubsub_patterns */ + /* Misc */ FILE *devnull; }; +typedef struct pubsubPattern { + redisClient *client; + robj *pattern; +} pubsubPattern; + typedef void redisCommandProc(redisClient *c); struct redisCommand { char *name; redisCommandProc *proc; int arity; int flags; + /* Use a function to determine which keys need to be loaded + * in the background prior to executing this command. Takes precedence + * over vm_firstkey and others, ignored when NULL */ + redisCommandProc *vm_preload_proc; /* What keys should be loaded in background when calling this command? */ int vm_firstkey; /* The first argument that's a key (0 = no keys) */ int vm_lastkey; /* THe last argument that's a key */ @@ -480,7 +512,9 @@ struct sharedObjectsStruct { *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *outofrangeerr, *plus, *select0, *select1, *select2, *select3, *select4, - *select5, *select6, *select7, *select8, *select9; + *select5, *select6, *select7, *select8, *select9, + *messagebulk, *subscribebulk, *unsubscribebulk, *mbulk3, + *psubscribebulk, *punsubscribebulk; } shared; /* Global vars that are actally used as constants. The following double @@ -500,7 +534,7 @@ typedef struct iojob { robj *val; /* the value to swap for REDIS_IOREQ_*_SWAP, otherwise this * field is populated by the I/O thread for REDIS_IOREQ_LOAD. */ off_t page; /* Swap page where to read/write the object */ - off_t pages; /* Swap pages needed to safe object. PREPARE_SWAP return val */ + off_t pages; /* Swap pages needed to save object. PREPARE_SWAP return val */ int canceled; /* True if this command was canceled by blocking side of VM */ pthread_t thread; /* ID of the thread processing this entry */ } iojob; @@ -520,7 +554,7 @@ static void incrRefCount(robj *o); static int rdbSaveBackground(char *filename); static robj *createStringObject(char *ptr, size_t len); static robj *dupStringObject(robj *o); -static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc); +static void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc); static void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc); static int syncWithMaster(void); static robj *tryObjectSharing(robj *o); @@ -571,6 +605,7 @@ static robj *vmReadObjectFromSwap(off_t page, int type); static void waitEmptyIOJobsQueue(void); static void vmReopenSwapFile(void); static int vmFreePage(off_t page); +static void zunionInterBlockClientOnSwappedKeys(redisClient *c); static int blockClientOnSwappedKeys(struct redisCommand *cmd, redisClient *c); static int dontWaitForSwappedKey(redisClient *c, robj *key); static void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key); @@ -578,6 +613,13 @@ static void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mas static struct redisCommand *lookupCommand(char *name); static void call(redisClient *c, struct redisCommand *cmd); static void resetClient(redisClient *c); +static void convertToRealHash(robj *o); +static int pubsubUnsubscribeAllChannels(redisClient *c, int notify); +static int pubsubUnsubscribeAllPatterns(redisClient *c, int notify); +static void freePubsubPattern(void *p); +static int listMatchPubsubPattern(void *a, void *b); +static int compareStringObjects(robj *a, robj *b); +static void usage(); static void authCommand(redisClient *c); static void pingCommand(redisClient *c); @@ -661,102 +703,140 @@ static void brpopCommand(redisClient *c); static void appendCommand(redisClient *c); static void substrCommand(redisClient *c); static void zrankCommand(redisClient *c); +static void zrevrankCommand(redisClient *c); +static void hsetCommand(redisClient *c); +static void hgetCommand(redisClient *c); +static void hdelCommand(redisClient *c); +static void hlenCommand(redisClient *c); +static void zremrangebyrankCommand(redisClient *c); +static void zunionCommand(redisClient *c); +static void zinterCommand(redisClient *c); +static void hkeysCommand(redisClient *c); +static void hvalsCommand(redisClient *c); +static void hgetallCommand(redisClient *c); +static void hexistsCommand(redisClient *c); +static void configCommand(redisClient *c); +static void hincrbyCommand(redisClient *c); +static void subscribeCommand(redisClient *c); +static void unsubscribeCommand(redisClient *c); +static void psubscribeCommand(redisClient *c); +static void punsubscribeCommand(redisClient *c); +static void publishCommand(redisClient *c); /*================================= Globals ================================= */ /* Global vars */ static struct redisServer server; /* server global state */ static struct redisCommand cmdTable[] = { - {"get",getCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,0,0,0}, - {"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,0,0,0}, - {"append",appendCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1}, - {"substr",substrCommand,4,REDIS_CMD_INLINE,1,1,1}, - {"del",delCommand,-2,REDIS_CMD_INLINE,0,0,0}, - {"exists",existsCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"incr",incrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,1,1}, - {"decr",decrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,1,1}, - {"mget",mgetCommand,-2,REDIS_CMD_INLINE,1,-1,1}, - {"rpush",rpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1}, - {"lpush",lpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1}, - {"rpop",rpopCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"lpop",lpopCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"brpop",brpopCommand,-3,REDIS_CMD_INLINE,1,1,1}, - {"blpop",blpopCommand,-3,REDIS_CMD_INLINE,1,1,1}, - {"llen",llenCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"lindex",lindexCommand,3,REDIS_CMD_INLINE,1,1,1}, - {"lset",lsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1}, - {"lrange",lrangeCommand,4,REDIS_CMD_INLINE,1,1,1}, - {"ltrim",ltrimCommand,4,REDIS_CMD_INLINE,1,1,1}, - {"lrem",lremCommand,4,REDIS_CMD_BULK,1,1,1}, - {"rpoplpush",rpoplpushcommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,2,1}, - {"sadd",saddCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1}, - {"srem",sremCommand,3,REDIS_CMD_BULK,1,1,1}, - {"smove",smoveCommand,4,REDIS_CMD_BULK,1,2,1}, - {"sismember",sismemberCommand,3,REDIS_CMD_BULK,1,1,1}, - {"scard",scardCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"spop",spopCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"srandmember",srandmemberCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"sinter",sinterCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,-1,1}, - {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,2,-1,1}, - {"sunion",sunionCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,-1,1}, - {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,2,-1,1}, - {"sdiff",sdiffCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,-1,1}, - {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,2,-1,1}, - {"smembers",sinterCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"zadd",zaddCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1}, - {"zincrby",zincrbyCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1}, - {"zrem",zremCommand,3,REDIS_CMD_BULK,1,1,1}, - {"zremrangebyscore",zremrangebyscoreCommand,4,REDIS_CMD_INLINE,1,1,1}, - {"zrange",zrangeCommand,-4,REDIS_CMD_INLINE,1,1,1}, - {"zrangebyscore",zrangebyscoreCommand,-4,REDIS_CMD_INLINE,1,1,1}, - {"zcount",zcountCommand,4,REDIS_CMD_INLINE,1,1,1}, - {"zrevrange",zrevrangeCommand,-4,REDIS_CMD_INLINE,1,1,1}, - {"zcard",zcardCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"zscore",zscoreCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1}, - {"zrank",zrankCommand,3,REDIS_CMD_INLINE,1,1,1}, - {"incrby",incrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,1,1}, - {"decrby",decrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,1,1}, - {"getset",getsetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,1,1}, - {"mset",msetCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,-1,2}, - {"msetnx",msetnxCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,1,-1,2}, - {"randomkey",randomkeyCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"select",selectCommand,2,REDIS_CMD_INLINE,0,0,0}, - {"move",moveCommand,3,REDIS_CMD_INLINE,1,1,1}, - {"rename",renameCommand,3,REDIS_CMD_INLINE,1,1,1}, - {"renamenx",renamenxCommand,3,REDIS_CMD_INLINE,1,1,1}, - {"expire",expireCommand,3,REDIS_CMD_INLINE,0,0,0}, - {"expireat",expireatCommand,3,REDIS_CMD_INLINE,0,0,0}, - {"keys",keysCommand,2,REDIS_CMD_INLINE,0,0,0}, - {"dbsize",dbsizeCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"auth",authCommand,2,REDIS_CMD_INLINE,0,0,0}, - {"ping",pingCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"echo",echoCommand,2,REDIS_CMD_BULK,0,0,0}, - {"save",saveCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"bgsave",bgsaveCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"bgrewriteaof",bgrewriteaofCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"shutdown",shutdownCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"lastsave",lastsaveCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"type",typeCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"multi",multiCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"exec",execCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"discard",discardCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"sync",syncCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"flushdb",flushdbCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"flushall",flushallCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"sort",sortCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,1,1,1}, - {"info",infoCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"monitor",monitorCommand,1,REDIS_CMD_INLINE,0,0,0}, - {"ttl",ttlCommand,2,REDIS_CMD_INLINE,1,1,1}, - {"slaveof",slaveofCommand,3,REDIS_CMD_INLINE,0,0,0}, - {"debug",debugCommand,-2,REDIS_CMD_INLINE,0,0,0}, - {NULL,NULL,0,0,0,0,0} + {"get",getCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, + {"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, + {"append",appendCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"substr",substrCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, + {"del",delCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, + {"exists",existsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"incr",incrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"decr",decrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"mget",mgetCommand,-2,REDIS_CMD_INLINE,NULL,1,-1,1}, + {"rpush",rpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"lpush",lpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"rpop",rpopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"lpop",lpopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"brpop",brpopCommand,-3,REDIS_CMD_INLINE,NULL,1,1,1}, + {"blpop",blpopCommand,-3,REDIS_CMD_INLINE,NULL,1,1,1}, + {"llen",llenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"lindex",lindexCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, + {"lset",lsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"lrange",lrangeCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, + {"ltrim",ltrimCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, + {"lrem",lremCommand,4,REDIS_CMD_BULK,NULL,1,1,1}, + {"rpoplpush",rpoplpushcommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,2,1}, + {"sadd",saddCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"srem",sremCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, + {"smove",smoveCommand,4,REDIS_CMD_BULK,NULL,1,2,1}, + {"sismember",sismemberCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, + {"scard",scardCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"spop",spopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"srandmember",srandmemberCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"sinter",sinterCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1}, + {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1}, + {"sunion",sunionCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1}, + {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1}, + {"sdiff",sdiffCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1}, + {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1}, + {"smembers",sinterCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"zadd",zaddCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"zincrby",zincrbyCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"zrem",zremCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, + {"zremrangebyscore",zremrangebyscoreCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, + {"zremrangebyrank",zremrangebyrankCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, + {"zunion",zunionCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0}, + {"zinter",zinterCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0}, + {"zrange",zrangeCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1}, + {"zrangebyscore",zrangebyscoreCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1}, + {"zcount",zcountCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, + {"zrevrange",zrevrangeCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1}, + {"zcard",zcardCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"zscore",zscoreCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"zrank",zrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, + {"zrevrank",zrevrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, + {"hset",hsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hincrby",hincrbyCommand,4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hget",hgetCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, + {"hdel",hdelCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, + {"hlen",hlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"hkeys",hkeysCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"hvals",hvalsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"hgetall",hgetallCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"hexists",hexistsCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, + {"incrby",incrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"decrby",decrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"getset",getsetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"mset",msetCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,-1,2}, + {"msetnx",msetnxCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,-1,2}, + {"randomkey",randomkeyCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"select",selectCommand,2,REDIS_CMD_INLINE,NULL,0,0,0}, + {"move",moveCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, + {"rename",renameCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, + {"renamenx",renamenxCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, + {"expire",expireCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, + {"expireat",expireatCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, + {"keys",keysCommand,2,REDIS_CMD_INLINE,NULL,0,0,0}, + {"dbsize",dbsizeCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"auth",authCommand,2,REDIS_CMD_INLINE,NULL,0,0,0}, + {"ping",pingCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"echo",echoCommand,2,REDIS_CMD_BULK,NULL,0,0,0}, + {"save",saveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"bgsave",bgsaveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"bgrewriteaof",bgrewriteaofCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"shutdown",shutdownCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"lastsave",lastsaveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"type",typeCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"multi",multiCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"exec",execCommand,1,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,0,0,0}, + {"discard",discardCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"sync",syncCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"flushdb",flushdbCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"flushall",flushallCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"sort",sortCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"info",infoCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"monitor",monitorCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"ttl",ttlCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, + {"slaveof",slaveofCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, + {"debug",debugCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, + {"config",configCommand,-2,REDIS_CMD_BULK,NULL,0,0,0}, + {"subscribe",subscribeCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, + {"unsubscribe",unsubscribeCommand,-1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"psubscribe",psubscribeCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, + {"punsubscribe",punsubscribeCommand,-1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"publish",publishCommand,3,REDIS_CMD_BULK,NULL,0,0,0}, + {NULL,NULL,0,0,NULL,0,0,0} }; /*============================ Utility functions ============================ */ /* Glob-style pattern matching. */ -int stringmatchlen(const char *pattern, int patternLen, +static int stringmatchlen(const char *pattern, int patternLen, const char *string, int stringLen, int nocase) { while(patternLen) { @@ -878,6 +958,10 @@ int stringmatchlen(const char *pattern, int patternLen, return 0; } +static int stringmatch(const char *pattern, const char *string, int nocase) { + return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase); +} + static void redisLog(int level, const char *fmt, ...) { va_list ap; FILE *fp; @@ -959,6 +1043,10 @@ static int dictEncObjKeyCompare(void *privdata, const void *key1, robj *o1 = (robj*) key1, *o2 = (robj*) key2; int cmp; + if (o1->encoding == REDIS_ENCODING_INT && + o2->encoding == REDIS_ENCODING_INT && + o1->ptr == o2->ptr) return 1; + o1 = getDecodedObject(o1); o2 = getDecodedObject(o2); cmp = sdsDictKeyCompare(privdata,o1->ptr,o2->ptr); @@ -1011,7 +1099,7 @@ static dictType zsetDictType = { }; /* Db->dict */ -static dictType hashDictType = { +static dictType dbDictType = { dictObjHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ @@ -1030,6 +1118,16 @@ static dictType keyptrDictType = { NULL /* val destructor */ }; +/* Hash type hash table (note that small hashes are represented with zimpaps) */ +static dictType hashDictType = { + dictEncObjHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictEncObjKeyCompare, /* key compare */ + dictRedisObjectDestructor, /* key destructor */ + dictRedisObjectDestructor /* val destructor */ +}; + /* Keylist hash table type has unencoded redis objects as keys and * lists as values. It's used for blocking operations (BLPOP) and to * map swapped keys to a list of clients waiting for this keys to be loaded. */ @@ -1042,6 +1140,8 @@ static dictType keylistDictType = { dictListDestructor /* val destructor */ }; +static void version(); + /* ========================= Random utility functions ======================= */ /* Redis generally does not try to recover from out of memory conditions @@ -1068,7 +1168,9 @@ static void closeTimedoutClients(void) { if (server.maxidletime && !(c->flags & REDIS_SLAVE) && /* no timeout for slaves */ !(c->flags & REDIS_MASTER) && /* no timeout for masters */ - (now - c->lastinteraction > server.maxidletime)) + dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */ + listLength(c->pubsub_patterns) == 0 && + (now - c->lastinteraction > server.maxidletime)) { redisLog(REDIS_VERBOSE,"Closing idle client"); freeClient(c); @@ -1209,7 +1311,7 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD size = dictSlots(server.db[j].dict); used = dictSize(server.db[j].dict); vkeys = dictSize(server.db[j].expires); - if (!(loops % 5) && (used || vkeys)) { + if (!(loops % 50) && (used || vkeys)) { redisLog(REDIS_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size); /* dictPrintStats(server.dict); */ } @@ -1221,10 +1323,10 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD * if we resize the HT while there is the saving child at work actually * a lot of memory movements in the parent will cause a lot of pages * copied. */ - if (server.bgsavechildpid == -1) tryResizeHashTables(); + if (server.bgsavechildpid == -1 && !(loops % 10)) tryResizeHashTables(); /* Show information about connected clients */ - if (!(loops % 5)) { + if (!(loops % 50)) { redisLog(REDIS_VERBOSE,"%d clients connected (%d slaves), %zu bytes in use, %d shared objects", listLength(server.clients)-listLength(server.slaves), listLength(server.slaves), @@ -1233,7 +1335,7 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD } /* Close connections of timedout clients */ - if ((server.maxidletime && !(loops % 10)) || server.blpop_blocked_clients) + if ((server.maxidletime && !(loops % 100)) || server.blpop_blocked_clients) closeTimedoutClients(); /* Check if a background saving or AOF rewrite in progress terminated */ @@ -1291,6 +1393,7 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD if (now > t) { deleteKey(db,dictGetEntryKey(de)); expired++; + server.stat_expiredkeys++; } } } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4); @@ -1308,7 +1411,7 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD retval = (server.vm_max_threads == 0) ? vmSwapOneObjectBlocking() : vmSwapOneObjectThreaded(); - if (retval == REDIS_ERR && (loops % 30) == 0 && + if (retval == REDIS_ERR && !(loops % 300) && zmalloc_used_memory() > (server.vm_max_memory+server.vm_max_memory/10)) { @@ -1323,13 +1426,13 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD } /* Check if we should connect to a MASTER */ - if (server.replstate == REDIS_REPL_CONNECT) { + if (server.replstate == REDIS_REPL_CONNECT && !(loops % 10)) { redisLog(REDIS_NOTICE,"Connecting to MASTER..."); if (syncWithMaster() == REDIS_OK) { redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync succeeded"); } } - return 1000; + return 100; } /* This function gets called every time Redis is entering the @@ -1399,6 +1502,12 @@ static void createSharedObjects(void) { shared.select7 = createStringObject("select 7\r\n",10); shared.select8 = createStringObject("select 8\r\n",10); shared.select9 = createStringObject("select 9\r\n",10); + shared.messagebulk = createStringObject("$7\r\nmessage\r\n",13); + shared.subscribebulk = createStringObject("$9\r\nsubscribe\r\n",15); + shared.unsubscribebulk = createStringObject("$11\r\nunsubscribe\r\n",18); + shared.psubscribebulk = createStringObject("$10\r\npsubscribe\r\n",17); + shared.punsubscribebulk = createStringObject("$12\r\npunsubscribe\r\n",19); + shared.mbulk3 = createStringObject("*3\r\n",4); } static void appendServerSaveParams(time_t seconds, int changes) { @@ -1429,9 +1538,9 @@ static void initServerConfig() { server.lastfsync = time(NULL); server.appendfd = -1; server.appendseldb = -1; /* Make sure the first time will not match */ - server.pidfile = "/var/run/redis.pid"; - server.dbfilename = "dump.rdb"; - server.appendfilename = "appendonly.aof"; + server.pidfile = zstrdup("/var/run/redis.pid"); + server.dbfilename = zstrdup("dump.rdb"); + server.appendfilename = zstrdup("appendonly.aof"); server.requirepass = NULL; server.shareobjects = 0; server.rdbcompression = 1; @@ -1446,6 +1555,8 @@ static void initServerConfig() { server.vm_max_memory = 1024LL*1024*1024*1; /* 1 GB of RAM */ server.vm_max_threads = 4; server.vm_blocked_clients = 0; + server.hash_max_zipmap_entries = REDIS_HASH_MAX_ZIPMAP_ENTRIES; + server.hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE; resetServerSaveParams(); @@ -1493,13 +1604,17 @@ static void initServer() { exit(1); } for (j = 0; j < server.dbnum; j++) { - server.db[j].dict = dictCreate(&hashDictType,NULL); + server.db[j].dict = dictCreate(&dbDictType,NULL); server.db[j].expires = dictCreate(&keyptrDictType,NULL); server.db[j].blockingkeys = dictCreate(&keylistDictType,NULL); if (server.vm_enabled) server.db[j].io_keys = dictCreate(&keylistDictType,NULL); server.db[j].id = j; } + server.pubsub_channels = dictCreate(&keylistDictType,NULL); + server.pubsub_patterns = listCreate(); + listSetFreeMethod(server.pubsub_patterns,freePubsubPattern); + listSetMatchMethod(server.pubsub_patterns,listMatchPubsubPattern); server.cronloops = 0; server.bgsavechildpid = -1; server.bgrewritechildpid = -1; @@ -1508,6 +1623,7 @@ static void initServer() { server.dirty = 0; server.stat_numcommands = 0; server.stat_numconnections = 0; + server.stat_expiredkeys = 0; server.stat_starttime = time(NULL); server.unixtime = time(NULL); aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL); @@ -1552,12 +1668,15 @@ static void loadServerConfig(char *filename) { char buf[REDIS_CONFIGLINE_MAX+1], *err = NULL; int linenum = 0; sds line = NULL; + char *errormsg = "Fatal error, can't open config file '%s'"; + char *errorbuf = zmalloc(sizeof(char)*(strlen(errormsg)+strlen(filename))); + sprintf(errorbuf, errormsg, filename); if (filename[0] == '-' && filename[1] == '\0') fp = stdin; else { if ((fp = fopen(filename,"r")) == NULL) { - redisLog(REDIS_WARNING,"Fatal error, can't open config file"); + redisLog(REDIS_WARNING, errorbuf); exit(1); } } @@ -1639,6 +1758,8 @@ static void loadServerConfig(char *filename) { if (server.dbnum < 1) { err = "Invalid number of databases"; goto loaderr; } + } else if (!strcasecmp(argv[0],"include") && argc == 2) { + loadServerConfig(argv[1]); } else if (!strcasecmp(argv[0],"maxclients") && argc == 2) { server.maxclients = atoi(argv[1]); } else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) { @@ -1688,8 +1809,10 @@ static void loadServerConfig(char *filename) { } else if (!strcasecmp(argv[0],"requirepass") && argc == 2) { server.requirepass = zstrdup(argv[1]); } else if (!strcasecmp(argv[0],"pidfile") && argc == 2) { + zfree(server.pidfile); server.pidfile = zstrdup(argv[1]); } else if (!strcasecmp(argv[0],"dbfilename") && argc == 2) { + zfree(server.dbfilename); server.dbfilename = zstrdup(argv[1]); } else if (!strcasecmp(argv[0],"vm-enabled") && argc == 2) { if ((server.vm_enabled = yesnotoi(argv[1])) == -1) { @@ -1706,6 +1829,12 @@ static void loadServerConfig(char *filename) { server.vm_pages = strtoll(argv[1], NULL, 10); } else if (!strcasecmp(argv[0],"vm-max-threads") && argc == 2) { server.vm_max_threads = strtoll(argv[1], NULL, 10); + } else if (!strcasecmp(argv[0],"hash-max-zipmap-entries") && argc == 2){ + server.hash_max_zipmap_entries = strtol(argv[1], NULL, 10); + } else if (!strcasecmp(argv[0],"hash-max-zipmap-value") && argc == 2){ + server.hash_max_zipmap_value = strtol(argv[1], NULL, 10); + } else if (!strcasecmp(argv[0],"vm-max-threads") && argc == 2) { + server.vm_max_threads = strtoll(argv[1], NULL, 10); } else { err = "Bad directive or wrong number of arguments"; goto loaderr; } @@ -1749,6 +1878,12 @@ static void freeClient(redisClient *c) { if (c->flags & REDIS_BLOCKED) unblockClientWaitingData(c); + /* Unsubscribe from all the pubsub channels */ + pubsubUnsubscribeAllChannels(c,0); + pubsubUnsubscribeAllPatterns(c,0); + dictRelease(c->pubsub_channels); + listRelease(c->pubsub_patterns); + /* Obvious cleanup */ aeDeleteFileEvent(server.el,c->fd,AE_READABLE); aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); listRelease(c->reply); @@ -1771,7 +1906,7 @@ static void freeClient(redisClient *c) { dontWaitForSwappedKey(c,ln->value); } listRelease(c->io_keys); - /* Other cleanup */ + /* Master/slave cleanup */ if (c->flags & REDIS_SLAVE) { if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1) close(c->repldbfd); @@ -1784,6 +1919,7 @@ static void freeClient(redisClient *c) { server.master = NULL; server.replstate = REDIS_REPL_CONNECT; } + /* Release memory */ zfree(c->argv); zfree(c->mbargv); freeClientMultiState(c); @@ -1987,9 +2123,9 @@ static void call(redisClient *c, struct redisCommand *cmd) { if (server.appendonly && server.dirty-dirty) feedAppendOnlyFile(cmd,c->db->id,c->argv,c->argc); if (server.dirty-dirty && listLength(server.slaves)) - replicationFeedSlaves(server.slaves,cmd,c->db->id,c->argv,c->argc); + replicationFeedSlaves(server.slaves,c->db->id,c->argv,c->argc); if (listLength(server.monitors)) - replicationFeedSlaves(server.monitors,cmd,c->db->id,c->argv,c->argc); + replicationFeedSlaves(server.monitors,c->db->id,c->argv,c->argc); server.stat_numcommands++; } @@ -2099,10 +2235,6 @@ static int processCommand(redisClient *c) { cmd->name)); resetClient(c); return 1; - } else if (server.maxmemory && cmd->flags & REDIS_CMD_DENYOOM && zmalloc_used_memory() > server.maxmemory) { - addReplySds(c,sdsnew("-ERR command not allowed when used memory > 'maxmemory'\r\n")); - resetClient(c); - return 1; } else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) { /* This is a bulk command, we have to read the last argument yet. */ int bulklen = atoi(c->argv[c->argc-1]->ptr); @@ -2148,6 +2280,24 @@ static int processCommand(redisClient *c) { return 1; } + /* Handle the maxmemory directive */ + if (server.maxmemory && (cmd->flags & REDIS_CMD_DENYOOM) && + zmalloc_used_memory() > server.maxmemory) + { + addReplySds(c,sdsnew("-ERR command not allowed when used memory > 'maxmemory'\r\n")); + resetClient(c); + return 1; + } + + /* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */ + if (dictSize(c->pubsub_channels) > 0 && + cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand && + cmd->proc != psubscribeCommand && cmd->proc != punsubscribeCommand) { + addReplySds(c,sdsnew("-ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context\r\n")); + resetClient(c); + return 1; + } + /* Exec the command */ if (c->flags & REDIS_MULTI && cmd->proc != execCommand && cmd->proc != discardCommand) { queueMultiCommand(c,cmd); @@ -2163,34 +2313,36 @@ static int processCommand(redisClient *c) { return 1; } -static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc) { +static void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) { listNode *ln; listIter li; int outc = 0, j; robj **outv; - /* (args*2)+1 is enough room for args, spaces, newlines */ - robj *static_outv[REDIS_STATIC_ARGS*2+1]; + /* We need 1+(ARGS*3) objects since commands are using the new protocol + * and we one 1 object for the first "*\r\n" multibulk count, then + * for every additional object we have "$\r\n" + object + "\r\n". */ + robj *static_outv[REDIS_STATIC_ARGS*3+1]; + robj *lenobj; if (argc <= REDIS_STATIC_ARGS) { outv = static_outv; } else { - outv = zmalloc(sizeof(robj*)*(argc*2+1)); + outv = zmalloc(sizeof(robj*)*(argc*3+1)); } - - for (j = 0; j < argc; j++) { - if (j != 0) outv[outc++] = shared.space; - if ((cmd->flags & REDIS_CMD_BULK) && j == argc-1) { - robj *lenobj; - lenobj = createObject(REDIS_STRING, - sdscatprintf(sdsempty(),"%lu\r\n", - (unsigned long) stringObjectLen(argv[j]))); - lenobj->refcount = 0; - outv[outc++] = lenobj; - } + lenobj = createObject(REDIS_STRING, + sdscatprintf(sdsempty(), "*%d\r\n", argc)); + lenobj->refcount = 0; + outv[outc++] = lenobj; + for (j = 0; j < argc; j++) { + lenobj = createObject(REDIS_STRING, + sdscatprintf(sdsempty(),"$%lu\r\n", + (unsigned long) stringObjectLen(argv[j]))); + lenobj->refcount = 0; + outv[outc++] = lenobj; outv[outc++] = argv[j]; + outv[outc++] = shared.crlf; } - outv[outc++] = shared.crlf; /* Increment all the refcounts at start and decrement at end in order to * be sure to free objects if there is no slave in a replication state @@ -2342,8 +2494,7 @@ static void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mas } else { return; } - if (!(c->flags & REDIS_BLOCKED)) - processInputBuffer(c); + processInputBuffer(c); } static int selectDb(redisClient *c, int id) { @@ -2358,6 +2509,10 @@ static void *dupClientReplyValue(void *o) { return o; } +static int listMatchObjects(void *a, void *b) { + return compareStringObjects(a,b) == 0; +} + static redisClient *createClient(int fd) { redisClient *c = zmalloc(sizeof(*c)); @@ -2385,6 +2540,10 @@ static redisClient *createClient(int fd) { c->blockingkeysnum = 0; c->io_keys = listCreate(); listSetFreeMethod(c->io_keys,decrRefCount); + c->pubsub_channels = dictCreate(&setDictType,NULL); + c->pubsub_patterns = listCreate(); + listSetFreeMethod(c->pubsub_patterns,decrRefCount); + listSetMatchMethod(c->pubsub_patterns,listMatchObjects); if (aeCreateFileEvent(server.el, c->fd, AE_READABLE, readQueryFromClient, c) == AE_ERR) { freeClient(c); @@ -2427,10 +2586,32 @@ static void addReplyLong(redisClient *c, long l) { char buf[128]; size_t len; + if (l == 0) { + addReply(c,shared.czero); + return; + } else if (l == 1) { + addReply(c,shared.cone); + return; + } len = snprintf(buf,sizeof(buf),":%ld\r\n",l); addReplySds(c,sdsnewlen(buf,len)); } +static void addReplyUlong(redisClient *c, unsigned long ul) { + char buf[128]; + size_t len; + + if (ul == 0) { + addReply(c,shared.czero); + return; + } else if (ul == 1) { + addReply(c,shared.cone); + return; + } + len = snprintf(buf,sizeof(buf),":%lu\r\n",ul); + addReplySds(c,sdsnewlen(buf,len)); +} + static void addReplyBulkLen(redisClient *c, robj *obj) { size_t len; @@ -2452,6 +2633,23 @@ static void addReplyBulkLen(redisClient *c, robj *obj) { addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n",(unsigned long)len)); } +static void addReplyBulk(redisClient *c, robj *obj) { + addReplyBulkLen(c,obj); + addReply(c,obj); + addReply(c,shared.crlf); +} + +/* In the CONFIG command we need to add vanilla C string as bulk replies */ +static void addReplyBulkCString(redisClient *c, char *s) { + if (s == NULL) { + addReply(c,shared.nullbulk); + } else { + robj *o = createStringObject(s,strlen(s)); + addReplyBulk(c,o); + decrRefCount(o); + } +} + static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { int cport, cfd; char cip[128]; @@ -2543,6 +2741,16 @@ static robj *createSetObject(void) { return createObject(REDIS_SET,d); } +static robj *createHashObject(void) { + /* All the Hashes start as zipmaps. Will be automatically converted + * into hash tables if there are enough elements or big elements + * inside. */ + unsigned char *zm = zipmapNew(); + robj *o = createObject(REDIS_HASH,zm); + o->encoding = REDIS_ENCODING_ZIPMAP; + return o; +} + static robj *createZsetObject(void) { zset *zs = zmalloc(sizeof(*zs)); @@ -2574,7 +2782,17 @@ static void freeZsetObject(robj *o) { } static void freeHashObject(robj *o) { - dictRelease((dict*) o->ptr); + switch (o->encoding) { + case REDIS_ENCODING_HT: + dictRelease((dict*) o->ptr); + break; + case REDIS_ENCODING_ZIPMAP: + zfree(o->ptr); + break; + default: + redisAssert(0); + break; + } } static void incrRefCount(robj *o) { @@ -2615,7 +2833,7 @@ static void decrRefCount(void *obj) { case REDIS_SET: freeSetObject(o); break; case REDIS_ZSET: freeZsetObject(o); break; case REDIS_HASH: freeHashObject(o); break; - default: redisAssert(0 != 0); break; + default: redisAssert(0); break; } if (server.vm_enabled) pthread_mutex_lock(&server.obj_freelist_mutex); if (listLength(server.objfreelist) > REDIS_OBJFREELIST_MAX || @@ -2670,6 +2888,26 @@ static robj *lookupKeyWrite(redisDb *db, robj *key) { return lookupKey(db,key); } +static robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) { + robj *o = lookupKeyRead(c->db, key); + if (!o) addReply(c,reply); + return o; +} + +static robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) { + robj *o = lookupKeyWrite(c->db, key); + if (!o) addReply(c,reply); + return o; +} + +static int checkType(redisClient *c, robj *o, int type) { + if (o->type != type) { + addReply(c,shared.wrongtypeerr); + return 1; + } + return 0; +} + static int deleteKey(redisDb *db, robj *key) { int retval; @@ -2879,7 +3117,7 @@ static int rdbSaveLen(FILE *fp, uint32_t len) { /* String objects in the form "2391" "-100" without any space and with a * range of values that can fit in an 8, 16 or 32 bit signed value can be * encoded as integers to save space */ -static int rdbTryIntegerEncoding(sds s, unsigned char *enc) { +static int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) { long long value; char *endptr, buf[32]; @@ -2890,7 +3128,7 @@ static int rdbTryIntegerEncoding(sds s, unsigned char *enc) { /* If the number converted back into a string is not identical * then it's not possible to encode the string as integer */ - if (strlen(buf) != sdslen(s) || memcmp(buf,s,sdslen(s))) return 0; + if (strlen(buf) != len || memcmp(buf,s,len)) return 0; /* Finally check if it fits in our ranges */ if (value >= -(1<<7) && value <= (1<<7)-1) { @@ -2914,16 +3152,16 @@ static int rdbTryIntegerEncoding(sds s, unsigned char *enc) { } } -static int rdbSaveLzfStringObject(FILE *fp, robj *obj) { - unsigned int comprlen, outlen; +static int rdbSaveLzfStringObject(FILE *fp, unsigned char *s, size_t len) { + size_t comprlen, outlen; unsigned char byte; void *out; /* We require at least four bytes compression for this to be worth it */ - outlen = sdslen(obj->ptr)-4; - if (outlen <= 0) return 0; + if (len <= 4) return 0; + outlen = len-4; if ((out = zmalloc(outlen+1)) == NULL) return 0; - comprlen = lzf_compress(obj->ptr, sdslen(obj->ptr), out, outlen); + comprlen = lzf_compress(s, len, out, outlen); if (comprlen == 0) { zfree(out); return 0; @@ -2932,7 +3170,7 @@ static int rdbSaveLzfStringObject(FILE *fp, robj *obj) { byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF; if (fwrite(&byte,1,1,fp) == 0) goto writeerr; if (rdbSaveLen(fp,comprlen) == -1) goto writeerr; - if (rdbSaveLen(fp,sdslen(obj->ptr)) == -1) goto writeerr; + if (rdbSaveLen(fp,len) == -1) goto writeerr; if (fwrite(out,comprlen,1,fp) == 0) goto writeerr; zfree(out); return comprlen; @@ -2944,16 +3182,13 @@ writeerr: /* Save a string objet as [len][data] on disk. If the object is a string * representation of an integer value we try to safe it in a special form */ -static int rdbSaveStringObjectRaw(FILE *fp, robj *obj) { - size_t len; +static int rdbSaveRawString(FILE *fp, unsigned char *s, size_t len) { int enclen; - len = sdslen(obj->ptr); - /* Try integer encoding */ if (len <= 11) { unsigned char buf[5]; - if ((enclen = rdbTryIntegerEncoding(obj->ptr,buf)) > 0) { + if ((enclen = rdbTryIntegerEncoding((char*)s,len,buf)) > 0) { if (fwrite(buf,enclen,1,fp) == 0) return -1; return 0; } @@ -2964,7 +3199,7 @@ static int rdbSaveStringObjectRaw(FILE *fp, robj *obj) { if (server.rdbcompression && len > 20) { int retval; - retval = rdbSaveLzfStringObject(fp,obj); + retval = rdbSaveLzfStringObject(fp,s,len); if (retval == -1) return -1; if (retval > 0) return 0; /* retval == 0 means data can't be compressed, save the old way */ @@ -2972,7 +3207,7 @@ static int rdbSaveStringObjectRaw(FILE *fp, robj *obj) { /* Store verbatim */ if (rdbSaveLen(fp,len) == -1) return -1; - if (len && fwrite(obj->ptr,len,1,fp) == 0) return -1; + if (len && fwrite(s,len,1,fp) == 0) return -1; return 0; } @@ -2987,10 +3222,10 @@ static int rdbSaveStringObject(FILE *fp, robj *obj) { * this in order to avoid bugs) */ if (obj->encoding != REDIS_ENCODING_RAW) { obj = getDecodedObject(obj); - retval = rdbSaveStringObjectRaw(fp,obj); + retval = rdbSaveRawString(fp,obj->ptr,sdslen(obj->ptr)); decrRefCount(obj); } else { - retval = rdbSaveStringObjectRaw(fp,obj); + retval = rdbSaveRawString(fp,obj->ptr,sdslen(obj->ptr)); } return retval; } @@ -3068,8 +3303,35 @@ static int rdbSaveObject(FILE *fp, robj *o) { if (rdbSaveDoubleValue(fp,*score) == -1) return -1; } dictReleaseIterator(di); + } else if (o->type == REDIS_HASH) { + /* Save a hash value */ + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + unsigned char *p = zipmapRewind(o->ptr); + unsigned int count = zipmapLen(o->ptr); + unsigned char *key, *val; + unsigned int klen, vlen; + + if (rdbSaveLen(fp,count) == -1) return -1; + while((p = zipmapNext(p,&key,&klen,&val,&vlen)) != NULL) { + if (rdbSaveRawString(fp,key,klen) == -1) return -1; + if (rdbSaveRawString(fp,val,vlen) == -1) return -1; + } + } else { + dictIterator *di = dictGetIterator(o->ptr); + dictEntry *de; + + if (rdbSaveLen(fp,dictSize((dict*)o->ptr)) == -1) return -1; + while((de = dictNext(di)) != NULL) { + robj *key = dictGetEntryKey(de); + robj *val = dictGetEntryVal(de); + + if (rdbSaveStringObject(fp,key) == -1) return -1; + if (rdbSaveStringObject(fp,val) == -1) return -1; + } + dictReleaseIterator(di); + } } else { - redisAssert(0 != 0); + redisAssert(0); } return 0; } @@ -3289,7 +3551,7 @@ static robj *rdbLoadIntegerObject(FILE *fp, int enctype) { val = (int32_t)v; } else { val = 0; /* anti-warning */ - redisAssert(0!=0); + redisAssert(0); } return createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",val)); } @@ -3328,7 +3590,7 @@ static robj *rdbLoadStringObject(FILE*fp) { case REDIS_RDB_ENC_LZF: return tryObjectSharing(rdbLoadLzfStringObject(fp)); default: - redisAssert(0!=0); + redisAssert(0); } } @@ -3364,6 +3626,7 @@ static int rdbLoadDoubleValue(FILE *fp, double *val) { static robj *rdbLoadObject(int type, FILE *fp) { robj *o; + redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",type,ftell(fp)); if (type == REDIS_STRING) { /* Read string value */ if ((o = rdbLoadStringObject(fp)) == NULL) return NULL; @@ -3392,7 +3655,7 @@ static robj *rdbLoadObject(int type, FILE *fp) { } } else if (type == REDIS_ZSET) { /* Read list/set value */ - uint32_t zsetlen; + size_t zsetlen; zset *zs; if ((zsetlen = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) return NULL; @@ -3410,8 +3673,46 @@ static robj *rdbLoadObject(int type, FILE *fp) { zslInsert(zs->zsl,*score,ele); incrRefCount(ele); /* added to skiplist */ } + } else if (type == REDIS_HASH) { + size_t hashlen; + + if ((hashlen = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) return NULL; + o = createHashObject(); + /* Too many entries? Use an hash table. */ + if (hashlen > server.hash_max_zipmap_entries) + convertToRealHash(o); + /* Load every key/value, then set it into the zipmap or hash + * table, as needed. */ + while(hashlen--) { + robj *key, *val; + + if ((key = rdbLoadStringObject(fp)) == NULL) return NULL; + if ((val = rdbLoadStringObject(fp)) == NULL) return NULL; + /* If we are using a zipmap and there are too big values + * the object is converted to real hash table encoding. */ + if (o->encoding != REDIS_ENCODING_HT && + (sdslen(key->ptr) > server.hash_max_zipmap_value || + sdslen(val->ptr) > server.hash_max_zipmap_value)) + { + convertToRealHash(o); + } + + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + unsigned char *zm = o->ptr; + + zm = zipmapSet(zm,key->ptr,sdslen(key->ptr), + val->ptr,sdslen(val->ptr),NULL); + o->ptr = zm; + decrRefCount(key); + decrRefCount(val); + } else { + tryObjectEncoding(key); + tryObjectEncoding(val); + dictAdd((dict*)o->ptr,key,val); + } + } } else { - redisAssert(0 != 0); + redisAssert(0); } return o; } @@ -3518,9 +3819,7 @@ static void pingCommand(redisClient *c) { } static void echoCommand(redisClient *c) { - addReplyBulkLen(c,c->argv[1]); - addReply(c,c->argv[1]); - addReply(c,shared.crlf); + addReplyBulk(c,c->argv[1]); } /*=================================== Strings =============================== */ @@ -3536,7 +3835,7 @@ static void setGenericCommand(redisClient *c, int nx) { * to overwrite the old. So we delete the old key in the database. * This will also make sure that swap pages about the old object * will be marked as free. */ - if (deleteIfSwapped(c->db,c->argv[1])) + if (server.vm_enabled && deleteIfSwapped(c->db,c->argv[1])) incrRefCount(c->argv[1]); dictReplace(c->db->dict,c->argv[1],c->argv[2]); incrRefCount(c->argv[2]); @@ -3562,21 +3861,17 @@ static void setnxCommand(redisClient *c) { } static int getGenericCommand(redisClient *c) { - robj *o = lookupKeyRead(c->db,c->argv[1]); - - if (o == NULL) { - addReply(c,shared.nullbulk); + robj *o; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) return REDIS_OK; + + if (o->type != REDIS_STRING) { + addReply(c,shared.wrongtypeerr); + return REDIS_ERR; } else { - if (o->type != REDIS_STRING) { - addReply(c,shared.wrongtypeerr); - return REDIS_ERR; - } else { - addReplyBulkLen(c,o); - addReply(c,o); - addReply(c,shared.crlf); - return REDIS_OK; - } + addReplyBulk(c,o); + return REDIS_OK; } } @@ -3608,9 +3903,7 @@ static void mgetCommand(redisClient *c) { if (o->type != REDIS_STRING) { addReply(c,shared.nullbulk); } else { - addReplyBulkLen(c,o); - addReply(c,o); - addReply(c,shared.crlf); + addReplyBulk(c,o); } } } @@ -3770,39 +4063,37 @@ static void substrCommand(redisClient *c) { robj *o; long start = atoi(c->argv[2]->ptr); long end = atoi(c->argv[3]->ptr); + size_t rangelen, strlen; + sds range; - o = lookupKeyRead(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.nullbulk); - } else { - if (o->type != REDIS_STRING) { - addReply(c,shared.wrongtypeerr); - } else { - size_t rangelen, strlen = sdslen(o->ptr); - sds range; - - /* convert negative indexes */ - if (start < 0) start = strlen+start; - if (end < 0) end = strlen+end; - if (start < 0) start = 0; - if (end < 0) end = 0; - - /* indexes sanity checks */ - if (start > end || (size_t)start >= strlen) { - /* Out of range start or start > end result in null reply */ - addReply(c,shared.nullbulk); - return; - } - if ((size_t)end >= strlen) end = strlen-1; - rangelen = (end-start)+1; + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,o,REDIS_STRING)) return; - /* Return the result */ - addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n",rangelen)); - range = sdsnewlen((char*)o->ptr+start,rangelen); - addReplySds(c,range); - addReply(c,shared.crlf); - } + o = getDecodedObject(o); + strlen = sdslen(o->ptr); + + /* convert negative indexes */ + if (start < 0) start = strlen+start; + if (end < 0) end = strlen+end; + if (start < 0) start = 0; + if (end < 0) end = 0; + + /* indexes sanity checks */ + if (start > end || (size_t)start >= strlen) { + /* Out of range start or start > end result in null reply */ + addReply(c,shared.nullbulk); + decrRefCount(o); + return; } + if ((size_t)end >= strlen) end = strlen-1; + rangelen = (end-start)+1; + + /* Return the result */ + addReplySds(c,sdscatprintf(sdsempty(),"$%zu\r\n",rangelen)); + range = sdsnewlen((char*)o->ptr+start,rangelen); + addReplySds(c,range); + addReply(c,shared.crlf); + decrRefCount(o); } /* ========================= Type agnostic commands ========================= */ @@ -3816,17 +4107,7 @@ static void delCommand(redisClient *c) { deleted++; } } - switch(deleted) { - case 0: - addReply(c,shared.czero); - break; - case 1: - addReply(c,shared.cone); - break; - default: - addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",deleted)); - break; - } + addReplyLong(c,deleted); } static void existsCommand(redisClient *c) { @@ -3878,9 +4159,7 @@ static void keysCommand(redisClient *c) { if ((pattern[0] == '*' && pattern[1] == '\0') || stringmatchlen(pattern,plen,key,sdslen(key),0)) { if (expireIfNeeded(c->db,keyobj) == 0) { - addReplyBulkLen(c,keyobj); - addReply(c,keyobj); - addReply(c,shared.crlf); + addReplyBulk(c,keyobj); numkeys++; } } @@ -3912,7 +4191,8 @@ static void typeCommand(redisClient *c) { case REDIS_LIST: type = "+list"; break; case REDIS_SET: type = "+set"; break; case REDIS_ZSET: type = "+zset"; break; - default: type = "unknown"; break; + case REDIS_HASH: type = "+hash"; break; + default: type = "+unknown"; break; } } addReplySds(c,sdsnew(type)); @@ -3969,12 +4249,14 @@ static void shutdownCommand(redisClient *c) { if (server.vm_enabled) unlink(server.vm_swap_file); exit(0); } else { - /* Ooops.. error saving! The best we can do is to continue operating. - * Note that if there was a background saving process, in the next - * cron() Redis will be notified that the background saving aborted, - * handling special stuff like slaves pending for synchronization... */ + /* Ooops.. error saving! The best we can do is to continue + * operating. Note that if there was a background saving process, + * in the next cron() Redis will be notified that the background + * saving aborted, handling special stuff like slaves pending for + * synchronization... */ redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit"); - addReplySds(c,sdsnew("-ERR can't quit, problems saving the DB\r\n")); + addReplySds(c, + sdsnew("-ERR can't quit, problems saving the DB\r\n")); } } } @@ -3988,11 +4270,9 @@ static void renameGenericCommand(redisClient *c, int nx) { return; } - o = lookupKeyWrite(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.nokeyerr); + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) return; - } + incrRefCount(o); deleteIfVolatile(c->db,c->argv[2]); if (dictAdd(c->db->dict,c->argv[2],o) == DICT_ERR) { @@ -4070,7 +4350,7 @@ static void pushGenericCommand(redisClient *c, int where) { lobj = lookupKeyWrite(c->db,c->argv[1]); if (lobj == NULL) { if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { - addReply(c,shared.ok); + addReply(c,shared.cone); return; } lobj = createListObject(); @@ -4089,7 +4369,7 @@ static void pushGenericCommand(redisClient *c, int where) { return; } if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { - addReply(c,shared.ok); + addReply(c,shared.cone); return; } list = lobj->ptr; @@ -4101,7 +4381,7 @@ static void pushGenericCommand(redisClient *c, int where) { incrRefCount(c->argv[2]); } server.dirty++; - addReply(c,shared.ok); + addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",listLength(list))); } static void lpushCommand(redisClient *c) { @@ -4115,107 +4395,79 @@ static void rpushCommand(redisClient *c) { static void llenCommand(redisClient *c) { robj *o; list *l; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_LIST)) return; - o = lookupKeyRead(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.czero); - return; - } else { - if (o->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerr); - } else { - l = o->ptr; - addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",listLength(l))); - } - } + l = o->ptr; + addReplyUlong(c,listLength(l)); } static void lindexCommand(redisClient *c) { robj *o; int index = atoi(c->argv[2]->ptr); - - o = lookupKeyRead(c->db,c->argv[1]); - if (o == NULL) { + list *list; + listNode *ln; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,o,REDIS_LIST)) return; + list = o->ptr; + + ln = listIndex(list, index); + if (ln == NULL) { addReply(c,shared.nullbulk); } else { - if (o->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerr); - } else { - list *list = o->ptr; - listNode *ln; - - ln = listIndex(list, index); - if (ln == NULL) { - addReply(c,shared.nullbulk); - } else { - robj *ele = listNodeValue(ln); - addReplyBulkLen(c,ele); - addReply(c,ele); - addReply(c,shared.crlf); - } - } + robj *ele = listNodeValue(ln); + addReplyBulk(c,ele); } } static void lsetCommand(redisClient *c) { robj *o; int index = atoi(c->argv[2]->ptr); - - o = lookupKeyWrite(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.nokeyerr); + list *list; + listNode *ln; + + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL || + checkType(c,o,REDIS_LIST)) return; + list = o->ptr; + + ln = listIndex(list, index); + if (ln == NULL) { + addReply(c,shared.outofrangeerr); } else { - if (o->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerr); - } else { - list *list = o->ptr; - listNode *ln; - - ln = listIndex(list, index); - if (ln == NULL) { - addReply(c,shared.outofrangeerr); - } else { - robj *ele = listNodeValue(ln); + robj *ele = listNodeValue(ln); - decrRefCount(ele); - listNodeValue(ln) = c->argv[3]; - incrRefCount(c->argv[3]); - addReply(c,shared.ok); - server.dirty++; - } - } + decrRefCount(ele); + listNodeValue(ln) = c->argv[3]; + incrRefCount(c->argv[3]); + addReply(c,shared.ok); + server.dirty++; } } static void popGenericCommand(redisClient *c, int where) { robj *o; + list *list; + listNode *ln; - o = lookupKeyWrite(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.nullbulk); - } else { - if (o->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerr); - } else { - list *list = o->ptr; - listNode *ln; + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,o,REDIS_LIST)) return; + list = o->ptr; - if (where == REDIS_HEAD) - ln = listFirst(list); - else - ln = listLast(list); + if (where == REDIS_HEAD) + ln = listFirst(list); + else + ln = listLast(list); - if (ln == NULL) { - addReply(c,shared.nullbulk); - } else { - robj *ele = listNodeValue(ln); - addReplyBulkLen(c,ele); - addReply(c,ele); - addReply(c,shared.crlf); - listDelNode(list,ln); - server.dirty++; - } - } + if (ln == NULL) { + addReply(c,shared.nullbulk); + } else { + robj *ele = listNodeValue(ln); + addReplyBulk(c,ele); + listDelNode(list,ln); + if (listLength(list) == 0) deleteKey(c->db,c->argv[1]); + server.dirty++; } } @@ -4231,46 +4483,39 @@ static void lrangeCommand(redisClient *c) { robj *o; int start = atoi(c->argv[2]->ptr); int end = atoi(c->argv[3]->ptr); + int llen; + int rangelen, j; + list *list; + listNode *ln; + robj *ele; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullmultibulk)) == NULL || + checkType(c,o,REDIS_LIST)) return; + list = o->ptr; + llen = listLength(list); + + /* convert negative indexes */ + if (start < 0) start = llen+start; + if (end < 0) end = llen+end; + if (start < 0) start = 0; + if (end < 0) end = 0; + + /* indexes sanity checks */ + if (start > end || start >= llen) { + /* Out of range start or start > end result in empty list */ + addReply(c,shared.emptymultibulk); + return; + } + if (end >= llen) end = llen-1; + rangelen = (end-start)+1; - o = lookupKeyRead(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.nullmultibulk); - } else { - if (o->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerr); - } else { - list *list = o->ptr; - listNode *ln; - int llen = listLength(list); - int rangelen, j; - robj *ele; - - /* convert negative indexes */ - if (start < 0) start = llen+start; - if (end < 0) end = llen+end; - if (start < 0) start = 0; - if (end < 0) end = 0; - - /* indexes sanity checks */ - if (start > end || start >= llen) { - /* Out of range start or start > end result in empty list */ - addReply(c,shared.emptymultibulk); - return; - } - if (end >= llen) end = llen-1; - rangelen = (end-start)+1; - - /* Return the result in form of a multi-bulk reply */ - ln = listIndex(list, start); - addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen)); - for (j = 0; j < rangelen; j++) { - ele = listNodeValue(ln); - addReplyBulkLen(c,ele); - addReply(c,ele); - addReply(c,shared.crlf); - ln = ln->next; - } - } + /* Return the result in form of a multi-bulk reply */ + ln = listIndex(list, start); + addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen)); + for (j = 0; j < rangelen; j++) { + ele = listNodeValue(ln); + addReplyBulk(c,ele); + ln = ln->next; } } @@ -4278,87 +4523,78 @@ static void ltrimCommand(redisClient *c) { robj *o; int start = atoi(c->argv[2]->ptr); int end = atoi(c->argv[3]->ptr); - - o = lookupKeyWrite(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.ok); - } else { - if (o->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerr); - } else { - list *list = o->ptr; - listNode *ln; - int llen = listLength(list); - int j, ltrim, rtrim; - - /* convert negative indexes */ - if (start < 0) start = llen+start; - if (end < 0) end = llen+end; - if (start < 0) start = 0; - if (end < 0) end = 0; - - /* indexes sanity checks */ - if (start > end || start >= llen) { - /* Out of range start or start > end result in empty list */ - ltrim = llen; - rtrim = 0; - } else { - if (end >= llen) end = llen-1; - ltrim = start; - rtrim = llen-end-1; - } + int llen; + int j, ltrim, rtrim; + list *list; + listNode *ln; - /* Remove list elements to perform the trim */ - for (j = 0; j < ltrim; j++) { - ln = listFirst(list); - listDelNode(list,ln); - } - for (j = 0; j < rtrim; j++) { - ln = listLast(list); - listDelNode(list,ln); - } - server.dirty++; - addReply(c,shared.ok); - } + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.ok)) == NULL || + checkType(c,o,REDIS_LIST)) return; + list = o->ptr; + llen = listLength(list); + + /* convert negative indexes */ + if (start < 0) start = llen+start; + if (end < 0) end = llen+end; + if (start < 0) start = 0; + if (end < 0) end = 0; + + /* indexes sanity checks */ + if (start > end || start >= llen) { + /* Out of range start or start > end result in empty list */ + ltrim = llen; + rtrim = 0; + } else { + if (end >= llen) end = llen-1; + ltrim = start; + rtrim = llen-end-1; } -} + + /* Remove list elements to perform the trim */ + for (j = 0; j < ltrim; j++) { + ln = listFirst(list); + listDelNode(list,ln); + } + for (j = 0; j < rtrim; j++) { + ln = listLast(list); + listDelNode(list,ln); + } + if (listLength(list) == 0) deleteKey(c->db,c->argv[1]); + server.dirty++; + addReply(c,shared.ok); +} static void lremCommand(redisClient *c) { robj *o; - - o = lookupKeyWrite(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.czero); - } else { - if (o->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerr); - } else { - list *list = o->ptr; - listNode *ln, *next; - int toremove = atoi(c->argv[2]->ptr); - int removed = 0; - int fromtail = 0; - - if (toremove < 0) { - toremove = -toremove; - fromtail = 1; - } - ln = fromtail ? list->tail : list->head; - while (ln) { - robj *ele = listNodeValue(ln); - - next = fromtail ? ln->prev : ln->next; - if (compareStringObjects(ele,c->argv[3]) == 0) { - listDelNode(list,ln); - server.dirty++; - removed++; - if (toremove && removed == toremove) break; - } - ln = next; - } - addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed)); + list *list; + listNode *ln, *next; + int toremove = atoi(c->argv[2]->ptr); + int removed = 0; + int fromtail = 0; + + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_LIST)) return; + list = o->ptr; + + if (toremove < 0) { + toremove = -toremove; + fromtail = 1; + } + ln = fromtail ? list->tail : list->head; + while (ln) { + robj *ele = listNodeValue(ln); + + next = fromtail ? ln->prev : ln->next; + if (compareStringObjects(ele,c->argv[3]) == 0) { + listDelNode(list,ln); + server.dirty++; + removed++; + if (toremove && removed == toremove) break; } + ln = next; } + if (listLength(list) == 0) deleteKey(c->db,c->argv[1]); + addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed)); } /* This is the semantic of this command: @@ -4378,57 +4614,50 @@ static void lremCommand(redisClient *c) { */ static void rpoplpushcommand(redisClient *c) { robj *sobj; + list *srclist; + listNode *ln; - sobj = lookupKeyWrite(c->db,c->argv[1]); - if (sobj == NULL) { + if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,sobj,REDIS_LIST)) return; + srclist = sobj->ptr; + ln = listLast(srclist); + + if (ln == NULL) { addReply(c,shared.nullbulk); } else { - if (sobj->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerr); - } else { - list *srclist = sobj->ptr; - listNode *ln = listLast(srclist); + robj *dobj = lookupKeyWrite(c->db,c->argv[2]); + robj *ele = listNodeValue(ln); + list *dstlist; - if (ln == NULL) { - addReply(c,shared.nullbulk); - } else { - robj *dobj = lookupKeyWrite(c->db,c->argv[2]); - robj *ele = listNodeValue(ln); - list *dstlist; - - if (dobj && dobj->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerr); - return; - } - - /* Add the element to the target list (unless it's directly - * passed to some BLPOP-ing client */ - if (!handleClientsWaitingListPush(c,c->argv[2],ele)) { - if (dobj == NULL) { - /* Create the list if the key does not exist */ - dobj = createListObject(); - dictAdd(c->db->dict,c->argv[2],dobj); - incrRefCount(c->argv[2]); - } - dstlist = dobj->ptr; - listAddNodeHead(dstlist,ele); - incrRefCount(ele); - } - - /* Send the element to the client as reply as well */ - addReplyBulkLen(c,ele); - addReply(c,ele); - addReply(c,shared.crlf); + if (dobj && dobj->type != REDIS_LIST) { + addReply(c,shared.wrongtypeerr); + return; + } - /* Finally remove the element from the source list */ - listDelNode(srclist,ln); - server.dirty++; + /* Add the element to the target list (unless it's directly + * passed to some BLPOP-ing client */ + if (!handleClientsWaitingListPush(c,c->argv[2],ele)) { + if (dobj == NULL) { + /* Create the list if the key does not exist */ + dobj = createListObject(); + dictAdd(c->db->dict,c->argv[2],dobj); + incrRefCount(c->argv[2]); } + dstlist = dobj->ptr; + listAddNodeHead(dstlist,ele); + incrRefCount(ele); } + + /* Send the element to the client as reply as well */ + addReplyBulk(c,ele); + + /* Finally remove the element from the source list */ + listDelNode(srclist,ln); + if (listLength(srclist) == 0) deleteKey(c->db,c->argv[1]); + server.dirty++; } } - /* ==================================== Sets ================================ */ static void saddCommand(redisClient *c) { @@ -4457,21 +4686,16 @@ static void saddCommand(redisClient *c) { static void sremCommand(redisClient *c) { robj *set; - set = lookupKeyWrite(c->db,c->argv[1]); - if (set == NULL) { - addReply(c,shared.czero); + if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,set,REDIS_SET)) return; + + if (dictDelete(set->ptr,c->argv[2]) == DICT_OK) { + server.dirty++; + if (htNeedsResize(set->ptr)) dictResize(set->ptr); + if (dictSize((dict*)set->ptr) == 0) deleteKey(c->db,c->argv[1]); + addReply(c,shared.cone); } else { - if (set->type != REDIS_SET) { - addReply(c,shared.wrongtypeerr); - return; - } - if (dictDelete(set->ptr,c->argv[2]) == DICT_OK) { - server.dirty++; - if (htNeedsResize(set->ptr)) dictResize(set->ptr); - addReply(c,shared.cone); - } else { - addReply(c,shared.czero); - } + addReply(c,shared.czero); } } @@ -4498,6 +4722,8 @@ static void smoveCommand(redisClient *c) { addReply(c,shared.czero); return; } + if (dictSize((dict*)srcset->ptr) == 0 && srcset != dstset) + deleteKey(c->db,c->argv[1]); server.dirty++; /* Add the element to the destination set */ if (!dstset) { @@ -4513,65 +4739,44 @@ static void smoveCommand(redisClient *c) { static void sismemberCommand(redisClient *c) { robj *set; - set = lookupKeyRead(c->db,c->argv[1]); - if (set == NULL) { + if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,set,REDIS_SET)) return; + + if (dictFind(set->ptr,c->argv[2])) + addReply(c,shared.cone); + else addReply(c,shared.czero); - } else { - if (set->type != REDIS_SET) { - addReply(c,shared.wrongtypeerr); - return; - } - if (dictFind(set->ptr,c->argv[2])) - addReply(c,shared.cone); - else - addReply(c,shared.czero); - } } static void scardCommand(redisClient *c) { robj *o; dict *s; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_SET)) return; - o = lookupKeyRead(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.czero); - return; - } else { - if (o->type != REDIS_SET) { - addReply(c,shared.wrongtypeerr); - } else { - s = o->ptr; - addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n", - dictSize(s))); - } - } + s = o->ptr; + addReplyUlong(c,dictSize(s)); } static void spopCommand(redisClient *c) { robj *set; dictEntry *de; - set = lookupKeyWrite(c->db,c->argv[1]); - if (set == NULL) { + if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,set,REDIS_SET)) return; + + de = dictGetRandomKey(set->ptr); + if (de == NULL) { addReply(c,shared.nullbulk); } else { - if (set->type != REDIS_SET) { - addReply(c,shared.wrongtypeerr); - return; - } - de = dictGetRandomKey(set->ptr); - if (de == NULL) { - addReply(c,shared.nullbulk); - } else { - robj *ele = dictGetEntryKey(de); + robj *ele = dictGetEntryKey(de); - addReplyBulkLen(c,ele); - addReply(c,ele); - addReply(c,shared.crlf); - dictDelete(set->ptr,ele); - if (htNeedsResize(set->ptr)) dictResize(set->ptr); - server.dirty++; - } + addReplyBulk(c,ele); + dictDelete(set->ptr,ele); + if (htNeedsResize(set->ptr)) dictResize(set->ptr); + if (dictSize((dict*)set->ptr) == 0) deleteKey(c->db,c->argv[1]); + server.dirty++; } } @@ -4579,24 +4784,16 @@ static void srandmemberCommand(redisClient *c) { robj *set; dictEntry *de; - set = lookupKeyRead(c->db,c->argv[1]); - if (set == NULL) { + if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,set,REDIS_SET)) return; + + de = dictGetRandomKey(set->ptr); + if (de == NULL) { addReply(c,shared.nullbulk); } else { - if (set->type != REDIS_SET) { - addReply(c,shared.wrongtypeerr); - return; - } - de = dictGetRandomKey(set->ptr); - if (de == NULL) { - addReply(c,shared.nullbulk); - } else { - robj *ele = dictGetEntryKey(de); + robj *ele = dictGetEntryKey(de); - addReplyBulkLen(c,ele); - addReply(c,ele); - addReply(c,shared.crlf); - } + addReplyBulk(c,ele); } } @@ -4670,9 +4867,7 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long continue; /* at least one set does not contain the member */ ele = dictGetEntryKey(de); if (!dstkey) { - addReplyBulkLen(c,ele); - addReply(c,ele); - addReply(c,shared.crlf); + addReplyBulk(c,ele); cardinality++; } else { dictAdd(dstset->ptr,ele,NULL); @@ -4682,18 +4877,20 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long dictReleaseIterator(di); if (dstkey) { - /* Store the resulting set into the target */ + /* Store the resulting set into the target, if the intersection + * is not an empty set. */ deleteKey(c->db,dstkey); - dictAdd(c->db->dict,dstkey,dstset); - incrRefCount(dstkey); - } - - if (!dstkey) { - lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",cardinality); - } else { - addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n", - dictSize((dict*)dstset->ptr))); + if (dictSize((dict*)dstset->ptr) > 0) { + dictAdd(c->db->dict,dstkey,dstset); + incrRefCount(dstkey); + addReplyLong(c,dictSize((dict*)dstset->ptr)); + } else { + decrRefCount(dstset); + addReply(c,shared.czero); + } server.dirty++; + } else { + lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",cardinality); } zfree(dv); } @@ -4708,6 +4905,7 @@ static void sinterstoreCommand(redisClient *c) { #define REDIS_OP_UNION 0 #define REDIS_OP_DIFF 1 +#define REDIS_OP_INTER 2 static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnum, robj *dstkey, int op) { dict **dv = zmalloc(sizeof(dict*)*setsnum); @@ -4765,7 +4963,8 @@ static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnu } dictReleaseIterator(di); - if (op == REDIS_OP_DIFF && cardinality == 0) break; /* result set is empty */ + /* result set is empty? Exit asap. */ + if (op == REDIS_OP_DIFF && cardinality == 0) break; } /* Output the content of the resulting set, if not in STORE mode */ @@ -4776,25 +4975,22 @@ static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnu robj *ele; ele = dictGetEntryKey(de); - addReplyBulkLen(c,ele); - addReply(c,ele); - addReply(c,shared.crlf); + addReplyBulk(c,ele); } dictReleaseIterator(di); + decrRefCount(dstset); } else { /* If we have a target key where to store the resulting set * create this key with the result set inside */ deleteKey(c->db,dstkey); - dictAdd(c->db->dict,dstkey,dstset); - incrRefCount(dstkey); - } - - /* Cleanup */ - if (!dstkey) { - decrRefCount(dstset); - } else { - addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n", - dictSize((dict*)dstset->ptr))); + if (dictSize((dict*)dstset->ptr) > 0) { + dictAdd(c->db->dict,dstkey,dstset); + incrRefCount(dstkey); + addReplyLong(c,dictSize((dict*)dstset->ptr)); + } else { + decrRefCount(dstset); + addReply(c,shared.czero); + } server.dirty++; } zfree(dv); @@ -4839,7 +5035,8 @@ static zskiplistNode *zslCreateNode(int level, double score, robj *obj) { zskiplistNode *zn = zmalloc(sizeof(*zn)); zn->forward = zmalloc(sizeof(zskiplistNode*) * level); - zn->span = zmalloc(sizeof(unsigned int) * level); + if (level > 0) + zn->span = zmalloc(sizeof(unsigned int) * (level - 1)); zn->score = score; zn->obj = obj; return zn; @@ -4855,7 +5052,10 @@ static zskiplist *zslCreate(void) { zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL); for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) { zsl->header->forward[j] = NULL; - zsl->header->span[j] = 0; + + /* span has space for ZSKIPLIST_MAXLEVEL-1 elements */ + if (j < ZSKIPLIST_MAXLEVEL-1) + zsl->header->span[j] = 0; } zsl->header->backward = NULL; zsl->tail = NULL; @@ -4892,19 +5092,19 @@ static int zslRandomLevel(void) { static void zslInsert(zskiplist *zsl, double score, robj *obj) { zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; - unsigned int span[ZSKIPLIST_MAXLEVEL]; + unsigned int rank[ZSKIPLIST_MAXLEVEL]; int i, level; x = zsl->header; for (i = zsl->level-1; i >= 0; i--) { - /* store span that is crossed to reach the insert position */ - span[i] = i == (zsl->level-1) ? 0 : span[i+1]; + /* store rank that is crossed to reach the insert position */ + rank[i] = i == (zsl->level-1) ? 0 : rank[i+1]; while (x->forward[i] && (x->forward[i]->score < score || (x->forward[i]->score == score && compareStringObjects(x->forward[i]->obj,obj) < 0))) { - span[i] += x->span[i]; + rank[i] += i > 0 ? x->span[i-1] : 1; x = x->forward[i]; } update[i] = x; @@ -4916,9 +5116,9 @@ static void zslInsert(zskiplist *zsl, double score, robj *obj) { level = zslRandomLevel(); if (level > zsl->level) { for (i = zsl->level; i < level; i++) { - span[i] = 0; + rank[i] = 0; update[i] = zsl->header; - update[i]->span[i] = zsl->length; + update[i]->span[i-1] = zsl->length; } zsl->level = level; } @@ -4928,13 +5128,15 @@ static void zslInsert(zskiplist *zsl, double score, robj *obj) { update[i]->forward[i] = x; /* update span covered by update[i] as x is inserted here */ - x->span[i] = update[i]->span[i] - (span[0] - span[i]); - update[i]->span[i] = (span[0] - span[i]) + 1; + if (i > 0) { + x->span[i-1] = update[i]->span[i-1] - (rank[0] - rank[i]); + update[i]->span[i-1] = (rank[0] - rank[i]) + 1; + } } /* increment span for untouched levels */ for (i = level; i < zsl->level; i++) { - update[i]->span[i]++; + update[i]->span[i-1]++; } x->backward = (update[0] == zsl->header) ? NULL : update[0]; @@ -4945,6 +5147,31 @@ static void zslInsert(zskiplist *zsl, double score, robj *obj) { zsl->length++; } +/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */ +void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) { + int i; + for (i = 0; i < zsl->level; i++) { + if (update[i]->forward[i] == x) { + if (i > 0) { + update[i]->span[i-1] += x->span[i-1] - 1; + } + update[i]->forward[i] = x->forward[i]; + } else { + /* invariant: i > 0, because update[0]->forward[0] + * is always equal to x */ + update[i]->span[i-1] -= 1; + } + } + if (x->forward[0]) { + x->forward[0]->backward = x->backward; + } else { + zsl->tail = x->backward; + } + while(zsl->level > 1 && zsl->header->forward[zsl->level-1] == NULL) + zsl->level--; + zsl->length--; +} + /* Delete an element with matching score/object from the skiplist. */ static int zslDelete(zskiplist *zsl, double score, robj *obj) { zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; @@ -4963,23 +5190,8 @@ static int zslDelete(zskiplist *zsl, double score, robj *obj) { * is to find the element with both the right score and object. */ x = x->forward[0]; if (x && score == x->score && compareStringObjects(x->obj,obj) == 0) { - for (i = 0; i < zsl->level; i++) { - if (update[i]->forward[i] == x) { - update[i]->span[i] += x->span[i] - 1; - update[i]->forward[i] = x->forward[i]; - } else { - update[i]->span[i] -= 1; - } - } - if (x->forward[0]) { - x->forward[0]->backward = x->backward; - } else { - zsl->tail = x->backward; - } + zslDeleteNode(zsl, x, update); zslFreeNode(x); - while(zsl->level > 1 && zsl->header->forward[zsl->level-1] == NULL) - zsl->level--; - zsl->length--; return 1; } else { return 0; /* not found */ @@ -4991,7 +5203,7 @@ static int zslDelete(zskiplist *zsl, double score, robj *obj) { * Min and mx are inclusive, so a score >= min || score <= max is deleted. * Note that this function takes the reference to the hash table view of the * sorted set, in order to remove the elements from the hash table too. */ -static unsigned long zslDeleteRange(zskiplist *zsl, double min, double max, dict *dict) { +static unsigned long zslDeleteRangeByScore(zskiplist *zsl, double min, double max, dict *dict) { zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; unsigned long removed = 0; int i; @@ -5006,31 +5218,44 @@ static unsigned long zslDeleteRange(zskiplist *zsl, double min, double max, dict * is to find the element with both the right score and object. */ x = x->forward[0]; while (x && x->score <= max) { - zskiplistNode *next; + zskiplistNode *next = x->forward[0]; + zslDeleteNode(zsl, x, update); + dictDelete(dict,x->obj); + zslFreeNode(x); + removed++; + x = next; + } + return removed; /* not found */ +} - for (i = 0; i < zsl->level; i++) { - if (update[i]->forward[i] == x) { - update[i]->span[i] += x->span[i] - 1; - update[i]->forward[i] = x->forward[i]; - } else { - update[i]->span[i] -= 1; - } - } - if (x->forward[0]) { - x->forward[0]->backward = x->backward; - } else { - zsl->tail = x->backward; +/* Delete all the elements with rank between start and end from the skiplist. + * Start and end are inclusive. Note that start and end need to be 1-based */ +static unsigned long zslDeleteRangeByRank(zskiplist *zsl, unsigned int start, unsigned int end, dict *dict) { + zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; + unsigned long traversed = 0, removed = 0; + int i; + + x = zsl->header; + for (i = zsl->level-1; i >= 0; i--) { + while (x->forward[i] && (traversed + (i > 0 ? x->span[i-1] : 1)) < start) { + traversed += i > 0 ? x->span[i-1] : 1; + x = x->forward[i]; } - next = x->forward[0]; + update[i] = x; + } + + traversed++; + x = x->forward[0]; + while (x && traversed <= end) { + zskiplistNode *next = x->forward[0]; + zslDeleteNode(zsl, x, update); dictDelete(dict,x->obj); zslFreeNode(x); - while(zsl->level > 1 && zsl->header->forward[zsl->level-1] == NULL) - zsl->level--; - zsl->length--; removed++; + traversed++; x = next; } - return removed; /* not found */ + return removed; } /* Find the first node having a score equal or greater than the specified one. @@ -5049,6 +5274,53 @@ static zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) { return x->forward[0]; } +/* Find the rank for an element by both score and key. + * Returns 0 when the element cannot be found, rank otherwise. + * Note that the rank is 1-based due to the span of zsl->header to the + * first element. */ +static unsigned long zslGetRank(zskiplist *zsl, double score, robj *o) { + zskiplistNode *x; + unsigned long rank = 0; + int i; + + x = zsl->header; + for (i = zsl->level-1; i >= 0; i--) { + while (x->forward[i] && + (x->forward[i]->score < score || + (x->forward[i]->score == score && + compareStringObjects(x->forward[i]->obj,o) <= 0))) { + rank += i > 0 ? x->span[i-1] : 1; + x = x->forward[i]; + } + + /* x might be equal to zsl->header, so test if obj is non-NULL */ + if (x->obj && compareStringObjects(x->obj,o) == 0) { + return rank; + } + } + return 0; +} + +/* Finds an element by its rank. The rank argument needs to be 1-based. */ +zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) { + zskiplistNode *x; + unsigned long traversed = 0; + int i; + + x = zsl->header; + for (i = zsl->level-1; i >= 0; i--) { + while (x->forward[i] && (traversed + (i>0 ? x->span[i-1] : 1)) <= rank) + { + traversed += i > 0 ? x->span[i-1] : 1; + x = x->forward[i]; + } + if (traversed == rank) { + return x; + } + } + return NULL; +} + /* The actual Z-commands implementations */ /* This generic command implements both ZADD and ZINCRBY. @@ -5149,60 +5421,282 @@ static void zincrbyCommand(redisClient *c) { static void zremCommand(redisClient *c) { robj *zsetobj; zset *zs; + dictEntry *de; + double *oldscore; + int deleted; - zsetobj = lookupKeyWrite(c->db,c->argv[1]); - if (zsetobj == NULL) { - addReply(c,shared.czero); - } else { - dictEntry *de; - double *oldscore; - int deleted; - - if (zsetobj->type != REDIS_ZSET) { - addReply(c,shared.wrongtypeerr); - return; - } - zs = zsetobj->ptr; - de = dictFind(zs->dict,c->argv[2]); - if (de == NULL) { - addReply(c,shared.czero); - return; - } - /* Delete from the skiplist */ - oldscore = dictGetEntryVal(de); - deleted = zslDelete(zs->zsl,*oldscore,c->argv[2]); - redisAssert(deleted != 0); + if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,zsetobj,REDIS_ZSET)) return; - /* Delete from the hash table */ - dictDelete(zs->dict,c->argv[2]); - if (htNeedsResize(zs->dict)) dictResize(zs->dict); - server.dirty++; - addReply(c,shared.cone); + zs = zsetobj->ptr; + de = dictFind(zs->dict,c->argv[2]); + if (de == NULL) { + addReply(c,shared.czero); + return; } + /* Delete from the skiplist */ + oldscore = dictGetEntryVal(de); + deleted = zslDelete(zs->zsl,*oldscore,c->argv[2]); + redisAssert(deleted != 0); + + /* Delete from the hash table */ + dictDelete(zs->dict,c->argv[2]); + if (htNeedsResize(zs->dict)) dictResize(zs->dict); + if (dictSize(zs->dict) == 0) deleteKey(c->db,c->argv[1]); + server.dirty++; + addReply(c,shared.cone); } static void zremrangebyscoreCommand(redisClient *c) { double min = strtod(c->argv[2]->ptr,NULL); double max = strtod(c->argv[3]->ptr,NULL); + long deleted; robj *zsetobj; zset *zs; - zsetobj = lookupKeyWrite(c->db,c->argv[1]); - if (zsetobj == NULL) { + if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,zsetobj,REDIS_ZSET)) return; + + zs = zsetobj->ptr; + deleted = zslDeleteRangeByScore(zs->zsl,min,max,zs->dict); + if (htNeedsResize(zs->dict)) dictResize(zs->dict); + if (dictSize(zs->dict) == 0) deleteKey(c->db,c->argv[1]); + server.dirty += deleted; + addReplyLong(c,deleted); +} + +static void zremrangebyrankCommand(redisClient *c) { + int start = atoi(c->argv[2]->ptr); + int end = atoi(c->argv[3]->ptr); + int llen; + long deleted; + robj *zsetobj; + zset *zs; + + if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,zsetobj,REDIS_ZSET)) return; + zs = zsetobj->ptr; + llen = zs->zsl->length; + + /* convert negative indexes */ + if (start < 0) start = llen+start; + if (end < 0) end = llen+end; + if (start < 0) start = 0; + if (end < 0) end = 0; + + /* indexes sanity checks */ + if (start > end || start >= llen) { addReply(c,shared.czero); + return; + } + if (end >= llen) end = llen-1; + + /* increment start and end because zsl*Rank functions + * use 1-based rank */ + deleted = zslDeleteRangeByRank(zs->zsl,start+1,end+1,zs->dict); + if (htNeedsResize(zs->dict)) dictResize(zs->dict); + if (dictSize(zs->dict) == 0) deleteKey(c->db,c->argv[1]); + server.dirty += deleted; + addReplyLong(c, deleted); +} + +typedef struct { + dict *dict; + double weight; +} zsetopsrc; + +static int qsortCompareZsetopsrcByCardinality(const void *s1, const void *s2) { + zsetopsrc *d1 = (void*) s1, *d2 = (void*) s2; + unsigned long size1, size2; + size1 = d1->dict ? dictSize(d1->dict) : 0; + size2 = d2->dict ? dictSize(d2->dict) : 0; + return size1 - size2; +} + +#define REDIS_AGGR_SUM 1 +#define REDIS_AGGR_MIN 2 +#define REDIS_AGGR_MAX 3 + +inline static void zunionInterAggregate(double *target, double val, int aggregate) { + if (aggregate == REDIS_AGGR_SUM) { + *target = *target + val; + } else if (aggregate == REDIS_AGGR_MIN) { + *target = val < *target ? val : *target; + } else if (aggregate == REDIS_AGGR_MAX) { + *target = val > *target ? val : *target; } else { - long deleted; + /* safety net */ + redisAssert(0 != 0); + } +} - if (zsetobj->type != REDIS_ZSET) { - addReply(c,shared.wrongtypeerr); - return; +static void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) { + int i, j, zsetnum; + int aggregate = REDIS_AGGR_SUM; + zsetopsrc *src; + robj *dstobj; + zset *dstzset; + dictIterator *di; + dictEntry *de; + + /* expect zsetnum input keys to be given */ + zsetnum = atoi(c->argv[2]->ptr); + if (zsetnum < 1) { + addReplySds(c,sdsnew("-ERR at least 1 input key is needed for ZUNION/ZINTER\r\n")); + return; + } + + /* test if the expected number of keys would overflow */ + if (3+zsetnum > c->argc) { + addReply(c,shared.syntaxerr); + return; + } + + /* read keys to be used for input */ + src = zmalloc(sizeof(zsetopsrc) * zsetnum); + for (i = 0, j = 3; i < zsetnum; i++, j++) { + robj *zsetobj = lookupKeyWrite(c->db,c->argv[j]); + if (!zsetobj) { + src[i].dict = NULL; + } else { + if (zsetobj->type != REDIS_ZSET) { + zfree(src); + addReply(c,shared.wrongtypeerr); + return; + } + src[i].dict = ((zset*)zsetobj->ptr)->dict; + } + + /* default all weights to 1 */ + src[i].weight = 1.0; + } + + /* parse optional extra arguments */ + if (j < c->argc) { + int remaining = c->argc - j; + + while (remaining) { + if (remaining >= (zsetnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) { + j++; remaining--; + for (i = 0; i < zsetnum; i++, j++, remaining--) { + src[i].weight = strtod(c->argv[j]->ptr, NULL); + } + } else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) { + j++; remaining--; + if (!strcasecmp(c->argv[j]->ptr,"sum")) { + aggregate = REDIS_AGGR_SUM; + } else if (!strcasecmp(c->argv[j]->ptr,"min")) { + aggregate = REDIS_AGGR_MIN; + } else if (!strcasecmp(c->argv[j]->ptr,"max")) { + aggregate = REDIS_AGGR_MAX; + } else { + zfree(src); + addReply(c,shared.syntaxerr); + return; + } + j++; remaining--; + } else { + zfree(src); + addReply(c,shared.syntaxerr); + return; + } } - zs = zsetobj->ptr; - deleted = zslDeleteRange(zs->zsl,min,max,zs->dict); - if (htNeedsResize(zs->dict)) dictResize(zs->dict); - server.dirty += deleted; - addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n",deleted)); } + + /* sort sets from the smallest to largest, this will improve our + * algorithm's performance */ + qsort(src,zsetnum,sizeof(zsetopsrc), qsortCompareZsetopsrcByCardinality); + + dstobj = createZsetObject(); + dstzset = dstobj->ptr; + + if (op == REDIS_OP_INTER) { + /* skip going over all entries if the smallest zset is NULL or empty */ + if (src[0].dict && dictSize(src[0].dict) > 0) { + /* precondition: as src[0].dict is non-empty and the zsets are ordered + * from small to large, all src[i > 0].dict are non-empty too */ + di = dictGetIterator(src[0].dict); + while((de = dictNext(di)) != NULL) { + double *score = zmalloc(sizeof(double)), value; + *score = src[0].weight * (*(double*)dictGetEntryVal(de)); + + for (j = 1; j < zsetnum; j++) { + dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de)); + if (other) { + value = src[j].weight * (*(double*)dictGetEntryVal(other)); + zunionInterAggregate(score, value, aggregate); + } else { + break; + } + } + + /* skip entry when not present in every source dict */ + if (j != zsetnum) { + zfree(score); + } else { + robj *o = dictGetEntryKey(de); + dictAdd(dstzset->dict,o,score); + incrRefCount(o); /* added to dictionary */ + zslInsert(dstzset->zsl,*score,o); + incrRefCount(o); /* added to skiplist */ + } + } + dictReleaseIterator(di); + } + } else if (op == REDIS_OP_UNION) { + for (i = 0; i < zsetnum; i++) { + if (!src[i].dict) continue; + + di = dictGetIterator(src[i].dict); + while((de = dictNext(di)) != NULL) { + /* skip key when already processed */ + if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL) continue; + + double *score = zmalloc(sizeof(double)), value; + *score = src[i].weight * (*(double*)dictGetEntryVal(de)); + + /* because the zsets are sorted by size, its only possible + * for sets at larger indices to hold this entry */ + for (j = (i+1); j < zsetnum; j++) { + dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de)); + if (other) { + value = src[j].weight * (*(double*)dictGetEntryVal(other)); + zunionInterAggregate(score, value, aggregate); + } + } + + robj *o = dictGetEntryKey(de); + dictAdd(dstzset->dict,o,score); + incrRefCount(o); /* added to dictionary */ + zslInsert(dstzset->zsl,*score,o); + incrRefCount(o); /* added to skiplist */ + } + dictReleaseIterator(di); + } + } else { + /* unknown operator */ + redisAssert(op == REDIS_OP_INTER || op == REDIS_OP_UNION); + } + + deleteKey(c->db,dstkey); + if (dstzset->zsl->length) { + dictAdd(c->db->dict,dstkey,dstobj); + incrRefCount(dstkey); + addReplyLong(c, dstzset->zsl->length); + server.dirty++; + } else { + decrRefCount(dstzset); + addReply(c, shared.czero); + } + zfree(src); +} + +static void zunionCommand(redisClient *c) { + zunionInterGenericCommand(c,c->argv[1], REDIS_OP_UNION); +} + +static void zinterCommand(redisClient *c) { + zunionInterGenericCommand(c,c->argv[1], REDIS_OP_INTER); } static void zrangeGenericCommand(redisClient *c, int reverse) { @@ -5210,6 +5704,12 @@ static void zrangeGenericCommand(redisClient *c, int reverse) { int start = atoi(c->argv[2]->ptr); int end = atoi(c->argv[3]->ptr); int withscores = 0; + int llen; + int rangelen, j; + zset *zsetobj; + zskiplist *zsl; + zskiplistNode *ln; + robj *ele; if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) { withscores = 1; @@ -5218,59 +5718,45 @@ static void zrangeGenericCommand(redisClient *c, int reverse) { return; } - o = lookupKeyRead(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.nullmultibulk); - } else { - if (o->type != REDIS_ZSET) { - addReply(c,shared.wrongtypeerr); - } else { - zset *zsetobj = o->ptr; - zskiplist *zsl = zsetobj->zsl; - zskiplistNode *ln; + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullmultibulk)) == NULL || + checkType(c,o,REDIS_ZSET)) return; + zsetobj = o->ptr; + zsl = zsetobj->zsl; + llen = zsl->length; - int llen = zsl->length; - int rangelen, j; - robj *ele; + /* convert negative indexes */ + if (start < 0) start = llen+start; + if (end < 0) end = llen+end; + if (start < 0) start = 0; + if (end < 0) end = 0; - /* convert negative indexes */ - if (start < 0) start = llen+start; - if (end < 0) end = llen+end; - if (start < 0) start = 0; - if (end < 0) end = 0; + /* indexes sanity checks */ + if (start > end || start >= llen) { + /* Out of range start or start > end result in empty list */ + addReply(c,shared.emptymultibulk); + return; + } + if (end >= llen) end = llen-1; + rangelen = (end-start)+1; - /* indexes sanity checks */ - if (start > end || start >= llen) { - /* Out of range start or start > end result in empty list */ - addReply(c,shared.emptymultibulk); - return; - } - if (end >= llen) end = llen-1; - rangelen = (end-start)+1; - - /* Return the result in form of a multi-bulk reply */ - if (reverse) { - ln = zsl->tail; - while (start--) - ln = ln->backward; - } else { - ln = zsl->header->forward[0]; - while (start--) - ln = ln->forward[0]; - } + /* check if starting point is trivial, before searching + * the element in log(N) time */ + if (reverse) { + ln = start == 0 ? zsl->tail : zslGetElementByRank(zsl, llen-start); + } else { + ln = start == 0 ? + zsl->header->forward[0] : zslGetElementByRank(zsl, start+1); + } - addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n", - withscores ? (rangelen*2) : rangelen)); - for (j = 0; j < rangelen; j++) { - ele = ln->obj; - addReplyBulkLen(c,ele); - addReply(c,ele); - addReply(c,shared.crlf); - if (withscores) - addReplyDouble(c,ln->score); - ln = reverse ? ln->backward : ln->forward[0]; - } - } + /* Return the result in form of a multi-bulk reply */ + addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n", + withscores ? (rangelen*2) : rangelen)); + for (j = 0; j < rangelen; j++) { + ele = ln->obj; + addReplyBulk(c,ele); + if (withscores) + addReplyDouble(c,ln->score); + ln = reverse ? ln->backward : ln->forward[0]; } } @@ -5380,9 +5866,7 @@ static void genericZrangebyscoreCommand(redisClient *c, int justcount) { if (limit == 0) break; if (!justcount) { ele = ln->obj; - addReplyBulkLen(c,ele); - addReply(c,ele); - addReply(c,shared.crlf); + addReplyBulk(c,ele); if (withscores) addReplyDouble(c,ln->score); } @@ -5404,97 +5888,394 @@ static void zrangebyscoreCommand(redisClient *c) { genericZrangebyscoreCommand(c,0); } -static void zcountCommand(redisClient *c) { - genericZrangebyscoreCommand(c,1); +static void zcountCommand(redisClient *c) { + genericZrangebyscoreCommand(c,1); +} + +static void zcardCommand(redisClient *c) { + robj *o; + zset *zs; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_ZSET)) return; + + zs = o->ptr; + addReplyUlong(c,zs->zsl->length); +} + +static void zscoreCommand(redisClient *c) { + robj *o; + zset *zs; + dictEntry *de; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,o,REDIS_ZSET)) return; + + zs = o->ptr; + de = dictFind(zs->dict,c->argv[2]); + if (!de) { + addReply(c,shared.nullbulk); + } else { + double *score = dictGetEntryVal(de); + + addReplyDouble(c,*score); + } +} + +static void zrankGenericCommand(redisClient *c, int reverse) { + robj *o; + zset *zs; + zskiplist *zsl; + dictEntry *de; + unsigned long rank; + double *score; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,o,REDIS_ZSET)) return; + + zs = o->ptr; + zsl = zs->zsl; + de = dictFind(zs->dict,c->argv[2]); + if (!de) { + addReply(c,shared.nullbulk); + return; + } + + score = dictGetEntryVal(de); + rank = zslGetRank(zsl, *score, c->argv[2]); + if (rank) { + if (reverse) { + addReplyLong(c, zsl->length - rank); + } else { + addReplyLong(c, rank-1); + } + } else { + addReply(c,shared.nullbulk); + } +} + +static void zrankCommand(redisClient *c) { + zrankGenericCommand(c, 0); +} + +static void zrevrankCommand(redisClient *c) { + zrankGenericCommand(c, 1); +} + +/* =================================== Hashes =============================== */ +static void hsetCommand(redisClient *c) { + int update = 0; + robj *o = lookupKeyWrite(c->db,c->argv[1]); + + if (o == NULL) { + o = createHashObject(); + dictAdd(c->db->dict,c->argv[1],o); + incrRefCount(c->argv[1]); + } else { + if (o->type != REDIS_HASH) { + addReply(c,shared.wrongtypeerr); + return; + } + } + /* We want to convert the zipmap into an hash table right now if the + * entry to be added is too big. Note that we check if the object + * is integer encoded before to try fetching the length in the test below. + * This is because integers are small, but currently stringObjectLen() + * performs a slow conversion: not worth it. */ + if (o->encoding == REDIS_ENCODING_ZIPMAP && + ((c->argv[2]->encoding == REDIS_ENCODING_RAW && + sdslen(c->argv[2]->ptr) > server.hash_max_zipmap_value) || + (c->argv[3]->encoding == REDIS_ENCODING_RAW && + sdslen(c->argv[3]->ptr) > server.hash_max_zipmap_value))) + { + convertToRealHash(o); + } + + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + unsigned char *zm = o->ptr; + robj *valobj = getDecodedObject(c->argv[3]); + + zm = zipmapSet(zm,c->argv[2]->ptr,sdslen(c->argv[2]->ptr), + valobj->ptr,sdslen(valobj->ptr),&update); + decrRefCount(valobj); + o->ptr = zm; + + /* And here there is the second check for hash conversion... + * we want to do it only if the operation was not just an update as + * zipmapLen() is O(N). */ + if (!update && zipmapLen(zm) > server.hash_max_zipmap_entries) + convertToRealHash(o); + } else { + tryObjectEncoding(c->argv[2]); + /* note that c->argv[3] is already encoded, as the latest arg + * of a bulk command is always integer encoded if possible. */ + if (dictReplace(o->ptr,c->argv[2],c->argv[3])) { + incrRefCount(c->argv[2]); + } else { + update = 1; + } + incrRefCount(c->argv[3]); + } + server.dirty++; + addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",update == 0)); +} + +static void hincrbyCommand(redisClient *c) { + int update = 0; + long long value = 0, incr = 0; + robj *o = lookupKeyWrite(c->db,c->argv[1]); + + if (o == NULL) { + o = createHashObject(); + dictAdd(c->db->dict,c->argv[1],o); + incrRefCount(c->argv[1]); + } else { + if (o->type != REDIS_HASH) { + addReply(c,shared.wrongtypeerr); + return; + } + } + + robj *o_incr = getDecodedObject(c->argv[3]); + incr = strtoll(o_incr->ptr, NULL, 10); + decrRefCount(o_incr); + + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + unsigned char *zm = o->ptr; + unsigned char *zval; + unsigned int zvlen; + + /* Find value if already present in hash */ + if (zipmapGet(zm,c->argv[2]->ptr,sdslen(c->argv[2]->ptr), + &zval,&zvlen)) { + /* strtoll needs the char* to have a trailing \0, but + * the zipmap doesn't include them. */ + sds szval = sdsnewlen(zval, zvlen); + value = strtoll(szval,NULL,10); + sdsfree(szval); + } + + value += incr; + sds svalue = sdscatprintf(sdsempty(),"%lld",value); + zm = zipmapSet(zm,c->argv[2]->ptr,sdslen(c->argv[2]->ptr), + (unsigned char*)svalue,sdslen(svalue),&update); + sdsfree(svalue); + o->ptr = zm; + + /* Check if the zipmap needs to be converted + * if this was not an update. */ + if (!update && zipmapLen(zm) > server.hash_max_zipmap_entries) + convertToRealHash(o); + } else { + robj *hval; + dictEntry *de; + + /* Find value if already present in hash */ + de = dictFind(o->ptr,c->argv[2]); + if (de != NULL) { + hval = dictGetEntryVal(de); + if (hval->encoding == REDIS_ENCODING_RAW) + value = strtoll(hval->ptr,NULL,10); + else if (hval->encoding == REDIS_ENCODING_INT) + value = (long)hval->ptr; + else + redisAssert(1 != 1); + } + + value += incr; + hval = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value)); + tryObjectEncoding(hval); + if (dictReplace(o->ptr,c->argv[2],hval)) { + incrRefCount(c->argv[2]); + } + } + + server.dirty++; + addReplyLong(c, value); } -static void zcardCommand(redisClient *c) { +static void hgetCommand(redisClient *c) { robj *o; - zset *zs; - - o = lookupKeyRead(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.czero); - return; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,o,REDIS_HASH)) return; + + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + unsigned char *zm = o->ptr; + unsigned char *val; + unsigned int vlen; + robj *field; + + field = getDecodedObject(c->argv[2]); + if (zipmapGet(zm,field->ptr,sdslen(field->ptr), &val,&vlen)) { + addReplySds(c,sdscatprintf(sdsempty(),"$%u\r\n", vlen)); + addReplySds(c,sdsnewlen(val,vlen)); + addReply(c,shared.crlf); + decrRefCount(field); + return; + } else { + addReply(c,shared.nullbulk); + decrRefCount(field); + return; + } } else { - if (o->type != REDIS_ZSET) { - addReply(c,shared.wrongtypeerr); + struct dictEntry *de; + + de = dictFind(o->ptr,c->argv[2]); + if (de == NULL) { + addReply(c,shared.nullbulk); } else { - zs = o->ptr; - addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n",zs->zsl->length)); + robj *e = dictGetEntryVal(de); + + addReplyBulk(c,e); } } } -static void zscoreCommand(redisClient *c) { +static void hdelCommand(redisClient *c) { robj *o; - zset *zs; - - o = lookupKeyRead(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.nullbulk); - return; + int deleted = 0; + + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_HASH)) return; + + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + robj *field = getDecodedObject(c->argv[2]); + + o->ptr = zipmapDel((unsigned char*) o->ptr, + (unsigned char*) field->ptr, + sdslen(field->ptr), &deleted); + decrRefCount(field); + if (zipmapLen((unsigned char*) o->ptr) == 0) + deleteKey(c->db,c->argv[1]); } else { - if (o->type != REDIS_ZSET) { - addReply(c,shared.wrongtypeerr); - } else { - dictEntry *de; + deleted = dictDelete((dict*)o->ptr,c->argv[2]) == DICT_OK; + if (htNeedsResize(o->ptr)) dictResize(o->ptr); + if (dictSize((dict*)o->ptr) == 0) deleteKey(c->db,c->argv[1]); + } + if (deleted) server.dirty++; + addReply(c,deleted ? shared.cone : shared.czero); +} - zs = o->ptr; - de = dictFind(zs->dict,c->argv[2]); - if (!de) { - addReply(c,shared.nullbulk); - } else { - double *score = dictGetEntryVal(de); +static void hlenCommand(redisClient *c) { + robj *o; + unsigned long len; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_HASH)) return; + + len = (o->encoding == REDIS_ENCODING_ZIPMAP) ? + zipmapLen((unsigned char*)o->ptr) : dictSize((dict*)o->ptr); + addReplyUlong(c,len); +} - addReplyDouble(c,*score); +#define REDIS_GETALL_KEYS 1 +#define REDIS_GETALL_VALS 2 +static void genericHgetallCommand(redisClient *c, int flags) { + robj *o, *lenobj; + unsigned long count = 0; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullmultibulk)) == NULL + || checkType(c,o,REDIS_HASH)) return; + + lenobj = createObject(REDIS_STRING,NULL); + addReply(c,lenobj); + decrRefCount(lenobj); + + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + unsigned char *p = zipmapRewind(o->ptr); + unsigned char *field, *val; + unsigned int flen, vlen; + + while((p = zipmapNext(p,&field,&flen,&val,&vlen)) != NULL) { + robj *aux; + + if (flags & REDIS_GETALL_KEYS) { + aux = createStringObject((char*)field,flen); + addReplyBulk(c,aux); + decrRefCount(aux); + count++; + } + if (flags & REDIS_GETALL_VALS) { + aux = createStringObject((char*)val,vlen); + addReplyBulk(c,aux); + decrRefCount(aux); + count++; + } + } + } else { + dictIterator *di = dictGetIterator(o->ptr); + dictEntry *de; + + while((de = dictNext(di)) != NULL) { + robj *fieldobj = dictGetEntryKey(de); + robj *valobj = dictGetEntryVal(de); + + if (flags & REDIS_GETALL_KEYS) { + addReplyBulk(c,fieldobj); + count++; + } + if (flags & REDIS_GETALL_VALS) { + addReplyBulk(c,valobj); + count++; } } + dictReleaseIterator(di); } + lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",count); } -static void zrankCommand(redisClient *c) { +static void hkeysCommand(redisClient *c) { + genericHgetallCommand(c,REDIS_GETALL_KEYS); +} + +static void hvalsCommand(redisClient *c) { + genericHgetallCommand(c,REDIS_GETALL_VALS); +} + +static void hgetallCommand(redisClient *c) { + genericHgetallCommand(c,REDIS_GETALL_KEYS|REDIS_GETALL_VALS); +} + +static void hexistsCommand(redisClient *c) { robj *o; - o = lookupKeyRead(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,shared.nullbulk); - return; - } - if (o->type != REDIS_ZSET) { - addReply(c,shared.wrongtypeerr); - return; - } + int exists = 0; - zset *zs = o->ptr; - zskiplist *zsl = zs->zsl; - dictEntry *de = dictFind(zs->dict,c->argv[2]); - if (!de) { - addReply(c,shared.nullbulk); - return; + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_HASH)) return; + + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + robj *field; + unsigned char *zm = o->ptr; + + field = getDecodedObject(c->argv[2]); + exists = zipmapExists(zm,field->ptr,sdslen(field->ptr)); + decrRefCount(field); + } else { + exists = dictFind(o->ptr,c->argv[2]) != NULL; } + addReply(c,exists ? shared.cone : shared.czero); +} - double *score = dictGetEntryVal(de); - zskiplistNode *x; - unsigned int rank = 0; - int i; +static void convertToRealHash(robj *o) { + unsigned char *key, *val, *p, *zm = o->ptr; + unsigned int klen, vlen; + dict *dict = dictCreate(&hashDictType,NULL); - x = zsl->header; - for (i = zsl->level-1; i >= 0; i--) { - while (x->forward[i] && - (x->forward[i]->score < *score || - (x->forward[i]->score == *score && - compareStringObjects(x->forward[i]->obj,c->argv[2]) < 0))) { - rank += x->span[i]; - x = x->forward[i]; - } + assert(o->type == REDIS_HASH && o->encoding != REDIS_ENCODING_HT); + p = zipmapRewind(zm); + while((p = zipmapNext(p,&key,&klen,&val,&vlen)) != NULL) { + robj *keyobj, *valobj; - if (x->forward[i] && compareStringObjects(x->forward[i]->obj,c->argv[2]) == 0) { - addReplyLong(c, rank); - return; - } + keyobj = createStringObject((char*)key,klen); + valobj = createStringObject((char*)val,vlen); + tryObjectEncoding(keyobj); + tryObjectEncoding(valobj); + dictAdd(dict,keyobj,valobj); } - - addReply(c,shared.nullbulk); + o->encoding = REDIS_ENCODING_HT; + o->ptr = dict; + zfree(zm); } /* ========================= Non type-specific commands ==================== */ @@ -5509,6 +6290,10 @@ static void flushdbCommand(redisClient *c) { static void flushallCommand(redisClient *c) { server.dirty += emptyDb(); addReply(c,shared.ok); + if (server.bgsavechildpid != -1) { + kill(server.bgsavechildpid,SIGKILL); + rdbRemoveTempFile(server.bgsavechildpid); + } rdbSave(server.dbfilename); server.dirty++; } @@ -5801,11 +6586,7 @@ static void sortCommand(redisClient *c) { listNode *ln; listIter li; - if (!getop) { - addReplyBulkLen(c,vector[j].obj); - addReply(c,vector[j].obj); - addReply(c,shared.crlf); - } + if (!getop) addReplyBulk(c,vector[j].obj); listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; @@ -5816,9 +6597,7 @@ static void sortCommand(redisClient *c) { if (!val || val->type != REDIS_STRING) { addReply(c,shared.nullbulk); } else { - addReplyBulkLen(c,val); - addReply(c,val); - addReply(c,shared.crlf); + addReplyBulk(c,val); } } else { redisAssert(sop->type == REDIS_SORT_GET); /* always fails */ @@ -5905,7 +6684,7 @@ static sds genRedisInfoString(void) { time_t uptime = time(NULL)-server.stat_starttime; int j; char hmem[64]; - + bytesToHuman(hmem,zmalloc_used_memory()); info = sdscatprintf(sdsempty(), "redis_version:%s\r\n" @@ -5925,6 +6704,11 @@ static sds genRedisInfoString(void) { "bgrewriteaof_in_progress:%d\r\n" "total_connections_received:%lld\r\n" "total_commands_processed:%lld\r\n" + "expired_keys:%lld\r\n" + "hash_max_zipmap_entries:%ld\r\n" + "hash_max_zipmap_value:%ld\r\n" + "pubsub_channels:%ld\r\n" + "pubsub_patterns:%u\r\n" "vm_enabled:%d\r\n" "role:%s\r\n" ,REDIS_VERSION, @@ -5944,6 +6728,11 @@ static sds genRedisInfoString(void) { server.bgrewritechildpid != -1, server.stat_numconnections, server.stat_numcommands, + server.stat_expiredkeys, + server.hash_max_zipmap_entries, + server.hash_max_zipmap_value, + dictSize(server.pubsub_channels), + listLength(server.pubsub_patterns), server.vm_enabled != 0, server.masterhost == NULL ? "master" : "slave" ); @@ -6065,6 +6854,7 @@ static int expireIfNeeded(redisDb *db, robj *key) { /* Delete the key */ dictDelete(db->expires,key); + server.stat_expiredkeys++; return dictDelete(db->dict,key) == DICT_OK; } @@ -6077,6 +6867,7 @@ static int deleteIfVolatile(redisDb *db, robj *key) { /* Delete the key */ server.dirty++; + server.stat_expiredkeys++; dictDelete(db->expires,key); return dictDelete(db->dict,key) == DICT_OK; } @@ -6329,12 +7120,8 @@ static int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) { receiver = ln->value; addReplySds(receiver,sdsnew("*2\r\n")); - addReplyBulkLen(receiver,key); - addReply(receiver,key); - addReply(receiver,shared.crlf); - addReplyBulkLen(receiver,ele); - addReply(receiver,ele); - addReply(receiver,shared.crlf); + addReplyBulk(receiver,key); + addReplyBulk(receiver,ele); unblockClientWaitingData(receiver); return 1; } @@ -6373,9 +7160,7 @@ static void blockingPopGenericCommand(redisClient *c, int where) { * for us. If this souds like an hack to you it's just * because it is... */ addReplySds(c,sdsnew("*2\r\n")); - addReplyBulkLen(c,argv[1]); - addReply(c,argv[1]); - addReply(c,shared.crlf); + addReplyBulk(c,argv[1]); popGenericCommand(c,where); /* Fix the client structure with the original stuff */ @@ -6637,9 +7422,9 @@ static void updateSlavesWaitingBgsave(int bgsaveerr) { static int syncWithMaster(void) { char buf[1024], tmpfile[256], authcmd[1024]; - int dumpsize; + long dumpsize; int fd = anetTcpConnect(NULL,server.masterhost,server.masterport); - int dfd; + int dfd, maxtries = 5; if (fd == -1) { redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s", @@ -6689,11 +7474,16 @@ static int syncWithMaster(void) { redisLog(REDIS_WARNING,"Bad protocol from MASTER, the first byte is not '$', are you sure the host and port are right?"); return REDIS_ERR; } - dumpsize = atoi(buf+1); - redisLog(REDIS_NOTICE,"Receiving %d bytes data dump from MASTER",dumpsize); + dumpsize = strtol(buf+1,NULL,10); + redisLog(REDIS_NOTICE,"Receiving %ld bytes data dump from MASTER",dumpsize); /* Read the bulk write data on a temp file */ - snprintf(tmpfile,256,"temp-%d.%ld.rdb",(int)time(NULL),(long int)random()); - dfd = open(tmpfile,O_CREAT|O_WRONLY,0644); + while(maxtries--) { + snprintf(tmpfile,256, + "temp-%d.%ld.rdb",(int)time(NULL),(long int)getpid()); + dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644); + if (dfd != -1) break; + sleep(1); + } if (dfd == -1) { close(fd); redisLog(REDIS_WARNING,"Opening the temp file needed for MASTER <-> SLAVE synchronization: %s",strerror(errno)); @@ -7033,7 +7823,7 @@ fmterr: } /* Write an object into a file in the bulk format $\r\n\r\n */ -static int fwriteBulk(FILE *fp, robj *obj) { +static int fwriteBulkObject(FILE *fp, robj *obj) { char buf[128]; int decrrc = 0; @@ -7058,6 +7848,18 @@ err: return 0; } +/* Write binary-safe string into a file in the bulkformat + * $\r\n\r\n */ +static int fwriteBulkString(FILE *fp, char *s, unsigned long len) { + char buf[128]; + + snprintf(buf,sizeof(buf),"$%ld\r\n",(unsigned long)len); + if (fwrite(buf,strlen(buf),1,fp) == 0) return 0; + if (len && fwrite(s,len,1,fp) == 0) return 0; + if (fwrite("\r\n",2,1,fp) == 0) return 0; + return 1; +} + /* Write a double value in bulk format $\r\n\r\n */ static int fwriteBulkDouble(FILE *fp, double d) { char buf[128], dbuf[128]; @@ -7140,8 +7942,8 @@ static int rewriteAppendOnlyFile(char *filename) { char cmd[]="*3\r\n$3\r\nSET\r\n"; if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr; /* Key and value */ - if (fwriteBulk(fp,key) == 0) goto werr; - if (fwriteBulk(fp,o) == 0) goto werr; + if (fwriteBulkObject(fp,key) == 0) goto werr; + if (fwriteBulkObject(fp,o) == 0) goto werr; } else if (o->type == REDIS_LIST) { /* Emit the RPUSHes needed to rebuild the list */ list *list = o->ptr; @@ -7154,8 +7956,8 @@ static int rewriteAppendOnlyFile(char *filename) { robj *eleobj = listNodeValue(ln); if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr; - if (fwriteBulk(fp,key) == 0) goto werr; - if (fwriteBulk(fp,eleobj) == 0) goto werr; + if (fwriteBulkObject(fp,key) == 0) goto werr; + if (fwriteBulkObject(fp,eleobj) == 0) goto werr; } } else if (o->type == REDIS_SET) { /* Emit the SADDs needed to rebuild the set */ @@ -7168,8 +7970,8 @@ static int rewriteAppendOnlyFile(char *filename) { robj *eleobj = dictGetEntryKey(de); if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr; - if (fwriteBulk(fp,key) == 0) goto werr; - if (fwriteBulk(fp,eleobj) == 0) goto werr; + if (fwriteBulkObject(fp,key) == 0) goto werr; + if (fwriteBulkObject(fp,eleobj) == 0) goto werr; } dictReleaseIterator(di); } else if (o->type == REDIS_ZSET) { @@ -7184,13 +7986,45 @@ static int rewriteAppendOnlyFile(char *filename) { double *score = dictGetEntryVal(de); if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr; - if (fwriteBulk(fp,key) == 0) goto werr; + if (fwriteBulkObject(fp,key) == 0) goto werr; if (fwriteBulkDouble(fp,*score) == 0) goto werr; - if (fwriteBulk(fp,eleobj) == 0) goto werr; + if (fwriteBulkObject(fp,eleobj) == 0) goto werr; } dictReleaseIterator(di); + } else if (o->type == REDIS_HASH) { + char cmd[]="*4\r\n$4\r\nHSET\r\n"; + + /* Emit the HSETs needed to rebuild the hash */ + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + unsigned char *p = zipmapRewind(o->ptr); + unsigned char *field, *val; + unsigned int flen, vlen; + + while((p = zipmapNext(p,&field,&flen,&val,&vlen)) != NULL) { + if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr; + if (fwriteBulkObject(fp,key) == 0) goto werr; + if (fwriteBulkString(fp,(char*)field,flen) == -1) + return -1; + if (fwriteBulkString(fp,(char*)val,vlen) == -1) + return -1; + } + } else { + dictIterator *di = dictGetIterator(o->ptr); + dictEntry *de; + + while((de = dictNext(di)) != NULL) { + robj *field = dictGetEntryKey(de); + robj *val = dictGetEntryVal(de); + + if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr; + if (fwriteBulkObject(fp,key) == 0) goto werr; + if (fwriteBulkObject(fp,field) == -1) return -1; + if (fwriteBulkObject(fp,val) == -1) return -1; + } + dictReleaseIterator(di); + } } else { - redisAssert(0 != 0); + redisAssert(0); } /* Save the expire time */ if (expiretime != -1) { @@ -7198,7 +8032,7 @@ static int rewriteAppendOnlyFile(char *filename) { /* If this key is already expired skip it */ if (expiretime < now) continue; if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr; - if (fwriteBulk(fp,key) == 0) goto werr; + if (fwriteBulkObject(fp,key) == 0) goto werr; if (fwriteBulkLong(fp,expiretime) == 0) goto werr; } if (swapped) decrRefCount(o); @@ -7690,6 +8524,38 @@ static double computeObjectSwappability(robj *o) { if (z) asize += sizeof(zskiplistNode)*dictSize(d); } break; + case REDIS_HASH: + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + unsigned char *p = zipmapRewind((unsigned char*)o->ptr); + unsigned int len = zipmapLen((unsigned char*)o->ptr); + unsigned int klen, vlen; + unsigned char *key, *val; + + if ((p = zipmapNext(p,&key,&klen,&val,&vlen)) == NULL) { + klen = 0; + vlen = 0; + } + asize = len*(klen+vlen+3); + } else if (o->encoding == REDIS_ENCODING_HT) { + d = o->ptr; + asize = sizeof(dict)+(sizeof(struct dictEntry*)*dictSlots(d)); + if (dictSize(d)) { + long elesize; + robj *ele; + + de = dictGetRandomKey(d); + ele = dictGetEntryKey(de); + elesize = (ele->encoding == REDIS_ENCODING_RAW) ? + (sizeof(*o)+sdslen(ele->ptr)) : + sizeof(*o); + ele = dictGetEntryVal(de); + elesize = (ele->encoding == REDIS_ENCODING_RAW) ? + (sizeof(*o)+sdslen(ele->ptr)) : + sizeof(*o); + asize += (sizeof(struct dictEntry)+elesize)*dictSize(d); + } + } + break; } return (double)age*log(1+asize); } @@ -8092,13 +8958,18 @@ static void *IOThreadEntryPoint(void *arg) { static void spawnIOThread(void) { pthread_t thread; sigset_t mask, omask; + int err; sigemptyset(&mask); sigaddset(&mask,SIGCHLD); sigaddset(&mask,SIGHUP); sigaddset(&mask,SIGPIPE); pthread_sigmask(SIG_SETMASK, &mask, &omask); - pthread_create(&thread,&server.io_threads_attr,IOThreadEntryPoint,NULL); + while ((err = pthread_create(&thread,&server.io_threads_attr,IOThreadEntryPoint,NULL)) != 0) { + redisLog(REDIS_WARNING,"Unable to spawn an I/O thread: %s", + strerror(err)); + usleep(1000000); + } pthread_sigmask(SIG_SETMASK, &omask, NULL); server.io_active_threads++; } @@ -8243,6 +9114,15 @@ static int waitForSwappedKey(redisClient *c, robj *key) { return 1; } +/* Preload keys needed for the ZUNION and ZINTER commands. */ +static void zunionInterBlockClientOnSwappedKeys(redisClient *c) { + int i, num; + num = atoi(c->argv[2]->ptr); + for (i = 0; i < num; i++) { + waitForSwappedKey(c,c->argv[3+i]); + } +} + /* Is this client attempting to run a command against swapped keys? * If so, block it ASAP, load the keys in background, then resume it. * @@ -8256,11 +9136,16 @@ static int waitForSwappedKey(redisClient *c, robj *key) { static int blockClientOnSwappedKeys(struct redisCommand *cmd, redisClient *c) { int j, last; - if (cmd->vm_firstkey == 0) return 0; - last = cmd->vm_lastkey; - if (last < 0) last = c->argc+last; - for (j = cmd->vm_firstkey; j <= last; j += cmd->vm_keystep) - waitForSwappedKey(c,c->argv[j]); + if (cmd->vm_preload_proc != NULL) { + cmd->vm_preload_proc(c); + } else { + if (cmd->vm_firstkey == 0) return 0; + last = cmd->vm_lastkey; + if (last < 0) last = c->argc+last; + for (j = cmd->vm_firstkey; j <= last; j += cmd->vm_keystep) + waitForSwappedKey(c,c->argv[j]); + } + /* If the client was blocked for at least one key, mark it as blocked. */ if (listLength(c->io_keys)) { c->flags |= REDIS_IO_WAIT; @@ -8330,6 +9215,353 @@ static void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key) { } } +/* =========================== Remote Configuration ========================= */ + +static void configSetCommand(redisClient *c) { + robj *o = getDecodedObject(c->argv[3]); + if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) { + zfree(server.dbfilename); + server.dbfilename = zstrdup(o->ptr); + } else if (!strcasecmp(c->argv[2]->ptr,"requirepass")) { + zfree(server.requirepass); + server.requirepass = zstrdup(o->ptr); + } else if (!strcasecmp(c->argv[2]->ptr,"masterauth")) { + zfree(server.masterauth); + server.masterauth = zstrdup(o->ptr); + } else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) { + server.maxmemory = strtoll(o->ptr, NULL, 10); + } else { + addReplySds(c,sdscatprintf(sdsempty(), + "-ERR not supported CONFIG parameter %s\r\n", + (char*)c->argv[2]->ptr)); + decrRefCount(o); + return; + } + decrRefCount(o); + addReply(c,shared.ok); +} + +static void configGetCommand(redisClient *c) { + robj *o = getDecodedObject(c->argv[2]); + robj *lenobj = createObject(REDIS_STRING,NULL); + char *pattern = o->ptr; + int matches = 0; + + addReply(c,lenobj); + decrRefCount(lenobj); + + if (stringmatch(pattern,"dbfilename",0)) { + addReplyBulkCString(c,"dbfilename"); + addReplyBulkCString(c,server.dbfilename); + matches++; + } + if (stringmatch(pattern,"requirepass",0)) { + addReplyBulkCString(c,"requirepass"); + addReplyBulkCString(c,server.requirepass); + matches++; + } + if (stringmatch(pattern,"masterauth",0)) { + addReplyBulkCString(c,"masterauth"); + addReplyBulkCString(c,server.masterauth); + matches++; + } + if (stringmatch(pattern,"maxmemory",0)) { + char buf[128]; + + snprintf(buf,128,"%llu\n",server.maxmemory); + addReplyBulkCString(c,"maxmemory"); + addReplyBulkCString(c,buf); + matches++; + } + decrRefCount(o); + lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",matches*2); +} + +static void configCommand(redisClient *c) { + if (!strcasecmp(c->argv[1]->ptr,"set")) { + if (c->argc != 4) goto badarity; + configSetCommand(c); + } else if (!strcasecmp(c->argv[1]->ptr,"get")) { + if (c->argc != 3) goto badarity; + configGetCommand(c); + } else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) { + if (c->argc != 2) goto badarity; + server.stat_numcommands = 0; + server.stat_numconnections = 0; + server.stat_expiredkeys = 0; + server.stat_starttime = time(NULL); + addReply(c,shared.ok); + } else { + addReplySds(c,sdscatprintf(sdsempty(), + "-ERR CONFIG subcommand must be one of GET, SET, RESETSTAT\r\n")); + } + return; + +badarity: + addReplySds(c,sdscatprintf(sdsempty(), + "-ERR Wrong number of arguments for CONFIG %s\r\n", + (char*) c->argv[1]->ptr)); +} + +/* =========================== Pubsub implementation ======================== */ + +static void freePubsubPattern(void *p) { + pubsubPattern *pat = p; + + decrRefCount(pat->pattern); + zfree(pat); +} + +static int listMatchPubsubPattern(void *a, void *b) { + pubsubPattern *pa = a, *pb = b; + + return (pa->client == pb->client) && + (compareStringObjects(pa->pattern,pb->pattern) == 0); +} + +/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or + * 0 if the client was already subscribed to that channel. */ +static int pubsubSubscribeChannel(redisClient *c, robj *channel) { + struct dictEntry *de; + list *clients = NULL; + int retval = 0; + + /* Add the channel to the client -> channels hash table */ + if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) { + retval = 1; + incrRefCount(channel); + /* Add the client to the channel -> list of clients hash table */ + de = dictFind(server.pubsub_channels,channel); + if (de == NULL) { + clients = listCreate(); + dictAdd(server.pubsub_channels,channel,clients); + incrRefCount(channel); + } else { + clients = dictGetEntryVal(de); + } + listAddNodeTail(clients,c); + } + /* Notify the client */ + addReply(c,shared.mbulk3); + addReply(c,shared.subscribebulk); + addReplyBulk(c,channel); + addReplyLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns)); + return retval; +} + +/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or + * 0 if the client was not subscribed to the specified channel. */ +static int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) { + struct dictEntry *de; + list *clients; + listNode *ln; + int retval = 0; + + /* Remove the channel from the client -> channels hash table */ + incrRefCount(channel); /* channel may be just a pointer to the same object + we have in the hash tables. Protect it... */ + if (dictDelete(c->pubsub_channels,channel) == DICT_OK) { + retval = 1; + /* Remove the client from the channel -> clients list hash table */ + de = dictFind(server.pubsub_channels,channel); + assert(de != NULL); + clients = dictGetEntryVal(de); + ln = listSearchKey(clients,c); + assert(ln != NULL); + listDelNode(clients,ln); + if (listLength(clients) == 0) { + /* Free the list and associated hash entry at all if this was + * the latest client, so that it will be possible to abuse + * Redis PUBSUB creating millions of channels. */ + dictDelete(server.pubsub_channels,channel); + } + } + /* Notify the client */ + if (notify) { + addReply(c,shared.mbulk3); + addReply(c,shared.unsubscribebulk); + addReplyBulk(c,channel); + addReplyLong(c,dictSize(c->pubsub_channels)+ + listLength(c->pubsub_patterns)); + + } + decrRefCount(channel); /* it is finally safe to release it */ + return retval; +} + +/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the clinet was already subscribed to that pattern. */ +static int pubsubSubscribePattern(redisClient *c, robj *pattern) { + int retval = 0; + + if (listSearchKey(c->pubsub_patterns,pattern) == NULL) { + retval = 1; + pubsubPattern *pat; + listAddNodeTail(c->pubsub_patterns,pattern); + incrRefCount(pattern); + pat = zmalloc(sizeof(*pat)); + pat->pattern = getDecodedObject(pattern); + pat->client = c; + listAddNodeTail(server.pubsub_patterns,pat); + } + /* Notify the client */ + addReply(c,shared.mbulk3); + addReply(c,shared.psubscribebulk); + addReplyBulk(c,pattern); + addReplyLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns)); + return retval; +} + +/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or + * 0 if the client was not subscribed to the specified channel. */ +static int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) { + listNode *ln; + pubsubPattern pat; + int retval = 0; + + incrRefCount(pattern); /* Protect the object. May be the same we remove */ + if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) { + retval = 1; + listDelNode(c->pubsub_patterns,ln); + pat.client = c; + pat.pattern = pattern; + ln = listSearchKey(server.pubsub_patterns,&pat); + listDelNode(server.pubsub_patterns,ln); + } + /* Notify the client */ + if (notify) { + addReply(c,shared.mbulk3); + addReply(c,shared.punsubscribebulk); + addReplyBulk(c,pattern); + addReplyLong(c,dictSize(c->pubsub_channels)+ + listLength(c->pubsub_patterns)); + } + decrRefCount(pattern); + return retval; +} + +/* Unsubscribe from all the channels. Return the number of channels the + * client was subscribed from. */ +static int pubsubUnsubscribeAllChannels(redisClient *c, int notify) { + dictIterator *di = dictGetIterator(c->pubsub_channels); + dictEntry *de; + int count = 0; + + while((de = dictNext(di)) != NULL) { + robj *channel = dictGetEntryKey(de); + + count += pubsubUnsubscribeChannel(c,channel,notify); + } + dictReleaseIterator(di); + return count; +} + +/* Unsubscribe from all the patterns. Return the number of patterns the + * client was subscribed from. */ +static int pubsubUnsubscribeAllPatterns(redisClient *c, int notify) { + listNode *ln; + listIter li; + int count = 0; + + listRewind(c->pubsub_patterns,&li); + while ((ln = listNext(&li)) != NULL) { + robj *pattern = ln->value; + + count += pubsubUnsubscribePattern(c,pattern,notify); + } + return count; +} + +/* Publish a message */ +static int pubsubPublishMessage(robj *channel, robj *message) { + int receivers = 0; + struct dictEntry *de; + listNode *ln; + listIter li; + + /* Send to clients listening for that channel */ + de = dictFind(server.pubsub_channels,channel); + if (de) { + list *list = dictGetEntryVal(de); + listNode *ln; + listIter li; + + listRewind(list,&li); + while ((ln = listNext(&li)) != NULL) { + redisClient *c = ln->value; + + addReply(c,shared.mbulk3); + addReply(c,shared.messagebulk); + addReplyBulk(c,channel); + addReplyBulk(c,message); + receivers++; + } + } + /* Send to clients listening to matching channels */ + if (listLength(server.pubsub_patterns)) { + listRewind(server.pubsub_patterns,&li); + channel = getDecodedObject(channel); + while ((ln = listNext(&li)) != NULL) { + pubsubPattern *pat = ln->value; + + if (stringmatchlen((char*)pat->pattern->ptr, + sdslen(pat->pattern->ptr), + (char*)channel->ptr, + sdslen(channel->ptr),0)) { + addReply(pat->client,shared.mbulk3); + addReply(pat->client,shared.messagebulk); + addReplyBulk(pat->client,channel); + addReplyBulk(pat->client,message); + receivers++; + } + } + decrRefCount(channel); + } + return receivers; +} + +static void subscribeCommand(redisClient *c) { + int j; + + for (j = 1; j < c->argc; j++) + pubsubSubscribeChannel(c,c->argv[j]); +} + +static void unsubscribeCommand(redisClient *c) { + if (c->argc == 1) { + pubsubUnsubscribeAllChannels(c,1); + return; + } else { + int j; + + for (j = 1; j < c->argc; j++) + pubsubUnsubscribeChannel(c,c->argv[j],1); + } +} + +static void psubscribeCommand(redisClient *c) { + int j; + + for (j = 1; j < c->argc; j++) + pubsubSubscribePattern(c,c->argv[j]); +} + +static void punsubscribeCommand(redisClient *c) { + if (c->argc == 1) { + pubsubUnsubscribeAllPatterns(c,1); + return; + } else { + int j; + + for (j = 1; j < c->argc; j++) + pubsubUnsubscribePattern(c,c->argv[j],1); + } +} + +static void publishCommand(redisClient *c) { + int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]); + addReplyLong(c,receivers); +} + /* ================================= Debugging ============================== */ static void debugCommand(redisClient *c) { @@ -8367,11 +9599,20 @@ static void debugCommand(redisClient *c) { val = dictGetEntryVal(de); if (!server.vm_enabled || (key->storage == REDIS_VM_MEMORY || key->storage == REDIS_VM_SWAPPING)) { + char *strenc; + char buf[128]; + + if (val->encoding < (sizeof(strencoding)/sizeof(char*))) { + strenc = strencoding[val->encoding]; + } else { + snprintf(buf,64,"unknown encoding %d\n", val->encoding); + strenc = buf; + } addReplySds(c,sdscatprintf(sdsempty(), "+Key at:%p refcount:%d, value at:%p refcount:%d " - "encoding:%d serializedlength:%lld\r\n", + "encoding:%s serializedlength:%lld\r\n", (void*)key, key->refcount, (void*)val, val->refcount, - val->encoding, (long long) rdbSavedObjectLen(val,NULL))); + strenc, (long long) rdbSavedObjectLen(val,NULL))); } else { addReplySds(c,sdscatprintf(sdsempty(), "+Key at:%p refcount:%d, value swapped at: page %llu " @@ -8471,16 +9712,29 @@ static void daemonize(void) { } } +static void version() { + printf("Redis server version %s\n", REDIS_VERSION); + exit(0); +} + +static void usage() { + fprintf(stderr,"Usage: ./redis-server [/path/to/redis.conf]\n"); + fprintf(stderr," ./redis-server - (read config from stdin)\n"); + exit(1); +} + int main(int argc, char **argv) { time_t start; initServerConfig(); if (argc == 2) { + if (strcmp(argv[1], "-v") == 0 || + strcmp(argv[1], "--version") == 0) version(); + if (strcmp(argv[1], "--help") == 0) usage(); resetServerSaveParams(); loadServerConfig(argv[1]); - } else if (argc > 2) { - fprintf(stderr,"Usage: ./redis-server [/path/to/redis.conf]\n"); - exit(1); + } else if ((argc > 2)) { + usage(); } else { redisLog(REDIS_WARNING,"Warning: no config file specified, using the default config. In order to specify a config file use 'redis-server /path/to/redis.conf'"); }