]> git.saurik.com Git - redis.git/commitdiff
Merge pull request #181 from redsquirrel/unstable
authorSalvatore Sanfilippo <antirez@gmail.com>
Tue, 15 Nov 2011 14:36:53 +0000 (06:36 -0800)
committerSalvatore Sanfilippo <antirez@gmail.com>
Tue, 15 Nov 2011 14:36:53 +0000 (06:36 -0800)
Fixing inject: it was failing when there was 1 or >2 sources

26 files changed:
deps/lua/Makefile
deps/lua/src/Makefile
src/Makefile
src/aof.c
src/cluster.c
src/db.c
src/debug.c
src/dict.c
src/dict.h
src/object.c
src/pubsub.c
src/rdb.c
src/rdb.h
src/redis.c
src/redis.h
src/sort.c
src/t_hash.c
src/t_list.c
src/t_set.c
src/t_string.c
src/t_zset.c
tests/unit/basic.tcl
tests/unit/expire.tcl
tests/unit/other.tcl
tests/unit/type/hash.tcl
tests/unit/type/zset.tcl

index eed4aedc48e4db8356170d8493657e1e69fa81a1..6e78f66fa5b1763b119236e1bd7de95f42093e52 100644 (file)
@@ -53,7 +53,7 @@ R= 5.1.4
 all:   $(PLAT)
 
 $(PLATS) clean:
-       cd src && $(MAKE) ARCH="$(ARCH)" $@
+       cd src && $(MAKE) $@
 
 test:  dummy
        src/lua test/hello.lua
index 01088abceb0f70b8a51950f29109f579a9b0096f..ff6661607a0b8490e423f7fb7782cfb0b9c5197e 100644 (file)
@@ -8,7 +8,7 @@
 PLAT= none
 
 CC= gcc
-CFLAGS= -O2 -Wall $(MYCFLAGS) $(ARCH)
+CFLAGS= -O2 -Wall $(MYCFLAGS)
 AR= ar rcu
 RANLIB= ranlib
 RM= rm -f
@@ -52,10 +52,10 @@ $(LUA_A): $(CORE_O) $(LIB_O)
        $(RANLIB) $@
 
 $(LUA_T): $(LUA_O) $(LUA_A)
-       $(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) $(LIBS) $(ARCH)
+       $(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) $(LIBS)
 
 $(LUAC_T): $(LUAC_O) $(LUA_A)
-       $(CC) -o $@ $(MYLDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS) $(ARCH)
+       $(CC) -o $@ $(MYLDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS)
 
 clean:
        $(RM) $(ALL_T) $(ALL_O)
@@ -84,7 +84,7 @@ aix:
        $(MAKE) all CC="xlc" CFLAGS="-O2 -DLUA_USE_POSIX -DLUA_USE_DLOPEN" MYLIBS="-ldl" MYLDFLAGS="-brtl -bexpall"
 
 ansi:
-       $(MAKE) all MYCFLAGS=-DLUA_ANSI ARCH="$(ARCH)"
+       $(MAKE) all MYCFLAGS=-DLUA_ANSI
 
 bsd:
        $(MAKE) all MYCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN" MYLIBS="-Wl,-E"
index 04f2a68c832513d9677205bba6588ebc3c41e61a..6cb5f062302436771ef4f2e64c4825f2a7668a1e 100644 (file)
@@ -46,6 +46,7 @@ endif
 
 CCLINK+= $(ALLOC_LINK)
 CFLAGS+= $(ALLOC_FLAGS)
+LUA_CFLAGS+= $(ARCH)
 
 CCOPT= $(CFLAGS) $(ARCH) $(PROF)
 
@@ -167,7 +168,7 @@ dependencies:
        @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)linenoise$(ENDCOLOR)
        @cd ../deps/linenoise && $(MAKE) ARCH="$(ARCH)"
        @echo $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)Lua ansi$(ENDCOLOR)
-       @cd ../deps/lua && $(MAKE) ARCH="$(ARCH)" CFLAGS="$(LUA_CFLAGS)" ansi
+       @cd ../deps/lua && $(MAKE) CFLAGS="$(LUA_CFLAGS)" MYLDFLAGS="$(ARCH)" ansi
 
 ../deps/jemalloc/lib/libjemalloc.a:
        cd ../deps/jemalloc && ./configure $(JEMALLOC_CFLAGS) --with-jemalloc-prefix=je_ --enable-cc-silence && $(MAKE) lib/libjemalloc.a
index 66516f7879b267a86ea8bd575563b10137cacd5a..4a463bde0664b34e39ceb5a9da9fe96bc485bdc5 100644 (file)
--- a/src/aof.c
+++ b/src/aof.c
@@ -181,21 +181,38 @@ sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
     return dst;
 }
 
-sds catAppendOnlyExpireAtCommand(sds buf, robj *key, robj *seconds) {
-    int argc = 3;
-    long when;
+/* Create the sds representation of an PEXPIREAT command, using
+ * 'seconds' as time to live and 'cmd' to understand what command
+ * we are translating into a PEXPIREAT.
+ *
+ * This command is used in order to translate EXPIRE and PEXPIRE commands
+ * into PEXPIREAT command so that we retain precision in the append only
+ * file, and the time is always absolute and not relative. */
+sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, robj *seconds) {
+    long long when;
     robj *argv[3];
 
     /* Make sure we can use strtol */
     seconds = getDecodedObject(seconds);
-    when = time(NULL)+strtol(seconds->ptr,NULL,10);
+    when = strtoll(seconds->ptr,NULL,10);
+    /* Convert argument into milliseconds for EXPIRE, SETEX, EXPIREAT */
+    if (cmd->proc == expireCommand || cmd->proc == setexCommand ||
+        cmd->proc == expireatCommand)
+    {
+        when *= 1000;
+    }
+    /* Convert into absolute time for EXPIRE, PEXPIRE, SETEX, PSETEX */
+    if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
+        cmd->proc == setexCommand || cmd->proc == psetexCommand)
+    {
+        when += mstime();
+    }
     decrRefCount(seconds);
 
-    argv[0] = createStringObject("EXPIREAT",8);
+    argv[0] = createStringObject("PEXPIREAT",9);
     argv[1] = key;
-    argv[2] = createObject(REDIS_STRING,
-        sdscatprintf(sdsempty(),"%ld",when));
-    buf = catAppendOnlyGenericCommand(buf, argc, argv);
+    argv[2] = createStringObjectFromLongLong(when);
+    buf = catAppendOnlyGenericCommand(buf, 3, argv);
     decrRefCount(argv[0]);
     decrRefCount(argv[2]);
     return buf;
@@ -216,18 +233,22 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
         server.appendseldb = dictid;
     }
 
-    if (cmd->proc == expireCommand) {
-        /* Translate EXPIRE into EXPIREAT */
-        buf = catAppendOnlyExpireAtCommand(buf,argv[1],argv[2]);
-    } else if (cmd->proc == setexCommand) {
-        /* Translate SETEX to SET and EXPIREAT */
+    if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
+        cmd->proc == expireatCommand) {
+        /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
+        buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
+    } else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
+        /* Translate SETEX/PSETEX to SET and PEXPIREAT */
         tmpargv[0] = createStringObject("SET",3);
         tmpargv[1] = argv[1];
         tmpargv[2] = argv[3];
         buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
         decrRefCount(tmpargv[0]);
-        buf = catAppendOnlyExpireAtCommand(buf,argv[1],argv[2]);
+        buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
     } else {
+        /* All the other commands don't need translation or need the
+         * same translation already operated in the command vector
+         * for the replication itself. */
         buf = catAppendOnlyGenericCommand(buf,argc,argv);
     }
 
