#include "redis.h"
#include <math.h>
+#include <ctype.h>
robj *createObject(int type, void *ptr) {
robj *o = zmalloc(sizeof(*o));
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));
} 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;
}
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;
}
} 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;
redisPanic("Unknown string encoding");
}
}
-
if (target) *target = value;
return REDIS_OK;
}
}
return REDIS_ERR;
}
-
*target = value;
return REDIS_OK;
}
}
return REDIS_ERR;
}
-
*target = value;
return REDIS_OK;
}
# divisibility. Like we have 3 nodes and need to get 10 slots, we take
# 4 from the first, and 3 from the rest. So the biggest is always the first.
sources = sources.sort{|a,b| b.slots.length <=> a.slots.length}
- source_tot_slots = sources.inject {|a,b| a.slots.length+b.slots.length}
+ source_tot_slots = sources.inject(0) {|sum,source| sum+source.slots.length}
sources.each_with_index{|s,i|
# Every node will provide a number of slots proportional to the
# slots it has assigned.
{"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},
{"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},
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);
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);
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);
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);
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;
#include "redis.h"
+#include <math.h> /* isnan(), isinf() */
/*-----------------------------------------------------------------------------
* String Commands
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;
/* 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;
}
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;
}
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;
}
/* 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;
}
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
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]
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 {}
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
}
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" {
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}
}
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" {
}
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" {
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
}
}