]> git.saurik.com Git - redis.git/commitdiff
Merge branch 'unstable' into incrbyfloat
authorantirez <antirez@gmail.com>
Mon, 14 Nov 2011 14:59:56 +0000 (15:59 +0100)
committerantirez <antirez@gmail.com>
Mon, 14 Nov 2011 14:59:56 +0000 (15:59 +0100)
src/object.c
src/redis.c
src/redis.h
src/t_string.c
src/t_zset.c
tests/unit/basic.tcl
tests/unit/type/zset.tcl

index fd7786ed6e463a8ec6b1e225857b166a4f307244..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;
 }
index 23b7b280d8e8553d0806926b75a68c352e9e429b..a6d1b577203048b860549b9275854455da8ab9c3 100644 (file)
@@ -165,6 +165,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},
index 79ff3a6a0583eb5e8a941327afd497e3c8cfc634..3d1e81aa9845b5e8d8365d0d64c7a1b27106ecf7 100644 (file)
@@ -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);
@@ -1004,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);
index ce6233a98d3e8df653bc990f098eea0572d7f489..2bd1646e305627ecab073893553a40f48444c2bf 100644 (file)
@@ -1,4 +1,5 @@
 #include "redis.h"
+#include <math.h> /* isnan(), isinf() */
 
 /*-----------------------------------------------------------------------------
  * String Commands
@@ -382,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 43fdd48db237fbbdf6d4e463ad9a5fc6e2b52b1f..ccf9962a19f206c521f8b966d79aaed9034648ba 100644 (file)
@@ -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;
     }
 
@@ -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;
     }
 
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 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
                 }
             }