@@ -410,7 +431,7 @@ int rewriteAppendOnlyFile(char *filename) {
     FILE *fp;
     char tmpfile[256];
     int j;
-    time_t now = time(NULL);
+    long long now = mstime();
 
     /* Note that we have to use a different temp name here compared to the
      * one used by rewriteAppendOnlyFileBackground() function. */
@@ -441,10 +462,10 @@ int rewriteAppendOnlyFile(char *filename) {
         while((de = dictNext(di)) != NULL) {
             sds keystr;
             robj key, *o;
-            time_t expiretime;
+            long long expiretime;
 
-            keystr = dictGetEntryKey(de);
-            o = dictGetEntryVal(de);
+            keystr = dictGetKey(de);
+            o = dictGetVal(de);
             initStaticStringObject(key,keystr);
 
             expiretime = getExpire(db,&key);
@@ -511,7 +532,7 @@ int rewriteAppendOnlyFile(char *filename) {
                     dictIterator *di = dictGetIterator(o->ptr);
                     dictEntry *de;
                     while((de = dictNext(di)) != NULL) {
-                        robj *eleobj = dictGetEntryKey(de);
+                        robj *eleobj = dictGetKey(de);
                         if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
                         if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
                         if (rioWriteBulkObject(&aof,eleobj) == 0) goto werr;
@@ -559,8 +580,8 @@ int rewriteAppendOnlyFile(char *filename) {
                     dictEntry *de;
 
                     while((de = dictNext(di)) != NULL) {
-                        robj *eleobj = dictGetEntryKey(de);
-                        double *score = dictGetEntryVal(de);
+                        robj *eleobj = dictGetKey(de);
+                        double *score = dictGetVal(de);
 
                         if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
                         if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
@@ -593,8 +614,8 @@ int rewriteAppendOnlyFile(char *filename) {
                     dictEntry *de;
 
                     while((de = dictNext(di)) != NULL) {
-                        robj *field = dictGetEntryKey(de);
-                        robj *val = dictGetEntryVal(de);
+                        robj *field = dictGetKey(de);
+                        robj *val = dictGetVal(de);
 
                         if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
                         if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
@@ -608,7 +629,7 @@ int rewriteAppendOnlyFile(char *filename) {
             }
             /* Save the expire time */
             if (expiretime != -1) {
-                char cmd[]="*3\r\n$8\r\nEXPIREAT\r\n";
+                char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
                 /* If this key is already expired skip it */
                 if (expiretime < now) continue;
                 if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
index fadce42eacdb9a8adf082b2ecf8029801e63e762..4ccff657363659b4d8c8920e5c07f88aaf791b08 100644 (file)
@@ -377,7 +377,7 @@ clusterNode *clusterLookupNode(char *name) {
     de = dictFind(server.cluster.nodes,s);
     sdsfree(s);
     if (de == NULL) return NULL;
-    return dictGetEntryVal(de);
+    return dictGetVal(de);
 }
 
 /* This is only used after the handshake. When we connect a given IP/PORT
@@ -793,7 +793,7 @@ void clusterBroadcastMessage(void *buf, size_t len) {
 
     di = dictGetIterator(server.cluster.nodes);
     while((de = dictNext(di)) != NULL) {
-        clusterNode *node = dictGetEntryVal(de);
+        clusterNode *node = dictGetVal(de);
 
         if (!node->link) continue;
         if (node->flags & (REDIS_NODE_MYSELF|REDIS_NODE_NOADDR)) continue;
@@ -849,7 +849,7 @@ void clusterSendPing(clusterLink *link, int type) {
     /* Populate the gossip fields */
     while(freshnodes > 0 && gossipcount < 3) {
         struct dictEntry *de = dictGetRandomKey(server.cluster.nodes);
-        clusterNode *this = dictGetEntryVal(de);
+        clusterNode *this = dictGetVal(de);
         clusterMsgDataGossip *gossip;
         int j;
 
@@ -970,7 +970,7 @@ void clusterCron(void) {
     /* Check if we have disconnected nodes and reestablish the connection. */
     di = dictGetIterator(server.cluster.nodes);
     while((de = dictNext(di)) != NULL) {
-        clusterNode *node = dictGetEntryVal(de);
+        clusterNode *node = dictGetVal(de);
 
         if (node->flags & (REDIS_NODE_MYSELF|REDIS_NODE_NOADDR)) continue;
         if (node->link == NULL) {
@@ -1005,7 +1005,7 @@ void clusterCron(void) {
      * the oldest ping_sent time */
     for (j = 0; j < 5; j++) {
         de = dictGetRandomKey(server.cluster.nodes);
-        clusterNode *this = dictGetEntryVal(de);
+        clusterNode *this = dictGetVal(de);
 
         if (this->link == NULL) continue;
         if (this->flags & (REDIS_NODE_MYSELF|REDIS_NODE_HANDSHAKE)) continue;
@@ -1022,7 +1022,7 @@ void clusterCron(void) {
     /* Iterate nodes to check if we need to flag something as failing */
     di = dictGetIterator(server.cluster.nodes);
     while((de = dictNext(di)) != NULL) {
-        clusterNode *node = dictGetEntryVal(de);
+        clusterNode *node = dictGetVal(de);
         int delay;
 
         if (node->flags &
@@ -1153,7 +1153,7 @@ sds clusterGenNodesDescription(void) {
 
     di = dictGetIterator(server.cluster.nodes);
     while((de = dictNext(di)) != NULL) {
-        clusterNode *node = dictGetEntryVal(de);
+        clusterNode *node = dictGetVal(de);
 
         /* Node coordinates */
         ci = sdscatprintf(ci,"%.40s %s:%d ",
index cc9810b6bdebe5b2254e5ef46c517608491cde95..38983cb5a9d66ce331ee9226ef967ec6e83412fc 100644 (file)
--- a/src/db.c
+++ b/src/db.c
@@ -1,6 +1,7 @@
 #include "redis.h"
 
 #include <signal.h>
+#include <ctype.h>
 
 void SlotToKeyAdd(robj *key);
 void SlotToKeyDel(robj *key);
@@ -34,7 +35,7 @@ void SlotToKeyDel(robj *key);
 robj *lookupKey(redisDb *db, robj *key) {
     dictEntry *de = dictFind(db->dict,key->ptr);
     if (de) {
-        robj *val = dictGetEntryVal(de);
+        robj *val = dictGetVal(de);
 
         /* Update the access time for the aging algorithm.
          * Don't do it if we have a saving child, as this will trigger
@@ -130,7 +131,7 @@ robj *dbRandomKey(redisDb *db) {
         de = dictGetRandomKey(db->dict);
         if (de == NULL) return NULL;
 
-        key = dictGetEntryKey(de);
+        key = dictGetKey(de);
         keyobj = createStringObject(key,sdslen(key));
         if (dictFind(db->expires,key)) {
             if (expireIfNeeded(db,keyobj)) {
@@ -282,7 +283,7 @@ void keysCommand(redisClient *c) {
     di = dictGetIterator(c->db->dict);
     allkeys = (pattern[0] == '*' && pattern[1] == '\0');
     while((de = dictNext(di)) != NULL) {
-        sds key = dictGetEntryKey(de);
+        sds key = dictGetKey(de);
         robj *keyobj;
 
         if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
@@ -334,7 +335,7 @@ void shutdownCommand(redisClient *c) {
 
 void renameGenericCommand(redisClient *c, int nx) {
     robj *o;
-    time_t expire;
+    long long expire;
 
     /* To use the same key as src and dst is probably an error */
     if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) {
@@ -432,18 +433,19 @@ int removeExpire(redisDb *db, robj *key) {
     return dictDelete(db->expires,key->ptr) == DICT_OK;
 }
 
-void setExpire(redisDb *db, robj *key, time_t when) {
-    dictEntry *de;
+void setExpire(redisDb *db, robj *key, long long when) {
+    dictEntry *kde, *de;
 
     /* Reuse the sds from the main dict in the expire dict */
-    de = dictFind(db->dict,key->ptr);
-    redisAssertWithInfo(NULL,key,de != NULL);
-    dictReplace(db->expires,dictGetEntryKey(de),(void*)when);
+    kde = dictFind(db->dict,key->ptr);
+    redisAssertWithInfo(NULL,key,kde != NULL);
+    de = dictReplaceRaw(db->expires,dictGetKey(kde));
+    dictSetSignedIntegerVal(de,when);
 }
 
 /* Return the expire time of the specified key, or -1 if no expire
  * is associated with this key (i.e. the key is non volatile) */
-time_t getExpire(redisDb *db, robj *key) {
+long long getExpire(redisDb *db, robj *key) {
     dictEntry *de;
 
     /* No expire? return ASAP */
@@ -453,7 +455,7 @@ time_t getExpire(redisDb *db, robj *key) {
     /* The entry was found in the expire dict, this means it should also
      * be present in the main dict (safety check). */
     redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
-    return (time_t) dictGetEntryVal(de);
+    return dictGetSignedIntegerVal(de);
 }
 
 /* Propagate expires into slaves and the AOF file.
@@ -481,7 +483,7 @@ void propagateExpire(redisDb *db, robj *key) {
 }
 
 int expireIfNeeded(redisDb *db, robj *key) {
-    time_t when = getExpire(db,key);
+    long long when = getExpire(db,key);
 
     if (when < 0) return 0; /* No expire for this key */
 
@@ -500,7 +502,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
     }
 
     /* Return when this key has not expired */
-    if (time(NULL) <= when) return 0;
+    if (mstime() <= when) return 0;
 
     /* Delete the key */
     server.stat_expiredkeys++;
@@ -512,13 +514,24 @@ int expireIfNeeded(redisDb *db, robj *key) {
  * Expires Commands
  *----------------------------------------------------------------------------*/
 
-void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
+/* Given an string object return true if it contains exactly the "ms"
+ * or "MS" string. This is used in order to check if the last argument
+ * of EXPIRE, EXPIREAT or TTL is "ms" to switch into millisecond input/output */
+int stringObjectEqualsMs(robj *a) {
+    char *arg = a->ptr;
+    return tolower(arg[0]) == 'm' && tolower(arg[1]) == 's' && arg[2] == '\0';
+}
+
+void expireGenericCommand(redisClient *c, long long offset, int unit) {
     dictEntry *de;
-    long seconds;
+    robj *key = c->argv[1], *param = c->argv[2];
+    long long milliseconds;
 
-    if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return;
+    if (getLongLongFromObjectOrReply(c, param, &milliseconds, NULL) != REDIS_OK)
+        return;
 
-    seconds -= offset;
+    if (unit == UNIT_SECONDS) milliseconds *= 1000;
+    milliseconds -= offset;
 
     de = dictFind(c->db->dict,key->ptr);
     if (de == NULL) {
@@ -531,7 +544,7 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
      *
      * Instead we take the other branch of the IF statement setting an expire
      * (possibly in the past) and wait for an explicit DEL from the master. */
-    if (seconds <= 0 && !server.loading && !server.masterhost) {
+    if (milliseconds <= 0 && !server.loading && !server.masterhost) {
         robj *aux;
 
         redisAssertWithInfo(c,key,dbDelete(c->db,key));
@@ -545,7 +558,7 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
         addReply(c, shared.cone);
         return;
     } else {
-        time_t when = time(NULL)+seconds;
+        long long when = mstime()+milliseconds;
         setExpire(c->db,key,when);
         addReply(c,shared.cone);
         signalModifiedKey(c->db,key);
@@ -555,22 +568,42 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
 }
 
 void expireCommand(redisClient *c) {
-    expireGenericCommand(c,c->argv[1],c->argv[2],0);
+    expireGenericCommand(c,0,UNIT_SECONDS);
 }
 
 void expireatCommand(redisClient *c) {
-    expireGenericCommand(c,c->argv[1],c->argv[2],time(NULL));
+    expireGenericCommand(c,mstime(),UNIT_SECONDS);
 }
 
-void ttlCommand(redisClient *c) {
-    time_t expire, ttl = -1;
+void pexpireCommand(redisClient *c) {
+    expireGenericCommand(c,0,UNIT_MILLISECONDS);
+}
+
+void pexpireatCommand(redisClient *c) {
+    expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
+}
+
+void ttlGenericCommand(redisClient *c, int output_ms) {
+    long long expire, ttl = -1;
 
     expire = getExpire(c->db,c->argv[1]);
     if (expire != -1) {
-        ttl = (expire-time(NULL));
+        ttl = expire-mstime();
         if (ttl < 0) ttl = -1;
     }
-    addReplyLongLong(c,(long long)ttl);
+    if (ttl == -1) {
+        addReplyLongLong(c,-1);
+    } else {
+        addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000));
+    }
+}
+
+void ttlCommand(redisClient *c) {
+    ttlGenericCommand(c, 0);
+}
+
+void pttlCommand(redisClient *c) {
+    ttlGenericCommand(c, 1);
 }
 
 void persistCommand(redisClient *c) {
index 7751adf932b280d552d410c7de2537c0bd85a430..376e07125df988bf1942813879635619f2877bb3 100644 (file)
@@ -91,16 +91,16 @@ void computeDatasetDigest(unsigned char *final) {
         while((de = dictNext(di)) != NULL) {
             sds key;
             robj *keyobj, *o;
-            time_t expiretime;
+            long long expiretime;
 
             memset(digest,0,20); /* This key-val digest */
-            key = dictGetEntryKey(de);
+            key = dictGetKey(de);
             keyobj = createStringObject(key,sdslen(key));
 
             mixDigest(digest,key,sdslen(key));
 
             /* Make sure the key is loaded if VM is active */
-            o = dictGetEntryVal(de);
+            o = dictGetVal(de);
 
             aux = htonl(o->type);
             mixDigest(digest,&aux,sizeof(aux));
@@ -165,8 +165,8 @@ void computeDatasetDigest(unsigned char *final) {
                     dictEntry *de;
 
                     while((de = dictNext(di)) != NULL) {
-                        robj *eleobj = dictGetEntryKey(de);
-                        double *score = dictGetEntryVal(de);
+                        robj *eleobj = dictGetKey(de);
+                        double *score = dictGetVal(de);
 
                         snprintf(buf,sizeof(buf),"%.17g",*score);
                         memset(eledigest,0,20);
@@ -244,7 +244,7 @@ void debugCommand(redisClient *c) {
             addReply(c,shared.nokeyerr);
             return;
         }
-        val = dictGetEntryVal(de);
+        val = dictGetVal(de);
         strenc = strEncoding(val->encoding);
 
         addReplyStatusFormat(c,
index 24001fdd0a520098eaecdc00bc893d2761968a40..a573bcd6e3575477f1e58c67811b46122972de88 100644 (file)
@@ -258,6 +258,30 @@ static void _dictRehashStep(dict *d) {
 
 /* Add an element to the target hash table */
 int dictAdd(dict *d, void *key, void *val)
+{
+    dictEntry *entry = dictAddRaw(d,key);
+
+    if (!entry) return DICT_ERR;
+    dictSetVal(d, entry, val);
+    return DICT_OK;
+}
+
+/* Low level add. This function adds the entry but instead of setting
+ * a value returns the dictEntry structure to the user, that will make
+ * sure to fill the value field as he wishes.
+ *
+ * This function is also directly expoed to user API to be called
+ * mainly in order to store non-pointers inside the hash value, example:
+ *
+ * entry = dictAddRaw(dict,mykey);
+ * if (entry != NULL) dictSetSignedIntegerVal(entry,1000);
+ *
+ * Return values:
+ *
+ * If key already exists NULL is returned.
+ * If key was added, the hash entry is returned to be manipulated by the caller.
+ */
+dictEntry *dictAddRaw(dict *d, void *key)
 {
     int index;
     dictEntry *entry;
@@ -268,9 +292,9 @@ int dictAdd(dict *d, void *key, void *val)
     /* Get the index of the new element, or -1 if
      * the element already exists. */
     if ((index = _dictKeyIndex(d, key)) == -1)
-        return DICT_ERR;
+        return NULL;
 
-    /* Allocates the memory and stores key */
+    /* Allocate the memory and store the new entry */
     ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
     entry = zmalloc(sizeof(*entry));
     entry->next = ht->table[index];
@@ -278,9 +302,8 @@ int dictAdd(dict *d, void *key, void *val)
     ht->used++;
 
     /* Set the hash entry fields. */
-    dictSetHashKey(d, entry, key);
-    dictSetHashVal(d, entry, val);
-    return DICT_OK;
+    dictSetKey(d, entry, key);
+    return entry;
 }
 
 /* Add an element, discarding the old if the key already exists.
@@ -297,18 +320,29 @@ int dictReplace(dict *d, void *key, void *val)
         return 1;
     /* It already exists, get the entry */
     entry = dictFind(d, key);
-    /* Free the old value and set the new one */
     /* Set the new value and free the old one. Note that it is important
      * to do that in this order, as the value may just be exactly the same
      * as the previous one. In this context, think to reference counting,
      * you want to increment (set), and then decrement (free), and not the
      * reverse. */
     auxentry = *entry;
-    dictSetHashVal(d, entry, val);
-    dictFreeEntryVal(d, &auxentry);
+    dictSetVal(d, entry, val);
+    dictFreeVal(d, &auxentry);
     return 0;
 }
 
+/* dictReplaceRaw() is simply a version of dictAddRaw() that always
+ * returns the hash entry of the specified key, even if the key already
+ * exists and can't be added (in that case the entry of the already
+ * existing key is returned.)
+ *
+ * See dictAddRaw() for more information. */
+dictEntry *dictReplaceRaw(dict *d, void *key) {
+    dictEntry *entry = dictFind(d,key);
+
+    return entry ? entry : dictAddRaw(d,key);
+}
+
 /* Search and remove an element */
 static int dictGenericDelete(dict *d, const void *key, int nofree)
 {
@@ -325,15 +359,15 @@ static int dictGenericDelete(dict *d, const void *key, int nofree)
         he = d->ht[table].table[idx];
         prevHe = NULL;
         while(he) {
-            if (dictCompareHashKeys(d, key, he->key)) {
+            if (dictCompareKeys(d, key, he->key)) {
                 /* Unlink the element from the list */
                 if (prevHe)
                     prevHe->next = he->next;
                 else
                     d->ht[table].table[idx] = he->next;
                 if (!nofree) {
-                    dictFreeEntryKey(d, he);
-                    dictFreeEntryVal(d, he);
+                    dictFreeKey(d, he);
+                    dictFreeVal(d, he);
                 }
                 zfree(he);
                 d->ht[table].used--;
@@ -367,8 +401,8 @@ int _dictClear(dict *d, dictht *ht)
         if ((he = ht->table[i]) == NULL) continue;
         while(he) {
             nextHe = he->next;
-            dictFreeEntryKey(d, he);
-            dictFreeEntryVal(d, he);
+            dictFreeKey(d, he);
+            dictFreeVal(d, he);
             zfree(he);
             ht->used--;
             he = nextHe;
@@ -401,7 +435,7 @@ dictEntry *dictFind(dict *d, const void *key)
         idx = h & d->ht[table].sizemask;
         he = d->ht[table].table[idx];
         while(he) {
-            if (dictCompareHashKeys(d, key, he->key))
+            if (dictCompareKeys(d, key, he->key))
                 return he;
             he = he->next;
         }
@@ -414,7 +448,7 @@ void *dictFetchValue(dict *d, const void *key) {
     dictEntry *he;
 
     he = dictFind(d,key);
-    return he ? dictGetEntryVal(he) : NULL;
+    return he ? dictGetVal(he) : NULL;
 }
 
 dictIterator *dictGetIterator(dict *d)
@@ -573,7 +607,7 @@ static int _dictKeyIndex(dict *d, const void *key)
         /* Search if this slot does not already contain the given key */
         he = d->ht[table].table[idx];
         while(he) {
-            if (dictCompareHashKeys(d, key, he->key))
+            if (dictCompareKeys(d, key, he->key))
                 return -1;
             he = he->next;
         }
index 74bcd2aad67531c9c08a1360736a98c740a8e284..76451047324bd0de58c92d31c056899e1ec4dd55 100644 (file)
@@ -33,6 +33,8 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <stdint.h>
+
 #ifndef __DICT_H
 #define __DICT_H
 
 
 typedef struct dictEntry {
     void *key;
-    void *val;
+    union {
+        void *val;
+        uint64_t u64;
+        int64_t s64;
+    } v;
     struct dictEntry *next;
 } dictEntry;
 
@@ -88,37 +94,44 @@ typedef struct dictIterator {
 #define DICT_HT_INITIAL_SIZE     4
 
 /* ------------------------------- Macros ------------------------------------*/
-#define dictFreeEntryVal(d, entry) \
+#define dictFreeVal(d, entry) \
     if ((d)->type->valDestructor) \
-        (d)->type->valDestructor((d)->privdata, (entry)->val)
+        (d)->type->valDestructor((d)->privdata, (entry)->v.val)
 
-#define dictSetHashVal(d, entry, _val_) do { \
+#define dictSetVal(d, entry, _val_) do { \
     if ((d)->type->valDup) \
-        entry->val = (d)->type->valDup((d)->privdata, _val_); \
+        entry->v.val = (d)->type->valDup((d)->privdata, _val_); \
     else \
-        entry->val = (_val_); \
+        entry->v.val = (_val_); \
 } while(0)
 
-#define dictFreeEntryKey(d, entry) \
+#define dictSetSignedIntegerVal(entry, _val_) \
+    do { entry->v.s64 = _val_; } while(0)
+
+#define dictSetUnsignedIntegerVal(entry, _val_) \
+    do { entry->v.u64 = _val_; } while(0)
+
+#define dictFreeKey(d, entry) \
     if ((d)->type->keyDestructor) \
         (d)->type->keyDestructor((d)->privdata, (entry)->key)
 
-#define dictSetHashKey(d, entry, _key_) do { \
+#define dictSetKey(d, entry, _key_) do { \
     if ((d)->type->keyDup) \
         entry->key = (d)->type->keyDup((d)->privdata, _key_); \
     else \
         entry->key = (_key_); \
 } while(0)
 
-#define dictCompareHashKeys(d, key1, key2) \
+#define dictCompareKeys(d, key1, key2) \
     (((d)->type->keyCompare) ? \
         (d)->type->keyCompare((d)->privdata, key1, key2) : \
         (key1) == (key2))
 
 #define dictHashKey(d, key) (d)->type->hashFunction(key)
-
-#define dictGetEntryKey(he) ((he)->key)
-#define dictGetEntryVal(he) ((he)->val)
+#define dictGetKey(he) ((he)->key)
+#define dictGetVal(he) ((he)->v.val)
+#define dictGetSignedIntegerVal(he) ((he)->v.s64)
+#define dictGetUnsignedIntegerVal(he) ((he)->v.u64)
 #define dictSlots(d) ((d)->ht[0].size+(d)->ht[1].size)
 #define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)
 #define dictIsRehashing(ht) ((ht)->rehashidx != -1)
@@ -127,7 +140,9 @@ typedef struct dictIterator {
 dict *dictCreate(dictType *type, void *privDataPtr);
 int dictExpand(dict *d, unsigned long size);
 int dictAdd(dict *d, void *key, void *val);
+dictEntry *dictAddRaw(dict *d, void *key);
 int dictReplace(dict *d, void *key, void *val);
+dictEntry *dictReplaceRaw(dict *d, void *key);
 int dictDelete(dict *d, const void *key);
 int dictDeleteNoFree(dict *d, const void *key);
 void dictRelease(dict *d);
index 23462a5b595835b0f0fe45bcee14251aa7a3aacf..0711afed7aa840f15d970bb7399c7e233bc306bd 100644 (file)
@@ -1,5 +1,6 @@
 #include "redis.h"
 #include <math.h>
+#include <ctype.h>
 
 robj *createObject(int type, void *ptr) {
     robj *o = zmalloc(sizeof(*o));
@@ -44,6 +45,21 @@ robj *createStringObjectFromLongLong(long long value) {
     return o;
 }
 
+/* Note: this function is defined into object.c since here it is where it
+ * belongs but it is actually designed to be used just for INCRBYFLOAT */
+robj *createStringObjectFromLongDouble(long double value) {
+    char buf[256];
+    int len;
+
+    /* We use 17 digits precision since with 128 bit floats that precision
+     * after rouding is able to represent most small decimal numbers in a way
+     * that is "non surprising" for the user (that is, most small decimal
+     * numbers will be represented in a way that when converted back into
+     * a string are exactly the same as what the user typed.) */
+    len = snprintf(buf,sizeof(buf),"%.17Lg", value);
+    return createStringObject(buf,len);
+}
+
 robj *dupStringObject(robj *o) {
     redisAssertWithInfo(NULL,o,o->encoding == REDIS_ENCODING_RAW);
     return createStringObject(o->ptr,sdslen(o->ptr));
@@ -350,15 +366,17 @@ int getDoubleFromObject(robj *o, double *target) {
     } else {
         redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
         if (o->encoding == REDIS_ENCODING_RAW) {
+            errno = 0;
             value = strtod(o->ptr, &eptr);
-            if (eptr[0] != '\0' || isnan(value)) return REDIS_ERR;
+            if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
+                errno == ERANGE || isnan(value))
+                return REDIS_ERR;
         } else if (o->encoding == REDIS_ENCODING_INT) {
             value = (long)o->ptr;
         } else {
             redisPanic("Unknown string encoding");
         }
     }
-
     *target = value;
     return REDIS_OK;
 }
@@ -369,11 +387,48 @@ int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const ch
         if (msg != NULL) {
             addReplyError(c,(char*)msg);
         } else {
-            addReplyError(c,"value is not a double");
+            addReplyError(c,"value is not a valid float");
         }
         return REDIS_ERR;
     }
+    *target = value;
+    return REDIS_OK;
+}
+
+int getLongDoubleFromObject(robj *o, long double *target) {
+    long double value;
+    char *eptr;
 
+    if (o == NULL) {
+        value = 0;
+    } else {
+        redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
+        if (o->encoding == REDIS_ENCODING_RAW) {
+            errno = 0;
+            value = strtold(o->ptr, &eptr);
+            if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
+                errno == ERANGE || isnan(value))
+                return REDIS_ERR;
+        } else if (o->encoding == REDIS_ENCODING_INT) {
+            value = (long)o->ptr;
+        } else {
+            redisPanic("Unknown string encoding");
+        }
+    }
+    *target = value;
+    return REDIS_OK;
+}
+
+int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg) {
+    long double value;
+    if (getLongDoubleFromObject(o, &value) != REDIS_OK) {
+        if (msg != NULL) {
+            addReplyError(c,(char*)msg);
+        } else {
+            addReplyError(c,"value is not a valid float");
+        }
+        return REDIS_ERR;
+    }
     *target = value;
     return REDIS_OK;
 }
@@ -387,9 +442,10 @@ int getLongLongFromObject(robj *o, long long *target) {
     } else {
         redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
         if (o->encoding == REDIS_ENCODING_RAW) {
+            errno = 0;
             value = strtoll(o->ptr, &eptr, 10);
-            if (eptr[0] != '\0') return REDIS_ERR;
-            if (errno == ERANGE && (value == LLONG_MIN || value == LLONG_MAX))
+            if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
+                errno == ERANGE)
                 return REDIS_ERR;
         } else if (o->encoding == REDIS_ENCODING_INT) {
             value = (long)o->ptr;
@@ -397,7 +453,6 @@ int getLongLongFromObject(robj *o, long long *target) {
             redisPanic("Unknown string encoding");
         }
     }
-
     if (target) *target = value;
     return REDIS_OK;
 }
@@ -412,7 +467,6 @@ int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, con
         }
         return REDIS_ERR;
     }
-
     *target = value;
     return REDIS_OK;
 }
@@ -429,7 +483,6 @@ int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *
         }
         return REDIS_ERR;
     }
-
     *target = value;
     return REDIS_OK;
 }
@@ -465,7 +518,7 @@ robj *objectCommandLookup(redisClient *c, robj *key) {
     dictEntry *de;
 
     if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL;
-    return (robj*) dictGetEntryVal(de);
+    return (robj*) dictGetVal(de);
 }
 
 robj *objectCommandLookupOrReply(redisClient *c, robj *key, robj *reply) {
index af37df5c431f17ad5dcd79daec76f415cd72378d..27e6f9a5819b49cddb4bd4766c9af2fb2e076d6a 100644 (file)
@@ -36,7 +36,7 @@ int pubsubSubscribeChannel(redisClient *c, robj *channel) {
             dictAdd(server.pubsub_channels,channel,clients);
             incrRefCount(channel);
         } else {
-            clients = dictGetEntryVal(de);
+            clients = dictGetVal(de);
         }
         listAddNodeTail(clients,c);
     }
@@ -64,7 +64,7 @@ int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) {
         /* Remove the client from the channel -> clients list hash table */
         de = dictFind(server.pubsub_channels,channel);
         redisAssertWithInfo(c,NULL,de != NULL);
-        clients = dictGetEntryVal(de);
+        clients = dictGetVal(de);
         ln = listSearchKey(clients,c);
         redisAssertWithInfo(c,NULL,ln != NULL);
         listDelNode(clients,ln);
@@ -146,7 +146,7 @@ int pubsubUnsubscribeAllChannels(redisClient *c, int notify) {
     int count = 0;
 
     while((de = dictNext(di)) != NULL) {
-        robj *channel = dictGetEntryKey(de);
+        robj *channel = dictGetKey(de);
 
         count += pubsubUnsubscribeChannel(c,channel,notify);
     }
@@ -180,7 +180,7 @@ int pubsubPublishMessage(robj *channel, robj *message) {
     /* Send to clients listening for that channel */
     de = dictFind(server.pubsub_channels,channel);
     if (de) {
-        list *list = dictGetEntryVal(de);
+        list *list = dictGetVal(de);
         listNode *ln;
         listIter li;
 
index d2d54807e350792b149cafc97b8f26858224e636..2c0feb6ded874318b19226a14ea6026c58d705e5 100644 (file)
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -36,6 +36,17 @@ time_t rdbLoadTime(rio *rdb) {
     return (time_t)t32;
 }
 
+int rdbSaveMillisecondTime(rio *rdb, long long t) {
+    int64_t t64 = (int64_t) t;
+    return rdbWriteRaw(rdb,&t64,8);
+}
+
+long long rdbLoadMillisecondTime(rio *rdb) {
+    int64_t t64;
+    if (rioRead(rdb,&t64,8) == 0) return -1;
+    return (long long)t64;
+}
+
 /* Saves an encoded length. The first two bits in the first byte are used to
  * hold the encoding type. See the REDIS_RDB_* definitions for more information
  * on the types of encoding. */
@@ -476,7 +487,7 @@ int rdbSaveObject(rio *rdb, robj *o) {
             nwritten += n;
 
             while((de = dictNext(di)) != NULL) {
-                robj *eleobj = dictGetEntryKey(de);
+                robj *eleobj = dictGetKey(de);
                 if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                 nwritten += n;
             }
@@ -505,8 +516,8 @@ int rdbSaveObject(rio *rdb, robj *o) {
             nwritten += n;
 
             while((de = dictNext(di)) != NULL) {
-                robj *eleobj = dictGetEntryKey(de);
-                double *score = dictGetEntryVal(de);
+                robj *eleobj = dictGetKey(de);
+                double *score = dictGetVal(de);
 
                 if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                 nwritten += n;
@@ -532,8 +543,8 @@ int rdbSaveObject(rio *rdb, robj *o) {
             nwritten += n;
 
             while((de = dictNext(di)) != NULL) {
-                robj *key = dictGetEntryKey(de);
-                robj *val = dictGetEntryVal(de);
+                robj *key = dictGetKey(de);
+                robj *val = dictGetVal(de);
 
                 if ((n = rdbSaveStringObject(rdb,key)) == -1) return -1;
                 nwritten += n;
@@ -563,14 +574,14 @@ off_t rdbSavedObjectLen(robj *o) {
  * On success if the key was actaully saved 1 is returned, otherwise 0
  * is returned (the key was already expired). */
 int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
-                        time_t expiretime, time_t now)
+                        long long expiretime, long long now)
 {
     /* Save the expire time */
     if (expiretime != -1) {
         /* If this key is already expired skip it */
         if (expiretime < now) return 0;
-        if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME) == -1) return -1;
-        if (rdbSaveTime(rdb,expiretime) == -1) return -1;
+        if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
+        if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
     }
 
     /* Save type, key, value */
@@ -586,7 +597,7 @@ int rdbSave(char *filename) {
     dictEntry *de;
     char tmpfile[256];
     int j;
-    time_t now = time(NULL);
+    long long now = mstime();
     FILE *fp;
     rio rdb;
 
@@ -599,7 +610,7 @@ int rdbSave(char *filename) {
     }
 
     rioInitWithFile(&rdb,fp);
-    if (rdbWriteRaw(&rdb,"REDIS0002",9) == -1) goto werr;
+    if (rdbWriteRaw(&rdb,"REDIS0003",9) == -1) goto werr;
 
     for (j = 0; j < server.dbnum; j++) {
         redisDb *db = server.db+j;
@@ -617,9 +628,9 @@ int rdbSave(char *filename) {
 
         /* Iterate this DB writing every entry */
         while((de = dictNext(di)) != NULL) {
-            sds keystr = dictGetEntryKey(de);
-            robj key, *o = dictGetEntryVal(de);
-            time_t expire;
+            sds keystr = dictGetKey(de);
+            robj key, *o = dictGetVal(de);
+            long long expire;
             
             initStaticStringObject(key,keystr);
             expire = getExpire(db,&key);
@@ -942,7 +953,7 @@ int rdbLoad(char *filename) {
     int type, rdbver;
     redisDb *db = server.db+0;
     char buf[1024];
-    time_t expiretime, now = time(NULL);
+    long long expiretime, now = mstime();
     long loops = 0;
     FILE *fp;
     rio rdb;
@@ -962,7 +973,7 @@ int rdbLoad(char *filename) {
         return REDIS_ERR;
     }
     rdbver = atoi(buf+5);
-    if (rdbver < 1 || rdbver > 2) {
+    if (rdbver < 1 || rdbver > 3) {
         fclose(fp);
         redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver);
         errno = EINVAL;
@@ -986,6 +997,15 @@ int rdbLoad(char *filename) {
             if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
             /* We read the time so we need to read the object type again. */
             if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
+            /* the EXPIRETIME opcode specifies time in seconds, so convert
+             * into milliesconds. */
+            expiretime *= 1000;
+        } else if (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {
+            /* Milliseconds precision expire times introduced with RDB
+             * version 3. */
+            if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
+            /* We read the time so we need to read the object type again. */
+            if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
         }
 
         if (type == REDIS_RDB_OPCODE_EOF)
index fec16ffb13df93bf49651608369d0bb3e605b32e..827947b4a420d6f0faefda75934489d870088a8b 100644 (file)
--- a/src/rdb.h
+++ b/src/rdb.h
@@ -52,6 +52,7 @@
 #define rdbIsObjectType(t) ((t >= 0 && t <= 4) || (t >= 9 && t <= 12))
 
 /* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
+#define REDIS_RDB_OPCODE_EXPIRETIME_MS 252
 #define REDIS_RDB_OPCODE_EXPIRETIME 253
 #define REDIS_RDB_OPCODE_SELECTDB   254
 #define REDIS_RDB_OPCODE_EOF        255
@@ -76,7 +77,7 @@ off_t rdbSavedObjectLen(robj *o);
 off_t rdbSavedObjectPages(robj *o);
 robj *rdbLoadObject(int type, rio *rdb);
 void backgroundSaveDoneHandler(int exitcode, int bysignal);
-int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, time_t expireitme, time_t now);
+int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime, long long now);
 robj *rdbLoadStringObject(rio *rdb);
 
 #endif
index 832a9f5926e79d7588be3ff9f9e6220242de87aa..606ac978ee94a27f23cb285be046e0ea781aeb0d 100644 (file)
@@ -91,6 +91,7 @@ struct redisCommand redisCommandTable[] = {
     {"set",setCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
     {"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
     {"setex",setexCommand,4,"wm",0,noPreloadGetKeys,2,2,1,0,0},
+    {"psetex",psetexCommand,4,"wm",0,noPreloadGetKeys,2,2,1,0,0},
     {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
     {"strlen",strlenCommand,2,"r",0,NULL,1,1,1,0,0},
     {"del",delCommand,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0},
@@ -156,6 +157,7 @@ struct redisCommand redisCommandTable[] = {
     {"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0},
     {"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0},
     {"hincrby",hincrbyCommand,4,"wm",0,NULL,1,1,1,0,0},
+    {"hincrbyfloat",hincrbyfloatCommand,4,"wm",0,NULL,1,1,1,0,0},
     {"hdel",hdelCommand,-3,"w",0,NULL,1,1,1,0,0},
     {"hlen",hlenCommand,2,"r",0,NULL,1,1,1,0,0},
     {"hkeys",hkeysCommand,2,"r",0,NULL,1,1,1,0,0},
@@ -164,6 +166,7 @@ struct redisCommand redisCommandTable[] = {
     {"hexists",hexistsCommand,3,"r",0,NULL,1,1,1,0,0},
     {"incrby",incrbyCommand,3,"wm",0,NULL,1,1,1,0,0},
     {"decrby",decrbyCommand,3,"wm",0,NULL,1,1,1,0,0},
+    {"incrbyfloat",incrbyfloatCommand,3,"wm",0,NULL,1,1,1,0,0},
     {"getset",getsetCommand,3,"wm",0,NULL,1,1,1,0,0},
     {"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0},
     {"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0},
@@ -174,6 +177,8 @@ struct redisCommand redisCommandTable[] = {
     {"renamenx",renamenxCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
     {"expire",expireCommand,3,"w",0,NULL,1,1,1,0,0},
     {"expireat",expireatCommand,3,"w",0,NULL,1,1,1,0,0},
+    {"pexpire",pexpireCommand,3,"w",0,NULL,1,1,1,0,0},
+    {"pexpireat",pexpireatCommand,3,"w",0,NULL,1,1,1,0,0},
     {"keys",keysCommand,2,"r",0,NULL,0,0,0,0,0},
     {"dbsize",dbsizeCommand,1,"r",0,NULL,0,0,0,0,0},
     {"auth",authCommand,2,"r",0,NULL,0,0,0,0,0},
@@ -195,6 +200,7 @@ struct redisCommand redisCommandTable[] = {
     {"info",infoCommand,-1,"r",0,NULL,0,0,0,0,0},
     {"monitor",monitorCommand,1,"ars",0,NULL,0,0,0,0,0},
     {"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0},
+    {"pttl",pttlCommand,2,"r",0,NULL,1,1,1,0,0},
     {"persist",persistCommand,2,"w",0,NULL,1,1,1,0,0},
     {"slaveof",slaveofCommand,3,"aws",0,NULL,0,0,0,0,0},
     {"debug",debugCommand,-2,"aw",0,NULL,0,0,0,0,0},
@@ -288,6 +294,11 @@ long long ustime(void) {
     return ust;
 }
 
+/* Return the UNIX time in milliseconds */
+long long mstime(void) {
+    return ustime()/1000;
+}
+
 /*====================== Hash table type implementation  ==================== */
 
 /* This is an hash table type that uses the SDS dynamic strings libary as
@@ -553,19 +564,19 @@ void activeExpireCycle(void) {
          * of the keys were expired. */
         do {
             long num = dictSize(db->expires);
-            time_t now = time(NULL);
+            long long now = mstime();
 
             expired = 0;
             if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
                 num = REDIS_EXPIRELOOKUPS_PER_CRON;
             while (num--) {
                 dictEntry *de;
-                time_t t;
+                long long t;
 
                 if ((de = dictGetRandomKey(db->expires)) == NULL) break;
-                t = (time_t) dictGetEntryVal(de);
+                t = dictGetSignedIntegerVal(de);
                 if (now > t) {
-                    sds key = dictGetEntryKey(de);
+                    sds key = dictGetKey(de);
                     robj *keyobj = createStringObject(key,sdslen(key));
 
                     propagateExpire(db,keyobj);
@@ -1679,7 +1690,7 @@ void freeMemoryIfNeeded(void) {
                 server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM)
             {
                 de = dictGetRandomKey(dict);
-                bestkey = dictGetEntryKey(de);
+                bestkey = dictGetKey(de);
             }
 
             /* volatile-lru and allkeys-lru policy */
@@ -1692,12 +1703,12 @@ void freeMemoryIfNeeded(void) {
                     robj *o;
 
                     de = dictGetRandomKey(dict);
-                    thiskey = dictGetEntryKey(de);
+                    thiskey = dictGetKey(de);
                     /* When policy is volatile-lru we need an additonal lookup
                      * to locate the real key, as dict is set to db->expires. */
                     if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
                         de = dictFind(db->dict, thiskey);
-                    o = dictGetEntryVal(de);
+                    o = dictGetVal(de);
                     thisval = estimateObjectIdleTime(o);
 
                     /* Higher idle time is better candidate for deletion */
@@ -1715,8 +1726,8 @@ void freeMemoryIfNeeded(void) {
                     long thisval;
 
                     de = dictGetRandomKey(dict);
-                    thiskey = dictGetEntryKey(de);
-                    thisval = (long) dictGetEntryVal(de);
+                    thiskey = dictGetKey(de);
+                    thisval = (long) dictGetVal(de);
 
                     /* Expire sooner (minor expire unix timestamp) is better
                      * candidate for deletion */
index 8cc2474f4ce4faf8fe39a13fd7e4e270e006fc71..0330db74e670cc73e38b2f6210fae06784c6082e 100644 (file)
@@ -39,7 +39,7 @@
 
 /* Static server configuration */
 #define REDIS_SERVERPORT        6379    /* TCP port */
-#define REDIS_MAXIDLETIME       (60*5)  /* default client timeout */
+#define REDIS_MAXIDLETIME       0       /* default client timeout: infinite */
 #define REDIS_IOBUF_LEN         (1024*16)
 #define REDIS_LOADBUF_LEN       1024
 #define REDIS_DEFAULT_DBNUM     16
 #define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
 #define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
 
-/* Object types only used for dumping to disk */
-#define REDIS_EXPIRETIME 253
-#define REDIS_SELECTDB 254
-#define REDIS_EOF 255
-
 /* Defines related to the dump file format. To store 32 bits lengths for short
  * keys requires a lot of space, so we check the most significant 2 bits of
  * the first byte to interpreter the length:
 /* Scripting */
 #define REDIS_LUA_TIME_LIMIT 5000 /* milliseconds */
 
+/* Units */
+#define UNIT_SECONDS 0
+#define UNIT_MILLISECONDS 1
+
 /* We can print the stacktrace, so our assert is defined this way: */
 #define redisAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_redisAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1)))
 #define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
@@ -726,6 +725,7 @@ dictType hashDictType;
 
 /* Utils */
 long long ustime(void);
+long long mstime(void);
 
 /* networking.c -- Networking and Client related operations */
 redisClient *createClient(int fd);
@@ -811,6 +811,7 @@ robj *tryObjectEncoding(robj *o);
 robj *getDecodedObject(robj *o);
 size_t stringObjectLen(robj *o);
 robj *createStringObjectFromLongLong(long long value);
+robj *createStringObjectFromLongDouble(long double value);
 robj *createListObject(void);
 robj *createZiplistObject(void);
 robj *createSetObject(void);
@@ -823,6 +824,8 @@ int checkType(redisClient *c, robj *o, int type);
 int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg);
 int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg);
 int getLongLongFromObject(robj *o, long long *target);
+int getLongDoubleFromObject(robj *o, long double *target);
+int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg);
 char *strEncoding(int encoding);
 int compareStringObjects(robj *a, robj *b);
 int equalStringObjects(robj *a, robj *b);
@@ -940,8 +943,8 @@ void resetServerSaveParams();
 int removeExpire(redisDb *db, robj *key);
 void propagateExpire(redisDb *db, robj *key);
 int expireIfNeeded(redisDb *db, robj *key);
-time_t getExpire(redisDb *db, robj *key);
-void setExpire(redisDb *db, robj *key, time_t when);
+long long getExpire(redisDb *db, robj *key);
+void setExpire(redisDb *db, robj *key, long long when);
 robj *lookupKey(redisDb *db, robj *key);
 robj *lookupKeyRead(redisDb *db, robj *key);
 robj *lookupKeyWrite(redisDb *db, robj *key);
@@ -992,6 +995,7 @@ void echoCommand(redisClient *c);
 void setCommand(redisClient *c);
 void setnxCommand(redisClient *c);
 void setexCommand(redisClient *c);
+void psetexCommand(redisClient *c);
 void getCommand(redisClient *c);
 void delCommand(redisClient *c);
 void existsCommand(redisClient *c);
@@ -1003,6 +1007,7 @@ void incrCommand(redisClient *c);
 void decrCommand(redisClient *c);
 void incrbyCommand(redisClient *c);
 void decrbyCommand(redisClient *c);
+void incrbyfloatCommand(redisClient *c);
 void selectCommand(redisClient *c);
 void randomkeyCommand(redisClient *c);
 void keysCommand(redisClient *c);
@@ -1052,8 +1057,11 @@ void mgetCommand(redisClient *c);
 void monitorCommand(redisClient *c);
 void expireCommand(redisClient *c);
 void expireatCommand(redisClient *c);
+void pexpireCommand(redisClient *c);
+void pexpireatCommand(redisClient *c);
 void getsetCommand(redisClient *c);
 void ttlCommand(redisClient *c);
+void pttlCommand(redisClient *c);
 void persistCommand(redisClient *c);
 void slaveofCommand(redisClient *c);
 void debugCommand(redisClient *c);
@@ -1096,6 +1104,7 @@ void hgetallCommand(redisClient *c);
 void hexistsCommand(redisClient *c);
 void configCommand(redisClient *c);
 void hincrbyCommand(redisClient *c);
+void hincrbyfloatCommand(redisClient *c);
 void subscribeCommand(redisClient *c);
 void unsubscribeCommand(redisClient *c);
 void psubscribeCommand(redisClient *c);
index da31b1b538c3d94eef4166571da01afdf4cd3a87..f70810b9baeeef1a66eb4b6500c0f5d25c8e2e17 100644 (file)
@@ -238,7 +238,7 @@ void sortCommand(redisClient *c) {
         dictEntry *setele;
         di = dictGetIterator(set);
         while((setele = dictNext(di)) != NULL) {
-            vector[j].obj = dictGetEntryKey(setele);
+            vector[j].obj = dictGetKey(setele);
             vector[j].u.score = 0;
             vector[j].u.cmpobj = NULL;
             j++;
index 3ccdfd142c81ac9a4328fe1f8fcbfaaf042458ee..8ee5485c1ad86f877d6f24e6bce98ca461f7f172 100644 (file)
@@ -55,7 +55,7 @@ int hashTypeGet(robj *o, robj *key, robj **objval, unsigned char **v,
     } else {
         dictEntry *de = dictFind(o->ptr,key);
         if (de == NULL) return -1;
-        *objval = dictGetEntryVal(de);
+        *objval = dictGetVal(de);
     }
     return o->encoding;
 }
@@ -206,9 +206,9 @@ int hashTypeCurrent(hashTypeIterator *hi, int what, robj **objval, unsigned char
         }
     } else {
         if (what & REDIS_HASH_KEY)
-            *objval = dictGetEntryKey(hi->de);
+            *objval = dictGetKey(hi->de);
         else
-            *objval = dictGetEntryVal(hi->de);
+            *objval = dictGetVal(hi->de);
     }
     return hi->encoding;
 }
@@ -346,6 +346,33 @@ void hincrbyCommand(redisClient *c) {
     server.dirty++;
 }
 
+void hincrbyfloatCommand(redisClient *c) {
+    double long value, incr;
+    robj *o, *current, *new;
+
+    if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
+    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
+    if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
+        if (getLongDoubleFromObjectOrReply(c,current,&value,
+            "hash value is not a valid float") != REDIS_OK) {
+            decrRefCount(current);
+            return;
+        }
+        decrRefCount(current);
+    } else {
+        value = 0;
+    }
+
+    value += incr;
+    new = createStringObjectFromLongDouble(value);
+    hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
+    hashTypeSet(o,c->argv[2],new);
+    addReplyBulk(c,new);
+    decrRefCount(new);
+    signalModifiedKey(c->db,c->argv[1]);
+    server.dirty++;
+}
+
 void hgetCommand(redisClient *c) {
     robj *o, *value;
     unsigned char *v;
index c5e3df61490651f0fce92477e93b05e7674c6f1f..21fd1c2f5f333bc0fc9f0f6aa6efb57aa45001c0 100644 (file)
@@ -776,7 +776,7 @@ void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout, robj
             incrRefCount(keys[j]);
             redisAssertWithInfo(c,keys[j],retval == DICT_OK);
         } else {
-            l = dictGetEntryVal(de);
+            l = dictGetVal(de);
         }
         listAddNodeTail(l,c);
     }
@@ -797,7 +797,7 @@ void unblockClientWaitingData(redisClient *c) {
         /* Remove this client from the list of clients waiting for this key. */
         de = dictFind(c->db->blocking_keys,c->bpop.keys[j]);
         redisAssertWithInfo(c,c->bpop.keys[j],de != NULL);
-        l = dictGetEntryVal(de);
+        l = dictGetVal(de);
         listDelNode(l,listSearchKey(l,c));
         /* If the list is empty we need to remove it to avoid wasting memory */
         if (listLength(l) == 0)
@@ -836,7 +836,7 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
 
     de = dictFind(c->db->blocking_keys,key);
     if (de == NULL) return 0;
-    clients = dictGetEntryVal(de);
+    clients = dictGetVal(de);
     numclients = listLength(clients);
 
     /* Try to handle the push as long as there are clients waiting for a push.
index db54ffe7af972f6379393fa3449b11ae2ed410a5..3cf1cf005ae7f79d2b919e036d3ceeaf17ce1fc4 100644 (file)
@@ -115,7 +115,7 @@ int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele) {
     if (si->encoding == REDIS_ENCODING_HT) {
         dictEntry *de = dictNext(si->di);
         if (de == NULL) return -1;
-        *objele = dictGetEntryKey(de);
+        *objele = dictGetKey(de);
     } else if (si->encoding == REDIS_ENCODING_INTSET) {
         if (!intsetGet(si->subject->ptr,si->ii++,llele))
             return -1;
@@ -165,7 +165,7 @@ robj *setTypeNextObject(setTypeIterator *si) {
 int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {
     if (setobj->encoding == REDIS_ENCODING_HT) {
         dictEntry *de = dictGetRandomKey(setobj->ptr);
-        *objele = dictGetEntryKey(de);
+        *objele = dictGetKey(de);
     } else if (setobj->encoding == REDIS_ENCODING_INTSET) {
         *llele = intsetRandom(setobj->ptr);
     } else {
index e0b9b263dc11e81939258555bd9248066b849660..2bd1646e305627ecab073893553a40f48444c2bf 100644 (file)
@@ -1,4 +1,5 @@
 #include "redis.h"
+#include <math.h> /* isnan(), isinf() */
 
 /*-----------------------------------------------------------------------------
  * String Commands
@@ -12,16 +13,17 @@ static int checkStringLength(redisClient *c, long long size) {
     return REDIS_OK;
 }
 
-void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) {
-    long seconds = 0; /* initialized to avoid an harmness warning */
+void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire, int unit) {
+    long long milliseconds = 0; /* initialized to avoid an harmness warning */
 
     if (expire) {
-        if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK)
+        if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
             return;
-        if (seconds <= 0) {
+        if (milliseconds <= 0) {
             addReplyError(c,"invalid expire time in SETEX");
             return;
         }
+        if (unit == UNIT_SECONDS) milliseconds *= 1000;
     }
 
     if (lookupKeyWrite(c->db,key) != NULL && nx) {
@@ -30,23 +32,28 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir
     }
     setKey(c->db,key,val);
     server.dirty++;
-    if (expire) setExpire(c->db,key,time(NULL)+seconds);
+    if (expire) setExpire(c->db,key,mstime()+milliseconds);
     addReply(c, nx ? shared.cone : shared.ok);
 }
 
 void setCommand(redisClient *c) {
     c->argv[2] = tryObjectEncoding(c->argv[2]);
-    setGenericCommand(c,0,c->argv[1],c->argv[2],NULL);
+    setGenericCommand(c,0,c->argv[1],c->argv[2],NULL,0);
 }
 
 void setnxCommand(redisClient *c) {
     c->argv[2] = tryObjectEncoding(c->argv[2]);
-    setGenericCommand(c,1,c->argv[1],c->argv[2],NULL);
+    setGenericCommand(c,1,c->argv[1],c->argv[2],NULL,0);
 }
 
 void setexCommand(redisClient *c) {
     c->argv[3] = tryObjectEncoding(c->argv[3]);
-    setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2]);
+    setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS);
+}
+
+void psetexCommand(redisClient *c) {
+    c->argv[3] = tryObjectEncoding(c->argv[3]);
+    setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS);
 }
 
 int getGenericCommand(redisClient *c) {
@@ -376,6 +383,39 @@ void decrbyCommand(redisClient *c) {
     incrDecrCommand(c,-incr);
 }
 
+void incrbyfloatCommand(redisClient *c) {
+    long double incr, value;
+    robj *o, *new, *aux;
+
+    o = lookupKeyWrite(c->db,c->argv[1]);
+    if (o != NULL && checkType(c,o,REDIS_STRING)) return;
+    if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK ||
+        getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK)
+        return;
+
+    value += incr;
+    if (isnan(value) || isinf(value)) {
+        addReplyError(c,"increment would produce NaN or Infinity");
+        return;
+    }
+    new = createStringObjectFromLongDouble(value);
+    if (o)
+        dbOverwrite(c->db,c->argv[1],new);
+    else
+        dbAdd(c->db,c->argv[1],new);
+    signalModifiedKey(c->db,c->argv[1]);
+    server.dirty++;
+    addReplyBulk(c,new);
+
+    /* Always replicate INCRBYFLOAT as a SET command with the final value
+     * in order to make sure that differences in float pricision or formatting
+     * will not create differences in replicas or after an AOF restart. */
+    aux = createStringObject("SET",3);
+    rewriteClientCommandArgument(c,0,aux);
+    decrRefCount(aux);
+    rewriteClientCommandArgument(c,2,new);
+}
+
 void appendCommand(redisClient *c) {
     size_t totlen;
     robj *o, *append;
index 5dab64dc01c2990e736b32984ded1b2861a67215..ccf9962a19f206c521f8b966d79aaed9034648ba 100644 (file)
@@ -900,8 +900,8 @@ void zaddGenericCommand(redisClient *c, int incr) {
             ele = c->argv[3+j*2] = tryObjectEncoding(c->argv[3+j*2]);
             de = dictFind(zs->dict,ele);
             if (de != NULL) {
-                curobj = dictGetEntryKey(de);
-                curscore = *(double*)dictGetEntryVal(de);
+                curobj = dictGetKey(de);
+                curscore = *(double*)dictGetVal(de);
 
                 if (incr) {
                     score += curscore;
@@ -921,7 +921,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
                     redisAssertWithInfo(c,curobj,zslDelete(zs->zsl,curscore,curobj));
                     znode = zslInsert(zs->zsl,score,curobj);
                     incrRefCount(curobj); /* Re-inserted in skiplist. */
-                    dictGetEntryVal(de) = &znode->score; /* Update score ptr. */
+                    dictGetVal(de) = &znode->score; /* Update score ptr. */
 
                     signalModifiedKey(c->db,key);
                     server.dirty++;
@@ -987,7 +987,7 @@ void zremCommand(redisClient *c) {
                 deleted++;
 
                 /* Delete from the skiplist */
-                score = *(double*)dictGetEntryVal(de);
+                score = *(double*)dictGetVal(de);
                 redisAssertWithInfo(c,c->argv[j],zslDelete(zs->zsl,score,c->argv[j]));
 
                 /* Delete from the hash table */
@@ -1018,7 +1018,7 @@ void zremrangebyscoreCommand(redisClient *c) {
 
     /* Parse the range arguments. */
     if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
-        addReplyError(c,"min or max is not a double");
+        addReplyError(c,"min or max is not a float");
         return;
     }
 
@@ -1263,7 +1263,7 @@ int zuiNext(zsetopsrc *op, zsetopval *val) {
         } else if (op->encoding == REDIS_ENCODING_HT) {
             if (it->ht.de == NULL)
                 return 0;
-            val->ele = dictGetEntryKey(it->ht.de);
+            val->ele = dictGetKey(it->ht.de);
             val->score = 1.0;
 
             /* Move to next element. */
@@ -1397,7 +1397,7 @@ int zuiFind(zsetopsrc *op, zsetopval *val, double *score) {
         } else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
             dictEntry *de;
             if ((de = dictFind(it->sl.zs->dict,val->ele)) != NULL) {
-                *score = *(double*)dictGetEntryVal(de);
+                *score = *(double*)dictGetVal(de);
                 return 1;
             } else {
                 return 0;
@@ -1417,7 +1417,7 @@ int zuiCompareByCardinality(const void *s1, const void *s2) {
 #define REDIS_AGGR_SUM 1
 #define REDIS_AGGR_MIN 2
 #define REDIS_AGGR_MAX 3
-#define zunionInterDictValue(_e) (dictGetEntryVal(_e) == NULL ? 1.0 : *(double*)dictGetEntryVal(_e))
+#define zunionInterDictValue(_e) (dictGetVal(_e) == NULL ? 1.0 : *(double*)dictGetVal(_e))
 
 inline static void zunionInterAggregate(double *target, double val, int aggregate) {
     if (aggregate == REDIS_AGGR_SUM) {
@@ -1493,7 +1493,7 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
                 j++; remaining--;
                 for (i = 0; i < setnum; i++, j++, remaining--) {
                     if (getDoubleFromObjectOrReply(c,c->argv[j],&src[i].weight,
-                            "weight value is not a double") != REDIS_OK)
+                            "weight value is not a float") != REDIS_OK)
                     {
                         zfree(src);
                         return;
@@ -1777,7 +1777,7 @@ void genericZrangebyscoreCommand(redisClient *c, int reverse) {
     }
 
     if (zslParseRange(c->argv[minidx],c->argv[maxidx],&range) != REDIS_OK) {
-        addReplyError(c,"min or max is not a double");
+        addReplyError(c,"min or max is not a float");
         return;
     }
 
@@ -1959,7 +1959,7 @@ void zcountCommand(redisClient *c) {
 
     /* Parse the range arguments */
     if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
-        addReplyError(c,"min or max is not a double");
+        addReplyError(c,"min or max is not a float");
         return;
     }
 
@@ -2058,7 +2058,7 @@ void zscoreCommand(redisClient *c) {
         c->argv[2] = tryObjectEncoding(c->argv[2]);
         de = dictFind(zs->dict,c->argv[2]);
         if (de != NULL) {
-            score = *(double*)dictGetEntryVal(de);
+            score = *(double*)dictGetVal(de);
             addReplyDouble(c,score);
         } else {
             addReply(c,shared.nullbulk);
@@ -2114,7 +2114,7 @@ void zrankGenericCommand(redisClient *c, int reverse) {
         ele = c->argv[2] = tryObjectEncoding(c->argv[2]);
         de = dictFind(zs->dict,ele);
         if (de != NULL) {
-            score = *(double*)dictGetEntryVal(de);
+            score = *(double*)dictGetVal(de);
             rank = zslGetRank(zsl,score,ele);
             redisAssertWithInfo(c,ele,rank); /* Existing elements always have a rank. */
             if (reverse)
index 86645e95fe308aa98051447ce32dfadb449597be..b76c45a47d77acdfb3dd6dd4bf94c049d8ee556b 100644 (file)
@@ -120,7 +120,19 @@ start_server {tags {"basic"}} {
         r incrby novar 17179869184
     } {34359738368}
 
-    test {INCR fails against key with spaces (no integer encoded)} {
+    test {INCR fails against key with spaces (left)} {
+        r set novar "    11"
+        catch {r incr novar} err
+        format $err
+    } {ERR*}
+
+    test {INCR fails against key with spaces (right)} {
+        r set novar "11    "
+        catch {r incr novar} err
+        format $err
+    } {ERR*}
+
+    test {INCR fails against key with spaces (both)} {
         r set novar "    11    "
         catch {r incr novar} err
         format $err
@@ -138,6 +150,72 @@ start_server {tags {"basic"}} {
         r decrby novar 17179869185
     } {-1}
 
+    test {INCRBYFLOAT against non existing key} {
+        r del novar
+        list [r incrbyfloat novar 1] [r get novar] [r incrbyfloat novar 0.25] \
+             [r get novar]
+    } {1 1 1.25 1.25}
+
+    test {INCRBYFLOAT against key originally set with SET} {
+        r set novar 1.5
+        r incrbyfloat novar 1.5
+    } {3}
+
+    test {INCRBYFLOAT over 32bit value} {
+        r set novar 17179869184
+        r incrbyfloat novar 1.5
+    } {17179869185.5}
+
+    test {INCRBYFLOAT over 32bit value with over 32bit increment} {
+        r set novar 17179869184
+        r incrbyfloat novar 17179869184
+    } {34359738368}
+
+    test {INCRBYFLOAT fails against key with spaces (left)} {
+        set err {}
+        r set novar "    11"
+        catch {r incrbyfloat novar 1.0} err
+        format $err
+    } {ERR*valid*}
+
+    test {INCRBYFLOAT fails against key with spaces (right)} {
+        set err {}
+        r set novar "11    "
+        catch {r incrbyfloat novar 1.0} err
+        format $err
+    } {ERR*valid*}
+
+    test {INCRBYFLOAT fails against key with spaces (both)} {
+        set err {}
+        r set novar " 11 "
+        catch {r incrbyfloat novar 1.0} err
+        format $err
+    } {ERR*valid*}
+
+    test {INCRBYFLOAT fails against a key holding a list} {
+        r del mylist
+        set err {}
+        r rpush mylist 1
+        catch {r incrbyfloat mylist 1.0} err
+        r del mylist
+        format $err
+    } {ERR*kind*}
+
+    test {INCRBYFLOAT does not allow NaN or Infinity} {
+        r set foo 0
+        set err {}
+        catch {r incrbyfloat foo +inf} err
+        set err
+        # p.s. no way I can force NaN to test it from the API because
+        # there is no way to increment / decrement by infinity nor to
+        # perform divisions.
+    } {ERR*would produce*}
+
+    test {INCRBYFLOAT decrement} {
+        r set foo 1
+        r incrbyfloat foo -1.256
+    } {-0.256}
+
     test "SETNX target key missing" {
         r del novar
         assert_equal 1 [r setnx novar foobared]
index 415a0f53803b2061385db3ad844d8b1cd2cc5fda..b88ff821918a6c4acd324b8b6109fa7708046470 100644 (file)
@@ -5,7 +5,7 @@ start_server {tags {"expire"}} {
         set v2 [r ttl x]
         set v3 [r expire x 10]
         set v4 [r ttl x]
-        r expire x 4
+        r expire x 2
         list $v1 $v2 $v3 $v4
     } {1 [45] 1 10}
 
@@ -14,8 +14,8 @@ start_server {tags {"expire"}} {
     } {foobar}
 
     tags {"slow"} {
-        test {EXPIRE - After 6 seconds the key should no longer be here} {
-            after 6000
+        test {EXPIRE - After 2.1 seconds the key should no longer be here} {
+            after 2100
             list [r get x] [r exists x]
         } {{} 0}
     }
@@ -51,7 +51,7 @@ start_server {tags {"expire"}} {
 
     tags {"slow"} {
         test {SETEX - Wait for the key to expire} {
-            after 3000
+            after 1100
             r get y
         } {}
     }
@@ -71,4 +71,74 @@ start_server {tags {"expire"}} {
         r set x foo
         list [r persist foo] [r persist nokeyatall]
     } {0 0}
+
+    test {EXPIRE pricision is now the millisecond} {
+        # This test is very likely to do a false positive if the
+        # server is under pressure, so if it does not work give it a few more
+        # chances.
+        for {set j 0} {$j < 3} {incr j} {
+            r del x
+            r setex x 1 somevalue
+            after 997
+            set a [r get x]
+            after 1002
+            set b [r get x]
+            if {$a eq {somevalue} && $b eq {}} break
+        }
+        list $a $b
+    } {somevalue {}}
+
+    test {PEXPIRE/PSETEX/PEXPIREAT can set sub-second expires} {
+        # This test is very likely to do a false positive if the
+        # server is under pressure, so if it does not work give it a few more
+        # chances.
+        for {set j 0} {$j < 3} {incr j} {
+            r del x y z
+            r psetex x 100 somevalue
+            after 97
+            set a [r get x]
+            after 102
+            set b [r get x]
+
+            r set x somevalue
+            r pexpire x 100
+            after 97
+            set c [r get x]
+            after 102
+            set d [r get x]
+
+            r set x somevalue
+            r pexpireat x [expr ([clock seconds]*1000)+100]
+            after 97
+            set e [r get x]
+            after 102
+            set f [r get x]
+
+            if {$a eq {somevalue} && $b eq {} &&
+                $c eq {somevalue} && $d eq {} &&
+                $e eq {somevalue} && $f eq {}} break
+        }
+        list $a $b
+    } {somevalue {}}
+
+    test {PTTL returns millisecond time to live} {
+        r del x
+        r setex x 1 somevalue
+        set ttl [r pttl x]
+        assert {$ttl > 900 && $ttl <= 1000}
+    }
+
+    test {Redis should actively expire keys incrementally} {
+        r flushdb
+        r psetex key1 500 a
+        r psetex key2 500 a
+        r psetex key3 500 a
+        set size1 [r dbsize]
+        # Redis expires random keys ten times every second so we are
+        # fairly sure that all the three keys should be evicted after
+        # one second.
+        after 1000
+        set size2 [r dbsize]
+        list $size1 $size2
+    } {3 0}
 }
index 702c291f9a504e832b68ac50f3385e6afa9df021..bb65570a46685388cbdf41e02aefe23ccb084ea0 100644 (file)
@@ -107,7 +107,7 @@ start_server {tags {"other"}} {
         }
     }
 
-    test {EXPIRES after a reload (snapshot + append only file)} {
+    test {EXPIRES after a reload (snapshot + append only file rewrite)} {
         r flushdb
         r set x 10
         r expire x 1000
@@ -123,6 +123,39 @@ start_server {tags {"other"}} {
         list $e1 $e2
     } {1 1}
 
+    test {EXPIRES after AOF reload (without rewrite)} {
+        r flushdb
+        r config set appendonly yes
+        r set x somevalue
+        r expire x 1000
+        r setex y 2000 somevalue
+        r set z somevalue
+        r expireat z [expr {[clock seconds]+3000}]
+
+        # Milliseconds variants
+        r set px somevalue
+        r pexpire px 1000000
+        r psetex py 2000000 somevalue
+        r set pz somevalue
+        r pexpireat pz [expr {([clock seconds]+3000)*1000}]
+
+        # Reload and check
+        r debug loadaof
+        set ttl [r ttl x]
+        assert {$ttl > 900 && $ttl <= 1000}
+        set ttl [r ttl y]
+        assert {$ttl > 1900 && $ttl <= 2000}
+        set ttl [r ttl z]
+        assert {$ttl > 2900 && $ttl <= 3000}
+        set ttl [r ttl px]
+        assert {$ttl > 900 && $ttl <= 1000}
+        set ttl [r ttl py]
+        assert {$ttl > 1900 && $ttl <= 2000}
+        set ttl [r ttl pz]
+        assert {$ttl > 2900 && $ttl <= 3000}
+        r config set appendonly no
+    }
+
     tags {protocol} {
         test {PIPELINING stresser (also a regression for the old epoll bug)} {
             set fd2 [socket $::host $::port]
index 718bc04ad3a98988cc09c4e91c59965f7baba362..863c51a482191dd9a4c2a6d25f425909f63557ab 100644 (file)
@@ -298,9 +298,9 @@ start_server {tags {"hash"}} {
         list [r hincrby smallhash tmp 17179869184] [r hincrby bighash tmp 17179869184]
     } {34359738368 34359738368}
 
-    test {HINCRBY fails against hash value with spaces} {
-        r hset smallhash str "    11    "
-        r hset bighash str "    11    "
+    test {HINCRBY fails against hash value with spaces (left)} {
+        r hset smallhash str " 11"
+        r hset bighash str " 11"
         catch {r hincrby smallhash str 1} smallerr
         catch {r hincrby smallhash str 1} bigerr
         set rv {}
@@ -308,6 +308,78 @@ start_server {tags {"hash"}} {
         lappend rv [string match "ERR*not an integer*" $bigerr]
     } {1 1}
 
+    test {HINCRBY fails against hash value with spaces (right)} {
+        r hset smallhash str "11 "
+        r hset bighash str "11 "
+        catch {r hincrby smallhash str 1} smallerr
+        catch {r hincrby smallhash str 1} bigerr
+        set rv {}
+        lappend rv [string match "ERR*not an integer*" $smallerr]
+        lappend rv [string match "ERR*not an integer*" $bigerr]
+    } {1 1}
+
+    test {HINCRBYFLOAT against non existing database key} {
+        r del htest
+        list [r hincrbyfloat htest foo 2.5]
+    } {2.5}
+
+    test {HINCRBYFLOAT against non existing hash key} {
+        set rv {}
+        r hdel smallhash tmp
+        r hdel bighash tmp
+        lappend rv [r hincrbyfloat smallhash tmp 2.5]
+        lappend rv [r hget smallhash tmp]
+        lappend rv [r hincrbyfloat bighash tmp 2.5]
+        lappend rv [r hget bighash tmp]
+    } {2.5 2.5 2.5 2.5}
+
+    test {HINCRBYFLOAT against hash key created by hincrby itself} {
+        set rv {}
+        lappend rv [r hincrbyfloat smallhash tmp 3.1]
+        lappend rv [r hget smallhash tmp]
+        lappend rv [r hincrbyfloat bighash tmp 3.1]
+        lappend rv [r hget bighash tmp]
+    } {5.6 5.6 5.6 5.6}
+
+    test {HINCRBYFLOAT against hash key originally set with HSET} {
+        r hset smallhash tmp 100
+        r hset bighash tmp 100
+        list [r hincrbyfloat smallhash tmp 2.5] [r hincrbyfloat bighash tmp 2.5]
+    } {102.5 102.5}
+
+    test {HINCRBYFLOAT over 32bit value} {
+        r hset smallhash tmp 17179869184
+        r hset bighash tmp 17179869184
+        list [r hincrbyfloat smallhash tmp 1] [r hincrbyfloat bighash tmp 1]
+    } {17179869185 17179869185}
+
+    test {HINCRBYFLOAT over 32bit value with over 32bit increment} {
+        r hset smallhash tmp 17179869184
+        r hset bighash tmp 17179869184
+        list [r hincrbyfloat smallhash tmp 17179869184] \
+             [r hincrbyfloat bighash tmp 17179869184]
+    } {34359738368 34359738368}
+
+    test {HINCRBYFLOAT fails against hash value with spaces (left)} {
+        r hset smallhash str " 11"
+        r hset bighash str " 11"
+        catch {r hincrbyfloat smallhash str 1} smallerr
+        catch {r hincrbyfloat smallhash str 1} bigerr
+        set rv {}
+        lappend rv [string match "ERR*not*float*" $smallerr]
+        lappend rv [string match "ERR*not*float*" $bigerr]
+    } {1 1}
+
+    test {HINCRBYFLOAT fails against hash value with spaces (right)} {
+        r hset smallhash str "11 "
+        r hset bighash str "11 "
+        catch {r hincrbyfloat smallhash str 1} smallerr
+        catch {r hincrbyfloat smallhash str 1} bigerr
+        set rv {}
+        lappend rv [string match "ERR*not*float*" $smallerr]
+        lappend rv [string match "ERR*not*float*" $bigerr]
+    } {1 1}
+
     test {Hash zipmap regression test for large keys} {
         r hset hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk a
         r hset hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk b
index 41f5f588f557f8a9aa9ee4468cf911afcaa2285b..aa43f3bf91d85b9180102efee121063d3fda0431 100644 (file)
@@ -36,11 +36,11 @@ start_server {tags {"zset"}} {
         }
 
         test "ZSET element can't be set to NaN with ZADD - $encoding" {
-            assert_error "*not a double*" {r zadd myzset nan abc}
+            assert_error "*not*float*" {r zadd myzset nan abc}
         }
 
         test "ZSET element can't be set to NaN with ZINCRBY" {
-            assert_error "*not a double*" {r zadd myzset nan abc}
+            assert_error "*not*float*" {r zadd myzset nan abc}
         }
 
         test "ZINCRBY calls leading to NaN result in error" {
@@ -60,7 +60,7 @@ start_server {tags {"zset"}} {
         test {ZADD - Variadic version does not add nothing on single parsing err} {
             r del myzset
             catch {r zadd myzset 10 a 20 b 30.badscore c} e
-            assert_match {*ERR*not*double*} $e
+            assert_match {*ERR*not*float*} $e
             r exists myzset
         } {0}
 
@@ -291,9 +291,9 @@ start_server {tags {"zset"}} {
         }
 
         test "ZRANGEBYSCORE with non-value min or max" {
-            assert_error "*not a double*" {r zrangebyscore fooz str 1}
-            assert_error "*not a double*" {r zrangebyscore fooz 1 str}
-            assert_error "*not a double*" {r zrangebyscore fooz 1 NaN}
+            assert_error "*not*float*" {r zrangebyscore fooz str 1}
+            assert_error "*not*float*" {r zrangebyscore fooz 1 str}
+            assert_error "*not*float*" {r zrangebyscore fooz 1 NaN}
         }
 
         test "ZREMRANGEBYSCORE basics" {
@@ -353,9 +353,9 @@ start_server {tags {"zset"}} {
         }
 
         test "ZREMRANGEBYSCORE with non-value min or max" {
-            assert_error "*not a double*" {r zremrangebyscore fooz str 1}
-            assert_error "*not a double*" {r zremrangebyscore fooz 1 str}
-            assert_error "*not a double*" {r zremrangebyscore fooz 1 NaN}
+            assert_error "*not*float*" {r zremrangebyscore fooz str 1}
+            assert_error "*not*float*" {r zremrangebyscore fooz 1 str}
+            assert_error "*not*float*" {r zremrangebyscore fooz 1 NaN}
         }
 
         test "ZREMRANGEBYRANK basics" {
@@ -501,7 +501,7 @@ start_server {tags {"zset"}} {
 
                 r zadd zsetinf1 1.0 key
                 r zadd zsetinf2 1.0 key
-                assert_error "*weight value is not a double*" {
+                assert_error "*weight*not*float*" {
                     r $cmd zsetinf3 2 zsetinf1 zsetinf2 weights nan nan
                 }
             }