summaryrefslogtreecommitdiff
path: root/src/expire.c
diff options
context:
space:
mode:
authorNing Sun <classicning@gmail.com>2021-08-02 13:57:49 +0800
committerGitHub <noreply@github.com>2021-08-02 08:57:49 +0300
commitf74af0e61d6104922325e2f38284ea66e4f3ccd4 (patch)
tree678f0dc4410c91048718eeecffacd5ef6a22da2c /src/expire.c
parent82c3158ad5fe5aab002a6d0565832d5bd15082f5 (diff)
downloadredis-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.c106
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);
}
-