Add NX/XX/GT/LT options to EXPIRE command group (#2795)
Add NX, XX, GT, and LT flags to EXPIRE, PEXPIRE, EXPIREAT, PEXAPIREAT. - NX - only modify the TTL if no TTL is currently set - XX - only modify the TTL if there is a TTL currently set - GT - only increase the TTL (considering non-volatile keys as infinite expire time) - LT - only decrease the TTL (considering non-volatile keys as infinite expire time) return value of the command is 0 when the operation was skipped due to one of these flags. Signed-off-by: Ning Sun <sunng@protonmail.com>
This commit is contained in:
parent
82c3158ad5
commit
f74af0e61d
106
src/expire.c
106
src/expire.c
@ -489,6 +489,56 @@ int checkAlreadyExpired(long long when) {
|
||||
return (when <= mstime() && !server.loading && !server.masterhost);
|
||||
}
|
||||
|
||||
#define EXPIRE_NX (1<<0)
|
||||
#define EXPIRE_XX (1<<1)
|
||||
#define EXPIRE_GT (1<<2)
|
||||
#define EXPIRE_LT (1<<3)
|
||||
|
||||
/* Parse additional flags of expire commands
|
||||
*
|
||||
* Supported flags:
|
||||
* - NX: set expiry only when the key has no expiry
|
||||
* - XX: set expiry only when the key has an existing expiry
|
||||
* - GT: set expiry only when the new expiry is greater than current one
|
||||
* - LT: set expiry only when the new expiry is less than current one */
|
||||
int parseExtendedExpireArgumentsOrReply(client *c, int *flags) {
|
||||
int nx = 0, xx = 0, gt = 0, lt = 0;
|
||||
|
||||
int j = 3;
|
||||
while (j < c->argc) {
|
||||
char *opt = c->argv[j]->ptr;
|
||||
if (!strcasecmp(opt,"nx")) {
|
||||
*flags |= EXPIRE_NX;
|
||||
nx = 1;
|
||||
} else if (!strcasecmp(opt,"xx")) {
|
||||
*flags |= EXPIRE_XX;
|
||||
xx = 1;
|
||||
} else if (!strcasecmp(opt,"gt")) {
|
||||
*flags |= EXPIRE_GT;
|
||||
gt = 1;
|
||||
} else if (!strcasecmp(opt,"lt")) {
|
||||
*flags |= EXPIRE_LT;
|
||||
lt = 1;
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unsupported option %s", opt);
|
||||
return C_ERR;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
if ((nx && xx) || (nx && gt) || (nx && lt)) {
|
||||
addReplyError(c, "NX and XX, GT or LT options at the same time are not compatible");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (gt && lt) {
|
||||
addReplyError(c, "GT and LT options at the same time are not compatible");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Expires Commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
@ -499,10 +549,19 @@ int checkAlreadyExpired(long long when) {
|
||||
* for *AT variants of the command, or the current time for relative expires).
|
||||
*
|
||||
* unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
|
||||
* the argv[2] parameter. The basetime is always specified in milliseconds. */
|
||||
* the argv[2] parameter. The basetime is always specified in milliseconds.
|
||||
*
|
||||
* Additional flags are supported and parsed via parseExtendedExpireArguments */
|
||||
void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
robj *key = c->argv[1], *param = c->argv[2];
|
||||
long long when; /* unix time in milliseconds when the key will expire. */
|
||||
long long current_expire = -1;
|
||||
int flag = 0;
|
||||
|
||||
/* checking optional flags */
|
||||
if (parseExtendedExpireArgumentsOrReply(c, &flag) != C_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
|
||||
return;
|
||||
@ -521,6 +580,50 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
current_expire = getExpire(c->db, key);
|
||||
|
||||
/* NX option is set, check current expiry */
|
||||
if (flag & EXPIRE_NX) {
|
||||
if (current_expire != -1) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* XX option is set, check current expiry */
|
||||
if (flag & EXPIRE_XX) {
|
||||
if (current_expire == -1) {
|
||||
/* reply 0 when the key has no expiry */
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* GT option is set, check current expiry */
|
||||
if (flag & EXPIRE_GT) {
|
||||
/* When current_expire is -1, we consider it as infinite TTL,
|
||||
* so expire command with gt always fail the GT. */
|
||||
if (when <= current_expire || current_expire == -1) {
|
||||
/* reply 0 when the new expiry is not greater than current */
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* LT option is set, check current expiry */
|
||||
if (flag & EXPIRE_LT) {
|
||||
/* When current_expire -1, we consider it as infinite TTL,
|
||||
* but 'when' can still be negative at this point, so if there is
|
||||
* an expiry on the key and it's not less than current, we fail the LT. */
|
||||
if (current_expire != -1 && when >= current_expire) {
|
||||
/* reply 0 when the new expiry is not less than current */
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checkAlreadyExpired(when)) {
|
||||
robj *aux;
|
||||
|
||||
@ -637,4 +740,3 @@ void touchCommand(client *c) {
|
||||
if (lookupKeyRead(c->db,c->argv[j]) != NULL) touched++;
|
||||
addReplyLongLong(c,touched);
|
||||
}
|
||||
|
||||
|
@ -678,19 +678,19 @@ struct redisCommand redisCommandTable[] = {
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,2,1,0,0,0},
|
||||
|
||||
{"expire",expireCommand,3,
|
||||
{"expire",expireCommand,-3,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"expireat",expireatCommand,3,
|
||||
{"expireat",expireatCommand,-3,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"pexpire",pexpireCommand,3,
|
||||
{"pexpire",pexpireCommand,-3,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"pexpireat",pexpireatCommand,3,
|
||||
{"pexpireat",pexpireatCommand,-3,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
|
@ -601,4 +601,125 @@ start_server {tags {"expire"}} {
|
||||
{del foo}
|
||||
}
|
||||
} {} {needs:repl}
|
||||
|
||||
test {EXPIRE with NX option on a key with ttl} {
|
||||
r SET foo bar EX 100
|
||||
assert_equal [r EXPIRE foo 200 NX] 0
|
||||
assert_range [r TTL foo] 50 100
|
||||
} {}
|
||||
|
||||
test {EXPIRE with NX option on a key without ttl} {
|
||||
r SET foo bar
|
||||
assert_equal [r EXPIRE foo 200 NX] 1
|
||||
assert_range [r TTL foo] 100 200
|
||||
} {}
|
||||
|
||||
test {EXPIRE with XX option on a key with ttl} {
|
||||
r SET foo bar EX 100
|
||||
assert_equal [r EXPIRE foo 200 XX] 1
|
||||
assert_range [r TTL foo] 100 200
|
||||
} {}
|
||||
|
||||
test {EXPIRE with XX option on a key without ttl} {
|
||||
r SET foo bar
|
||||
assert_equal [r EXPIRE foo 200 XX] 0
|
||||
assert_equal [r TTL foo] -1
|
||||
} {}
|
||||
|
||||
test {EXPIRE with GT option on a key with lower ttl} {
|
||||
r SET foo bar EX 100
|
||||
assert_equal [r EXPIRE foo 200 GT] 1
|
||||
assert_range [r TTL foo] 100 200
|
||||
} {}
|
||||
|
||||
test {EXPIRE with GT option on a key with higher ttl} {
|
||||
r SET foo bar EX 200
|
||||
assert_equal [r EXPIRE foo 100 GT] 0
|
||||
assert_range [r TTL foo] 100 200
|
||||
} {}
|
||||
|
||||
test {EXPIRE with GT option on a key without ttl} {
|
||||
r SET foo bar
|
||||
assert_equal [r EXPIRE foo 200 GT] 0
|
||||
assert_equal [r TTL foo] -1
|
||||
} {}
|
||||
|
||||
test {EXPIRE with LT option on a key with higher ttl} {
|
||||
r SET foo bar EX 100
|
||||
assert_equal [r EXPIRE foo 200 LT] 0
|
||||
assert_range [r TTL foo] 50 100
|
||||
} {}
|
||||
|
||||
test {EXPIRE with LT option on a key with lower ttl} {
|
||||
r SET foo bar EX 200
|
||||
assert_equal [r EXPIRE foo 100 LT] 1
|
||||
assert_range [r TTL foo] 50 100
|
||||
} {}
|
||||
|
||||
test {EXPIRE with LT option on a key without ttl} {
|
||||
r SET foo bar
|
||||
assert_equal [r EXPIRE foo 100 LT] 1
|
||||
assert_range [r TTL foo] 50 100
|
||||
} {}
|
||||
|
||||
test {EXPIRE with LT and XX option on a key with ttl} {
|
||||
r SET foo bar EX 200
|
||||
assert_equal [r EXPIRE foo 100 LT XX] 1
|
||||
assert_range [r TTL foo] 50 100
|
||||
} {}
|
||||
|
||||
test {EXPIRE with LT and XX option on a key without ttl} {
|
||||
r SET foo bar
|
||||
assert_equal [r EXPIRE foo 200 LT XX] 0
|
||||
assert_equal [r TTL foo] -1
|
||||
} {}
|
||||
|
||||
test {EXPIRE with conflicting options: LT GT} {
|
||||
catch {r EXPIRE foo 200 LT GT} e
|
||||
set e
|
||||
} {ERR GT and LT options at the same time are not compatible}
|
||||
|
||||
test {EXPIRE with conflicting options: NX GT} {
|
||||
catch {r EXPIRE foo 200 NX GT} e
|
||||
set e
|
||||
} {ERR NX and XX, GT or LT options at the same time are not compatible}
|
||||
|
||||
test {EXPIRE with conflicting options: NX LT} {
|
||||
catch {r EXPIRE foo 200 NX LT} e
|
||||
set e
|
||||
} {ERR NX and XX, GT or LT options at the same time are not compatible}
|
||||
|
||||
test {EXPIRE with conflicting options: NX XX} {
|
||||
catch {r EXPIRE foo 200 NX XX} e
|
||||
set e
|
||||
} {ERR NX and XX, GT or LT options at the same time are not compatible}
|
||||
|
||||
test {EXPIRE with unsupported options} {
|
||||
catch {r EXPIRE foo 200 AB} e
|
||||
set e
|
||||
} {ERR Unsupported option AB}
|
||||
|
||||
test {EXPIRE with unsupported options} {
|
||||
catch {r EXPIRE foo 200 XX AB} e
|
||||
set e
|
||||
} {ERR Unsupported option AB}
|
||||
|
||||
test {EXPIRE with negative expiry} {
|
||||
r SET foo bar EX 100
|
||||
assert_equal [r EXPIRE foo -10 LT] 1
|
||||
assert_equal [r TTL foo] -2
|
||||
} {}
|
||||
|
||||
test {EXPIRE with negative expiry on a non-valitale key} {
|
||||
r SET foo bar
|
||||
assert_equal [r EXPIRE foo -10 LT] 1
|
||||
assert_equal [r TTL foo] -2
|
||||
} {}
|
||||
|
||||
test {EXPIRE with non-existed key} {
|
||||
assert_equal [r EXPIRE none 100 NX] 0
|
||||
assert_equal [r EXPIRE none 100 XX] 0
|
||||
assert_equal [r EXPIRE none 100 GT] 0
|
||||
assert_equal [r EXPIRE none 100 LT] 0
|
||||
} {}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user