diff options
author | Ning Sun <classicning@gmail.com> | 2021-08-02 13:57:49 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-02 08:57:49 +0300 |
commit | f74af0e61d6104922325e2f38284ea66e4f3ccd4 (patch) | |
tree | 678f0dc4410c91048718eeecffacd5ef6a22da2c /src/expire.c | |
parent | 82c3158ad5fe5aab002a6d0565832d5bd15082f5 (diff) | |
download | redis-f74af0e61d6104922325e2f38284ea66e4f3ccd4.tar.gz |
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>
Diffstat (limited to 'src/expire.c')
-rw-r--r-- | src/expire.c | 106 |
1 files changed, 104 insertions, 2 deletions
diff --git a/src/expire.c b/src/expire.c index 91798b527..2dcdddaf6 100644 --- a/src/expire.c +++ b/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); } - |