diff --git a/src/commands.def b/src/commands.def index 9b5692aa3..3bafaee3d 100644 --- a/src/commands.def +++ b/src/commands.def @@ -11859,13 +11859,6 @@ struct COMMAND_ARG INCREX_increment_Subargs[] = { {MAKE_ARG("integer",ARG_TYPE_INTEGER,-1,"BYINT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; -/* INCREX overflow_block argument table */ -struct COMMAND_ARG INCREX_overflow_block_Subargs[] = { -{MAKE_ARG("fail",ARG_TYPE_PURE_TOKEN,-1,"FAIL",NULL,NULL,CMD_ARG_NONE,0,NULL)}, -{MAKE_ARG("sat",ARG_TYPE_PURE_TOKEN,-1,"SAT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, -{MAKE_ARG("reject",ARG_TYPE_PURE_TOKEN,-1,"REJECT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, -}; - /* INCREX expiration argument table */ struct COMMAND_ARG INCREX_expiration_Subargs[] = { {MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"EX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, @@ -11879,7 +11872,7 @@ struct COMMAND_ARG INCREX_expiration_Subargs[] = { struct COMMAND_ARG INCREX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("increment",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=INCREX_increment_Subargs}, -{MAKE_ARG("overflow-block",ARG_TYPE_ONEOF,-1,"OVERFLOW","Out-of-bounds policy; defaults to FAIL. Missing LBOUND/UBOUND default to the type limits (LLONG_MIN/LLONG_MAX for BYINT, -LDBL_MAX/LDBL_MAX for BYFLOAT).",NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=INCREX_overflow_block_Subargs}, +{MAKE_ARG("saturate",ARG_TYPE_PURE_TOKEN,-1,"SATURATE","Saturate the result to LBOUND/UBOUND (or the type limits when no explicit bound is given) when out of bounds. Without this option, out-of-bounds operations are rejected and reply [current_value, 0].",NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("lowerbound",ARG_TYPE_STRING,-1,"LBOUND","Integer when used with BYINT, floating-point when used with BYFLOAT.",NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("upperbound",ARG_TYPE_STRING,-1,"UBOUND","Integer when used with BYINT, floating-point when used with BYFLOAT.",NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=INCREX_expiration_Subargs}, diff --git a/src/commands/increx.json b/src/commands/increx.json index 964822b49..5bf2232b9 100644 --- a/src/commands/increx.json +++ b/src/commands/increx.json @@ -74,28 +74,11 @@ ] }, { - "name": "overflow-block", - "token": "OVERFLOW", - "type": "oneof", + "name": "saturate", + "token": "SATURATE", + "type": "pure-token", "optional": true, - "summary": "Out-of-bounds policy; defaults to FAIL. Missing LBOUND/UBOUND default to the type limits (LLONG_MIN/LLONG_MAX for BYINT, -LDBL_MAX/LDBL_MAX for BYFLOAT).", - "arguments": [ - { - "name": "fail", - "type": "pure-token", - "token": "FAIL" - }, - { - "name": "sat", - "type": "pure-token", - "token": "SAT" - }, - { - "name": "reject", - "type": "pure-token", - "token": "REJECT" - } - ] + "summary": "Saturate the result to LBOUND/UBOUND (or the type limits when no explicit bound is given) when out of bounds. Without this option, out-of-bounds operations are rejected and reply [current_value, 0]." }, { "name": "lowerbound", diff --git a/src/t_string.c b/src/t_string.c index 4f5019e4e..d09b8ab24 100644 --- a/src/t_string.c +++ b/src/t_string.c @@ -1003,15 +1003,13 @@ void incrbyfloatCommand(client *c) { #define OBJ_INCREX_BYINT (1<<1) /* Set if integer increment is given */ #define OBJ_INCREX_LBOUND (1<<2) /* Set if lower bound of increx result is given */ #define OBJ_INCREX_UBOUND (1<<3) /* Set if upper bound of increx result is given */ -#define OBJ_INCREX_OVERFLOW_FAIL (1<<4) /* Return an error when the result is out of bounds (default) */ -#define OBJ_INCREX_OVERFLOW_SAT (1<<5) /* Saturate the result to LBOUND/UBOUND/type limits instead of failing */ -#define OBJ_INCREX_OVERFLOW_REJECT (1<<6) /* Leave the key unchanged and reply [current_value, 0] when the result is out of bounds */ -#define OBJ_INCREX_ENX (1<<7) /* Set expiration only when the key has no expiry */ -#define OBJ_INCREX_PERSIST (1<<8) /* Set if we need to remove the ttl */ -#define OBJ_INCREX_EX (1<<9) /* Set if time in seconds is given */ -#define OBJ_INCREX_PX (1<<10) /* Set if time in ms is given */ -#define OBJ_INCREX_EXAT (1<<11) /* Set if timestamp in second is given */ -#define OBJ_INCREX_PXAT (1<<12) /* Set if timestamp in ms is given */ +#define OBJ_INCREX_SATURATE (1<<4) /* Saturate the result to LBOUND/UBOUND/type limits when out of bounds. */ +#define OBJ_INCREX_ENX (1<<5) /* Set expiration only when the key has no expiry */ +#define OBJ_INCREX_PERSIST (1<<6) /* Set if we need to remove the ttl */ +#define OBJ_INCREX_EX (1<<7) /* Set if time in seconds is given */ +#define OBJ_INCREX_PX (1<<8) /* Set if time in ms is given */ +#define OBJ_INCREX_EXAT (1<<9) /* Set if timestamp in second is given */ +#define OBJ_INCREX_PXAT (1<<10) /* Set if timestamp in ms is given */ /* INCREX argument structure */ typedef struct { @@ -1076,20 +1074,8 @@ static int parseIncrExArgumentsOrReply(client *c, int start_pos, incrExArgs *arg args->flags |= OBJ_INCREX_UBOUND; upper_bound = next; j++; - } else if (!strcasecmp(opt, "OVERFLOW") && next && - !(args->flags & (OBJ_INCREX_OVERFLOW_FAIL|OBJ_INCREX_OVERFLOW_SAT|OBJ_INCREX_OVERFLOW_REJECT))) - { - if (!strcasecmp(next->ptr, "FAIL")) { - args->flags |= OBJ_INCREX_OVERFLOW_FAIL; - } else if (!strcasecmp(next->ptr, "SAT")) { - args->flags |= OBJ_INCREX_OVERFLOW_SAT; - } else if (!strcasecmp(next->ptr, "REJECT")) { - args->flags |= OBJ_INCREX_OVERFLOW_REJECT; - } else { - addReplyError(c, "OVERFLOW policy must be FAIL, SAT or REJECT"); - return C_ERR; - } - j++; + } else if (!strcasecmp(opt, "SATURATE") && !(args->flags & OBJ_INCREX_SATURATE)) { + args->flags |= OBJ_INCREX_SATURATE; } else if (!strcasecmp(opt, "ENX") && !(args->flags & (OBJ_INCREX_ENX|OBJ_INCREX_PERSIST))) { args->flags |= OBJ_INCREX_ENX; } else if (!strcasecmp(opt, "PERSIST") && !(args->flags & (expire_flags|OBJ_INCREX_ENX))) { @@ -1167,7 +1153,7 @@ static int parseIncrExArgumentsOrReply(client *c, int start_pos, incrExArgs *arg /* * INCREX [BYFLOAT increment | BYINT increment] [LBOUND lowerbound] - * [UBOUND upperbound] [OVERFLOW ] + * [UBOUND upperbound] [SATURATE] * [EX seconds | PX milliseconds | EXAT seconds-timestamp | PXAT milliseconds-timestamp | PERSIST] [ENX] * * Increments the numeric value of a key and optionally updates its expiration time. @@ -1181,15 +1167,13 @@ static int parseIncrExArgumentsOrReply(client *c, int start_pos, incrExArgs *arg * Range options: * LBOUND and UBOUND optionally restrict the result to a range. The behavior * when the result would land outside that range (or, with no explicit bound, - * would overflow the type limits) is controlled by OVERFLOW: - * - OVERFLOW FAIL (default): the operation is rejected with an error, - * matching the semantics of INCRBY/INCRBYFLOAT. - * - OVERFLOW SAT: the result is silently capped at UBOUND / floored at LBOUND - * (or saturated to the type limits when no explicit bound is - * given) instead of producing an error. - * - OVERFLOW REJECT: the operation is silently skipped (the key value and TTL - * are left unchanged) and the reply is the current value with - * an applied increment of 0, instead of producing an error. + * would overflow the type limits) is controlled by SATURATE: + * - Default: the operation is rejected (the key value and TTL are left + * unchanged) and the reply is the current value with an applied + * increment of 0. + * - SATURATE: the result is capped at UBOUND / floored at LBOUND (or + * saturated to the type limits when no explicit bound is given) + * instead of being rejected. * * Expiration options: * At most one of the following may be specified: @@ -1203,7 +1187,6 @@ static int parseIncrExArgumentsOrReply(client *c, int start_pos, incrExArgs *arg * ENX restricts expiration updates to keys that currently have no TTL. * * Reply: - * - (Simple Error) if any parameter is invalid, or if BYFLOAT produces NaN or Infinity. * - (Array) of two Bulk Strings on success: * 1. The new value of the key after the increment. * 2. The actual increment applied. @@ -1225,9 +1208,9 @@ void increxCommand(client *c) { if (checkType(c, o, OBJ_STRING)) return; int byfloat = args.flags & OBJ_INCREX_BYFLOAT; - /* FAIL is the default when no OVERFLOW policy is specified. */ - int fail_mode = !(args.flags & (OBJ_INCREX_OVERFLOW_SAT | OBJ_INCREX_OVERFLOW_REJECT)); - int reject_mode = args.flags & OBJ_INCREX_OVERFLOW_REJECT; + /* By default the operation is rejected on out-of-bounds: + * leave the key unchanged and reply [current_value, 0]. */ + int sat_mode = args.flags & OBJ_INCREX_SATURATE; if (byfloat) { long double lb = args.lb_ld, ub = args.ub_ld; if (getLongDoubleFromObjectOrReply(c, o, &value_ld, NULL) != C_OK) @@ -1244,24 +1227,17 @@ void increxCommand(client *c) { value_ld += args.incr_ld; int overflow = isinf(value_ld); if (overflow || value_ld > ub || value_ld < lb) { - /* FAIL: return an error. */ - if (fail_mode) { - addReplyError(c, overflow ? "increment would produce Infinity" : - "value is out of bounds"); - return; - } - /* Result is infinite or out of [LBOUND, UBOUND]: - * FAIL: error; SAT: clamp to +/-LDBL_MAX or the breached bound; - * REJECT: leave key untouched, reply [current_value, 0]. */ - if (reject_mode) { + * default: reject (leave key untouched, reply [current_value, 0]); + * SATURATE: clamp to +/-LDBL_MAX or the breached bound. */ + if (!sat_mode) { addReplyArrayLen(c, 2); addReplyHumanLongDouble(c, oldvalue_ld); addReplyHumanLongDouble(c, 0); return; } - /* SAT: clamp the result. */ + /* SATURATE: clamp the result. */ if (overflow) value_ld = (args.incr_ld >= 0) ? ub : lb; else @@ -1271,7 +1247,7 @@ void increxCommand(client *c) { long double delta = value_ld - oldvalue_ld; if (isinf(delta)) { /* The applied delta cannot be represented as a valid long double. This can - * only happen under OVERFLOW SAT when the saturated result and the + * only happen under SATURATE when the saturated result and the * prior value sit at opposite ends of the type range. */ addReplyError(c, "applied increment would be Infinity"); return; @@ -1288,24 +1264,17 @@ void increxCommand(client *c) { oldvalue_ll = value_ll; int overflow = add_overflow_ll(oldvalue_ll, args.incr_ll, &value_ll); if (overflow || value_ll > ub || value_ll < lb) { - /* FAIL: return an error. */ - if (fail_mode) { - addReplyError(c, overflow ? "increment or decrement would overflow" : - "value is out of bounds"); - return; - } - /* Result overflows long long or is out of [LBOUND, UBOUND]: - * FAIL: error; SAT: clamp to LLONG_MAX/LLONG_MIN or the breached bound; - * REJECT: leave key untouched, reply [current_value, 0]. */ - if (reject_mode) { + * default: reject (leave key untouched, reply [current_value, 0]); + * SATURATE: clamp to LLONG_MAX/LLONG_MIN or the breached bound. */ + if (!sat_mode) { addReplyArrayLen(c, 2); addReplyLongLong(c, oldvalue_ll); addReplyLongLong(c, 0); return; } - /* SAT: clamp the result. */ + /* SATURATE: clamp the result. */ if (overflow) value_ll = (args.incr_ll >= 0) ? ub : lb; else @@ -1315,7 +1284,7 @@ void increxCommand(client *c) { long long delta = 0; if (sub_overflow_ll(value_ll, oldvalue_ll, &delta)) { /* The applied delta cannot be represented as a long long. This can - * only happen under OVERFLOW SAT when the saturated result and the + * only happen under SATURATE when the saturated result and the * prior value sit at opposite ends of the type range. */ addReplyError(c, "applied increment would overflow"); return; diff --git a/tests/unit/type/increx.tcl b/tests/unit/type/increx.tcl index 1797cfbb5..26d2e641c 100644 --- a/tests/unit/type/increx.tcl +++ b/tests/unit/type/increx.tcl @@ -26,13 +26,13 @@ start_server {tags {"increx"}} { test {INCREX - BYINT saturates to UBOUND} { r set mykey 50 - assert_equal [r increx mykey BYINT 100 UBOUND 80 OVERFLOW SAT] {80 30} + assert_equal [r increx mykey BYINT 100 UBOUND 80 SATURATE] {80 30} assert_equal [r get mykey] 80 } test {INCREX - BYINT saturates to LBOUND} { r set mykey 10 - assert_equal [r increx mykey BYINT -100 LBOUND 0 OVERFLOW SAT] {0 -10} + assert_equal [r increx mykey BYINT -100 LBOUND 0 SATURATE] {0 -10} assert_equal [r get mykey] 0 } @@ -41,40 +41,40 @@ start_server {tags {"increx"}} { assert_equal [r increx mykey BYINT 1 LBOUND 0 UBOUND 10] {6 1} } - test {INCREX - BYINT positive overflow with OVERFLOW SAT saturates to LLONG_MAX} { + test {INCREX - BYINT positive overflow with SATURATE saturates to LLONG_MAX} { # LLONG_MAX = 9223372036854775807 r set mykey 9223372036854775800 - assert_equal [r increx mykey BYINT 9223372036854775800 OVERFLOW SAT] {9223372036854775807 7} + assert_equal [r increx mykey BYINT 9223372036854775800 SATURATE] {9223372036854775807 7} assert_equal [r get mykey] 9223372036854775807 } - test {INCREX - BYINT positive overflow with OVERFLOW SAT and UBOUND saturates to UBOUND} { + test {INCREX - BYINT positive overflow with SATURATE and UBOUND saturates to UBOUND} { # LLONG_MAX = 9223372036854775807 r set mykey 9223372036854775800 - assert_equal [r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807 OVERFLOW SAT] {9223372036854775807 7} + assert_equal [r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807 SATURATE] {9223372036854775807 7} assert_equal [r get mykey] 9223372036854775807 } - test {INCREX - BYINT negative overflow with OVERFLOW SAT saturates to LLONG_MIN} { + test {INCREX - BYINT negative overflow with SATURATE saturates to LLONG_MIN} { # LLONG_MIN = -9223372036854775808 r set mykey -9223372036854775800 - assert_equal [r increx mykey BYINT -9223372036854775800 OVERFLOW SAT] {-9223372036854775808 -8} + assert_equal [r increx mykey BYINT -9223372036854775800 SATURATE] {-9223372036854775808 -8} assert_equal [r get mykey] -9223372036854775808 } - test {INCREX - BYINT negative overflow with OVERFLOW SAT and LBOUND saturates to LBOUND} { + test {INCREX - BYINT negative overflow with SATURATE and LBOUND saturates to LBOUND} { # LLONG_MIN = -9223372036854775808 r set mykey -9223372036854775800 - assert_equal [r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808 OVERFLOW SAT] {-9223372036854775808 -8} + assert_equal [r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808 SATURATE] {-9223372036854775808 -8} assert_equal [r get mykey] -9223372036854775808 } - test {INCREX - BYINT SAT rejects when applied delta would overflow long long} { + test {INCREX - BYINT SATURATE rejects when applied delta would overflow long long} { # The saturated result lands at LLONG_MIN while the prior value is positive, # so the reported delta would not fit in a long long. r set mykey 9223372036854775800 assert_error "*applied increment would overflow*" { - r increx mykey BYINT 1 OVERFLOW SAT UBOUND -9223372036854775808 + r increx mykey BYINT 1 SATURATE UBOUND -9223372036854775808 } } @@ -102,9 +102,9 @@ start_server {tags {"increx"}} { test {INCREX - BYFLOAT saturates to UBOUND/LBOUND} { r set mykey 10 - assert_equal [lmap v [r increx mykey BYFLOAT 100 UBOUND 42.5 OVERFLOW SAT] {roundFloat $v}] {42.5 32.5} + assert_equal [lmap v [r increx mykey BYFLOAT 100 UBOUND 42.5 SATURATE] {roundFloat $v}] {42.5 32.5} r set mykey 0 - assert_equal [lmap v [r increx mykey BYFLOAT -100 LBOUND -5.5 OVERFLOW SAT] {roundFloat $v}] {-5.5 -5.5} + assert_equal [lmap v [r increx mykey BYFLOAT -100 LBOUND -5.5 SATURATE] {roundFloat $v}] {-5.5 -5.5} } # On some platforms strtold("+inf") with valgrind returns a non-inf result @@ -127,34 +127,35 @@ start_server {tags {"increx"}} { # --------------------------------------------------------------------- # Non-existent key whose default 0 is already outside [LBOUND, UBOUND] - # and the increment cannot bring it back into range -> refuse to create. + # and the increment cannot bring it back into range -> default policy + # leaves the key absent and replies [0, 0]. # --------------------------------------------------------------------- test {INCREX - BYINT/BYFLOAT on non-existent key refuses to create when result stays below LBOUND} { r del mykey - assert_error "*value is out of bounds*" {r increx mykey BYINT 5 LBOUND 10} + assert_equal [r increx mykey BYINT 5 LBOUND 10] {0 0} assert_equal [r exists mykey] 0 - assert_error "*value is out of bounds*" {r increx mykey BYFLOAT -0.5 UBOUND -1.5} + assert_equal [lmap v [r increx mykey BYFLOAT -0.5 UBOUND -1.5] {roundFloat $v}] {0 0} assert_equal [r exists mykey] 0 } # --------------------------------------------------------------------- # Existing key whose value is already outside [LBOUND, UBOUND] is treated - # the same as an in-range value pushed outside by the increment: OVERFLOW - # FAIL errors out and OVERFLOW SAT saturates the result. + # the same as an in-range value pushed outside by the increment: the + # default policy leaves the key alone and SATURATE saturates. # --------------------------------------------------------------------- test {INCREX - BYFLOAT existing value already outside bounds} { - # Above UBOUND, same-side increment: FAIL errors, SAT saturates to UBOUND. + # Above UBOUND, same-side increment: default leaves value unchanged, SATURATE saturates to UBOUND. r set mykey 50.5 - assert_error "*out of bounds*" {r increx mykey BYFLOAT 5.5 UBOUND 30} + assert_equal [lmap v [r increx mykey BYFLOAT 5.5 UBOUND 30] {roundFloat $v}] {50.5 0} assert_equal [roundFloat [r get mykey]] 50.5 - assert_equal [lmap v [r increx mykey BYFLOAT 5.5 UBOUND 30 OVERFLOW SAT] {roundFloat $v}] {30 -20.5} + assert_equal [lmap v [r increx mykey BYFLOAT 5.5 UBOUND 30 SATURATE] {roundFloat $v}] {30 -20.5} - # Below LBOUND, same-side decrement: SAT saturates to LBOUND. + # Below LBOUND, same-side decrement: SATURATE saturates to LBOUND. r set mykey -50.5 - assert_equal [lmap v [r increx mykey BYFLOAT -5.5 LBOUND -30 OVERFLOW SAT] {roundFloat $v}] {-30 20.5} + assert_equal [lmap v [r increx mykey BYFLOAT -5.5 LBOUND -30 SATURATE] {roundFloat $v}] {-30 20.5} # Increment that brings the out-of-range value back inside is applied normally. r set mykey 50 @@ -162,15 +163,15 @@ start_server {tags {"increx"}} { } test {INCREX - BYINT existing value already outside bounds} { - # Above UBOUND, same-side increment: FAIL errors, SAT saturates to UBOUND. + # Above UBOUND, same-side increment: default leaves value unchanged, SATURATE saturates to UBOUND. r set mykey 50 - assert_error "*out of bounds*" {r increx mykey BYINT 5 UBOUND 30} + assert_equal [r increx mykey BYINT 5 UBOUND 30] {50 0} assert_equal [r get mykey] 50 - assert_equal [r increx mykey BYINT 5 UBOUND 30 OVERFLOW SAT] {30 -20} + assert_equal [r increx mykey BYINT 5 UBOUND 30 SATURATE] {30 -20} - # Below LBOUND, same-side decrement: SAT saturates to LBOUND. + # Below LBOUND, same-side decrement: SATURATE saturates to LBOUND. r set mykey -50 - assert_equal [r increx mykey BYINT -5 LBOUND -30 OVERFLOW SAT] {-30 20} + assert_equal [r increx mykey BYINT -5 LBOUND -30 SATURATE] {-30 20} # Increment that brings the out-of-range value back inside is applied normally. r set mykey 50 @@ -178,37 +179,34 @@ start_server {tags {"increx"}} { } # --------------------------------------------------------------------- - # Out-of-range behavior: OVERFLOW FAIL (the default) errors out (like - # INCRBY); OVERFLOW SAT saturates the result silently. + # Out-of-range behavior: by default the operation is rejected + # (reply is [current_value, 0]); SATURATE saturates the result. # --------------------------------------------------------------------- - test {INCREX - BYINT OVERFLOW FAIL rejects increment exceeding UBOUND; OVERFLOW SAT saturates it} { + test {INCREX - BYINT default rejects increment exceeding UBOUND; SATURATE saturates it} { r set mykey 10 - assert_error "*out of bounds*" {r increx mykey BYINT 10 UBOUND 15} - # Value is unchanged after the error + assert_equal [r increx mykey BYINT 10 UBOUND 15] {10 0} + # Value is unchanged assert_equal [r get mykey] 10 - # OVERFLOW FAIL is the explicit form of the default - assert_error "*out of bounds*" {r increx mykey BYINT 10 UBOUND 15 OVERFLOW FAIL} - assert_equal [r get mykey] 10 - # OVERFLOW SAT saturates the result at UBOUND - assert_equal [r increx mykey BYINT 10 UBOUND 15 OVERFLOW SAT] {15 5} + # SATURATE saturates the result at UBOUND + assert_equal [r increx mykey BYINT 10 UBOUND 15 SATURATE] {15 5} assert_equal [r get mykey] 15 } - test {INCREX - BYINT OVERFLOW FAIL rejects decrement falling below LBOUND; OVERFLOW SAT floors it} { + test {INCREX - BYINT default rejects decrement falling below LBOUND; SATURATE floors it} { r set mykey 10 - assert_error "*out of bounds*" {r increx mykey BYINT -10 LBOUND 5} + assert_equal [r increx mykey BYINT -10 LBOUND 5] {10 0} assert_equal [r get mykey] 10 - # OVERFLOW SAT floors the result at LBOUND - assert_equal [r increx mykey BYINT -10 LBOUND 5 OVERFLOW SAT] {5 -5} + # SATURATE floors the result at LBOUND + assert_equal [r increx mykey BYINT -10 LBOUND 5 SATURATE] {5 -5} assert_equal [r get mykey] 5 } - test {INCREX - BYINT within bounds is unaffected by OVERFLOW policy} { + test {INCREX - BYINT within bounds is unaffected by SATURATE} { r set mykey 10 assert_equal [r increx mykey BYINT 3 UBOUND 20] {13 3} - assert_equal [r increx mykey BYINT -3 LBOUND 0 OVERFLOW SAT] {10 -3} - assert_equal [r increx mykey BYINT 1 UBOUND 20 OVERFLOW FAIL] {11 1} + assert_equal [r increx mykey BYINT -3 LBOUND 0 SATURATE] {10 -3} + assert_equal [r increx mykey BYINT 1 UBOUND 20] {11 1} } test {INCREX - BYINT with both LBOUND and UBOUND} { @@ -216,13 +214,13 @@ start_server {tags {"increx"}} { # Within range -> allowed assert_equal [r increx mykey BYINT 2 LBOUND 0 UBOUND 10] {7 2} # Exceeds UBOUND -> rejected, value unchanged - assert_error "*out of bounds*" {r increx mykey BYINT 10 LBOUND 0 UBOUND 10} + assert_equal [r increx mykey BYINT 10 LBOUND 0 UBOUND 10] {7 0} # Falls below LBOUND -> rejected, value unchanged - assert_error "*out of bounds*" {r increx mykey BYINT -20 LBOUND 0 UBOUND 10} + assert_equal [r increx mykey BYINT -20 LBOUND 0 UBOUND 10] {7 0} assert_equal [r get mykey] 7 - # OVERFLOW SAT saturates at the bounds - assert_equal [r increx mykey BYINT 10 LBOUND 0 UBOUND 10 OVERFLOW SAT] {10 3} - assert_equal [r increx mykey BYINT -20 LBOUND 0 UBOUND 10 OVERFLOW SAT] {0 -10} + # SATURATE saturates at the bounds + assert_equal [r increx mykey BYINT 10 LBOUND 0 UBOUND 10 SATURATE] {10 3} + assert_equal [r increx mykey BYINT -20 LBOUND 0 UBOUND 10 SATURATE] {0 -10} } test {INCREX - BYINT at exact bound value is accepted} { @@ -233,26 +231,26 @@ start_server {tags {"increx"}} { assert_equal [r increx mykey BYINT -10 LBOUND 0] {0 -10} } - test {INCREX - BYFLOAT OVERFLOW FAIL rejects increment exceeding UBOUND; OVERFLOW SAT saturates it} { + test {INCREX - BYFLOAT default rejects increment exceeding UBOUND; SATURATE saturates it} { r set mykey 10.0 - assert_error "ERR value is out of bounds*" {r increx mykey BYFLOAT 10.0 UBOUND 15.5} + assert_equal [lmap v [r increx mykey BYFLOAT 10.0 UBOUND 15.5] {roundFloat $v}] {10 0} assert_equal [roundFloat [r get mykey]] 10 - # OVERFLOW SAT saturates the result at UBOUND - assert_equal [lmap v [r increx mykey BYFLOAT 10.0 UBOUND 15.5 OVERFLOW SAT] {roundFloat $v}] {15.5 5.5} + # SATURATE saturates the result at UBOUND + assert_equal [lmap v [r increx mykey BYFLOAT 10.0 UBOUND 15.5 SATURATE] {roundFloat $v}] {15.5 5.5} } - test {INCREX - BYFLOAT OVERFLOW FAIL rejects decrement falling below LBOUND; OVERFLOW SAT floors it} { + test {INCREX - BYFLOAT default rejects decrement falling below LBOUND; SATURATE floors it} { r set mykey 10.0 - assert_error "ERR value is out of bounds*" {r increx mykey BYFLOAT -10.0 LBOUND 5.5} + assert_equal [lmap v [r increx mykey BYFLOAT -10.0 LBOUND 5.5] {roundFloat $v}] {10 0} assert_equal [roundFloat [r get mykey]] 10 - # OVERFLOW SAT floors the result at LBOUND - assert_equal [lmap v [r increx mykey BYFLOAT -10.0 LBOUND 5.5 OVERFLOW SAT] {roundFloat $v}] {5.5 -4.5} + # SATURATE floors the result at LBOUND + assert_equal [lmap v [r increx mykey BYFLOAT -10.0 LBOUND 5.5 SATURATE] {roundFloat $v}] {5.5 -4.5} } - test {INCREX - BYFLOAT within bounds is unaffected by OVERFLOW policy} { + test {INCREX - BYFLOAT within bounds is unaffected by SATURATE policy} { r set mykey 1.5 assert_equal [lmap v [r increx mykey BYFLOAT 0.25 UBOUND 10.0] {roundFloat $v}] {1.75 0.25} - assert_equal [lmap v [r increx mykey BYFLOAT 0.25 UBOUND 10.0 OVERFLOW SAT] {roundFloat $v}] {2 0.25} + assert_equal [lmap v [r increx mykey BYFLOAT 0.25 UBOUND 10.0 SATURATE] {roundFloat $v}] {2 0.25} } test {INCREX - BYFLOAT with both LBOUND and UBOUND} { @@ -260,9 +258,9 @@ start_server {tags {"increx"}} { # Within range -> allowed assert_equal [lmap v [r increx mykey BYFLOAT 1.5 LBOUND 0 UBOUND 10] {roundFloat $v}] {6.5 1.5} # Exceeds UBOUND -> rejected - assert_error "ERR value is out of bounds*" {r increx mykey BYFLOAT 10 LBOUND 0 UBOUND 10} + assert_equal [lmap v [r increx mykey BYFLOAT 10 LBOUND 0 UBOUND 10] {roundFloat $v}] {6.5 0} # Falls below LBOUND -> rejected - assert_error "ERR value is out of bounds*" {r increx mykey BYFLOAT -20 LBOUND 0 UBOUND 10} + assert_equal [lmap v [r increx mykey BYFLOAT -20 LBOUND 0 UBOUND 10] {roundFloat $v}] {6.5 0} assert_equal [lmap v [r get mykey] {roundFloat $v}] {6.5} } @@ -272,22 +270,22 @@ start_server {tags {"increx"}} { assert_equal [lmap v [r increx mykey BYFLOAT -10.0 LBOUND 0] {roundFloat $v}] {0 -10} } - test {INCREX - BYINT positive overflow: default errors, OVERFLOW SAT saturates} { + test {INCREX - BYINT positive overflow: default rejects, SATURATE saturates} { # LLONG_MAX = 9223372036854775807 r set mykey 9223372036854775800 - assert_error "*increment or decrement would overflow*" {r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807} + assert_equal [r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807] {9223372036854775800 0} assert_equal [r get mykey] 9223372036854775800 - # OVERFLOW SAT: overflow saturates to LLONG_MAX, then saturates to UBOUND - assert_equal [r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807 OVERFLOW SAT] {9223372036854775807 7} + # SATURATE: overflow saturates to LLONG_MAX, then saturates to UBOUND + assert_equal [r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807 SATURATE] {9223372036854775807 7} } - test {INCREX - BYINT negative overflow: default errors, OVERFLOW SAT saturates} { + test {INCREX - BYINT negative overflow: default rejects, SATURATE saturates} { # LLONG_MIN = -9223372036854775808 r set mykey -9223372036854775800 - assert_error "*increment or decrement would overflow*" {r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808} + assert_equal [r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808] {-9223372036854775800 0} assert_equal [r get mykey] -9223372036854775800 - # OVERFLOW SAT: overflow saturates to LLONG_MIN, then saturates to LBOUND - assert_equal [r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808 OVERFLOW SAT] {-9223372036854775808 -8} + # SATURATE: overflow saturates to LLONG_MIN, then saturates to LBOUND + assert_equal [r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808 SATURATE] {-9223372036854775808 -8} } test {INCREX - BYINT on new key (created from zero) with bound} { @@ -296,7 +294,7 @@ start_server {tags {"increx"}} { assert_equal [r increx mykey BYINT 5 UBOUND 10] {5 5} r del mykey # Increment from 0 exceeds UBOUND -> rejected, key not created - assert_error "*out of bounds*" {r increx mykey BYINT 15 UBOUND 10} + assert_equal [r increx mykey BYINT 15 UBOUND 10] {0 0} assert_equal [r exists mykey] 0 } @@ -306,28 +304,28 @@ start_server {tags {"increx"}} { assert_equal [lmap v [r increx mykey BYFLOAT 5.5 UBOUND 10] {roundFloat $v}] {5.5 5.5} r del mykey # Increment from 0 exceeds UBOUND -> rejected, key not created - assert_error "ERR value is out of bounds*" {r increx mykey BYFLOAT 15.5 UBOUND 10} + assert_equal [lmap v [r increx mykey BYFLOAT 15.5 UBOUND 10] {roundFloat $v}] {0 0} assert_equal [r exists mykey] 0 } - test {INCREX - default with no bound behaves like INCRBY/INCRBYFLOAT} { + test {INCREX - default with no bound saturates to type limits with SATURATE, rejects otherwise} { # In-range increments behave like INCRBY/INCRBYFLOAT. r set mykey 10 assert_equal [r increx mykey BYINT 1] {11 1} assert_equal [lmap v [r increx mykey BYFLOAT 1.0] {roundFloat $v}] {12 1} assert_equal [r increx mykey] {13 1} - # BYINT overflow without an explicit bound -> error (like INCRBY). + # BYINT overflow without an explicit bound -> default rejects (reply [current, 0]). r set mykey 9223372036854775800 - assert_error "*increment or decrement would overflow*" {r increx mykey BYINT 9223372036854775800} + assert_equal [r increx mykey BYINT 9223372036854775800] {9223372036854775800 0} assert_equal [r get mykey] 9223372036854775800 } - test {INCREX - error aborts before side effects: neither value nor TTL is modified} { + test {INCREX - reject aborts before side effects: neither value nor TTL is modified} { r del mykey r set mykey 10 # An out-of-range result aborts the command before any side effect. - assert_error "*out of bounds*" {r increx mykey BYINT 100 UBOUND 15 EX 100} + assert_equal [r increx mykey BYINT 100 UBOUND 15 EX 100] {10 0} assert_equal [r get mykey] 10 assert_equal [r ttl mykey] -1 @@ -339,32 +337,11 @@ start_server {tags {"increx"}} { r del mykey r set mykey 10 - # OVERFLOW SAT also updates the TTL when saturation kicks in. - assert_equal [r increx mykey BYINT 100 UBOUND 15 OVERFLOW SAT EX 200] {15 5} + # SATURATE also updates the TTL when saturation kicks in. + assert_equal [r increx mykey BYINT 100 UBOUND 15 SATURATE EX 200] {15 5} assert_morethan [r ttl mykey] 0 } - # --------------------------------------------------------------------- - # OVERFLOW REJECT: leave the key (and TTL) unchanged and reply - # [current_value, 0] when the result would be out of bounds, instead of - # producing an error. - # --------------------------------------------------------------------- - - test {INCREX - BYINT REJECT on overflow leaves value unchanged, in-range applies normally} { - # llong overflow path - r set mykey 9223372036854775800 - assert_equal [r increx mykey BYINT 9223372036854775800 OVERFLOW REJECT] {9223372036854775800 0} - assert_equal [r get mykey] 9223372036854775800 - # UBOUND / LBOUND paths - r set mykey 10 - assert_equal [r increx mykey BYINT 100 UBOUND 15 OVERFLOW REJECT] {10 0} - assert_equal [r increx mykey BYINT -100 LBOUND 5 OVERFLOW REJECT] {10 0} - assert_equal [r get mykey] 10 - # In-range increment is applied normally - assert_equal [r increx mykey BYINT 3 UBOUND 20 OVERFLOW REJECT] {13 3} - assert_equal [r get mykey] 13 - } - # --------------------------------------------------------------------- # Argument parsing / syntax validation # --------------------------------------------------------------------- @@ -401,10 +378,8 @@ start_server {tags {"increx"}} { assert_error "*syntax error*" {r increx mykey BYFLOAT 1.0 BYFLOAT 2.0} assert_error "*syntax error*" {r increx mykey LBOUND 0 LBOUND 1} assert_error "*syntax error*" {r increx mykey UBOUND 9 UBOUND 8} - assert_error "*syntax error*" {r increx mykey OVERFLOW FAIL OVERFLOW SAT LBOUND 0} - assert_error "*syntax error*" {r increx mykey OVERFLOW SAT OVERFLOW SAT LBOUND 0} - assert_error "*syntax error*" {r increx mykey OVERFLOW REJECT OVERFLOW SAT LBOUND 0} - assert_error "*syntax error*" {r increx mykey OVERFLOW REJECT OVERFLOW REJECT LBOUND 0} + assert_error "*syntax error*" {r increx mykey SATURATE SATURATE LBOUND 0} + assert_error "*syntax error*" {r increx mykey SAT LBOUND 0} assert_error "*syntax error*" {r increx mykey ENX ENX EX 10} assert_error "*syntax error*" {r increx mykey PERSIST PERSIST} assert_error "*syntax error*" {r increx mykey EX 10 EX 20} @@ -585,7 +560,7 @@ start_server {tags {"increx"}} { # LBOUND/UBOUND interleaved with increment r set mykey 5 - assert_equal [r increx mykey LBOUND 0 BYINT 100 UBOUND 10 OVERFLOW SAT] {10 5} + assert_equal [r increx mykey LBOUND 0 BYINT 100 UBOUND 10 SATURATE] {10 5} } # --------------------------------------------------------------------- @@ -713,11 +688,11 @@ start_server {tags {"increx"}} { r flushall set repl [attach_to_replication_stream] r set mykey 50 - # With UBOUND + OVERFLOW SAT the final value is saturated; the SET + # With UBOUND + SATURATE the final value is saturated; the SET # rewrite must carry the saturated value (80), not the unbounded 150. - r increx mykey BYINT 100 UBOUND 80 OVERFLOW SAT + r increx mykey BYINT 100 UBOUND 80 SATURATE r set myfloat 10 - r increx myfloat BYFLOAT 100 UBOUND 42.5 OVERFLOW SAT + r increx myfloat BYFLOAT 100 UBOUND 42.5 SATURATE assert_replication_stream $repl { {select *} {set mykey 50*}