diff --git a/src/db.cpp b/src/db.cpp index b4ac46a2a..2a047870f 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -1227,6 +1227,39 @@ int removeExpireCore(redisDb *db, robj *key, dictEntry *de) { return 1; } +int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey) { + dictEntry *de = dictFind(db->pdict,ptrFromObj(key)); + serverAssertWithInfo(NULL,key,de != NULL); + + robj *val = (robj*)dictGetVal(de); + if (!val->FExpires()) + return 0; + + auto itr = db->setexpire->find((sds)dictGetKey(de)); + serverAssert(itr != db->setexpire->end()); + serverAssert(itr->key() == (sds)dictGetKey(de)); + if (!itr->FFat()) + return 0; + + int found = 0; + for (auto subitr : *itr) + { + if (subitr.subkey() == nullptr) + continue; + if (sdscmp((sds)subitr.subkey(), szFromObj(subkey)) == 0) + { + itr->erase(subitr); + found = 1; + break; + } + } + + if (itr->pfatentry()->size() == 0) + removeExpireCore(db, key, de); + + return found; +} + /* Set an expire to the specified key. If the expire is set in the context * of an user calling a command 'c' is the client, otherwise 'c' is set * to NULL. The 'when' parameter is the absolute unix time in milliseconds diff --git a/src/expire.cpp b/src/expire.cpp index ba0b99284..72968119a 100644 --- a/src/expire.cpp +++ b/src/expire.cpp @@ -544,11 +544,32 @@ void ttlGenericCommand(client *c, int output_ms) { addReplyLongLong(c,-2); return; } + /* The key exists. Return -1 if it has no expire, or the actual - * TTL value otherwise. */ + * TTL value otherwise. */ expireEntry *pexpire = getExpire(c->db,c->argv[1]); - if (pexpire != nullptr) - pexpire->FGetPrimaryExpire(&expire); + + if (c->argc == 2) { + // primary expire + if (pexpire != nullptr) + pexpire->FGetPrimaryExpire(&expire); + } else if (c->argc == 3) { + // We want a subkey expire + if (pexpire && pexpire->FFat()) { + for (auto itr : *pexpire) { + if (itr.subkey() == nullptr) + continue; + if (sdscmp((sds)itr.subkey(), szFromObj(c->argv[2])) == 0) { + expire = itr.when(); + break; + } + } + } + } else { + addReplyError(c, "Invalid arguments"); + return; + } + if (expire != -1) { ttl = expire-mstime(); @@ -574,11 +595,22 @@ void pttlCommand(client *c) { /* PERSIST key */ void persistCommand(client *c) { if (lookupKeyWrite(c->db,c->argv[1])) { - if (removeExpire(c->db,c->argv[1])) { - addReply(c,shared.cone); - g_pserver->dirty++; + if (c->argc == 2) { + if (removeExpire(c->db,c->argv[1])) { + addReply(c,shared.cone); + g_pserver->dirty++; + } else { + addReply(c,shared.czero); + } + } else if (c->argc == 3) { + if (removeSubkeyExpire(c->db, c->argv[1], c->argv[2])) { + addReply(c,shared.cone); + g_pserver->dirty++; + } else { + addReply(c,shared.czero); + } } else { - addReply(c,shared.czero); + addReplyError(c, "Invalid arguments"); } } else { addReply(c,shared.czero); diff --git a/src/help.h b/src/help.h index 01b856b9d..96635c887 100644 --- a/src/help.h +++ b/src/help.h @@ -632,8 +632,8 @@ struct commandHelp { 0, "2.2.3" }, { "PERSIST", - "key", - "Remove the expiration from a key", + "key [subkey]", + "Remove the expiration from a key or subkey", 0, "2.2.0" }, { "PEXPIRE", @@ -677,8 +677,8 @@ struct commandHelp { 6, "2.0.0" }, { "PTTL", - "key", - "Get the time to live for a key in milliseconds", + "key [subkey]", + "Get the time to live for a key or subkey in milliseconds", 0, "2.6.0" }, { "PUBLISH", @@ -952,8 +952,8 @@ struct commandHelp { 0, "3.2.1" }, { "TTL", - "key", - "Get the time to live for a key", + "key [subkey]", + "Get the time to live for a key or subkey", 0, "1.0.0" }, { "TYPE", diff --git a/src/server.cpp b/src/server.cpp index d88a0d9e4..73d1c1752 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -730,7 +730,7 @@ struct redisCommand redisCommandTable[] = { "admin no-script", 0,NULL,0,0,0,0,0,0}, - {"ttl",ttlCommand,2, + {"ttl",ttlCommand,-2, "read-only fast random @keyspace", 0,NULL,1,1,1,0,0,0}, @@ -738,11 +738,11 @@ struct redisCommand redisCommandTable[] = { "read-only fast @keyspace", 0,NULL,1,-1,1,0,0,0}, - {"pttl",pttlCommand,2, + {"pttl",pttlCommand,-2, "read-only fast random @keyspace", 0,NULL,1,1,1,0,0,0}, - {"persist",persistCommand,2, + {"persist",persistCommand,-2, "write fast @keyspace", 0,NULL,1,1,1,0,0,0}, diff --git a/src/server.h b/src/server.h index 4e7dab351..85d8b442e 100644 --- a/src/server.h +++ b/src/server.h @@ -800,6 +800,16 @@ public: void expireSubKey(const char *szSubkey, long long when) { + // First check if the subkey already has an expiration + for (auto &entry : m_vecexpireEntries) + { + if (entry.spsubkey == nullptr) + continue; + if (sdscmp((sds)entry.spsubkey.get(), (sds)szSubkey) == 0) { + m_vecexpireEntries.erase(m_vecexpireEntries.begin() + (&entry - m_vecexpireEntries.data())); + break; + } + } auto itrInsert = std::lower_bound(m_vecexpireEntries.begin(), m_vecexpireEntries.end(), when); const char *subkey = (szSubkey) ? sdsdup(szSubkey) : nullptr; m_vecexpireEntries.emplace(itrInsert, when, subkey); @@ -823,6 +833,7 @@ class expireEntry { public: class iter { + friend class expireEntry; expireEntry *m_pentry = nullptr; size_t m_idx = 0; @@ -958,6 +969,14 @@ public: return iter(this, u.m_pfatentry->size()); return iter(this, 1); } + + void erase(iter &itr) + { + if (!FFat()) + throw -1; // assert + pfatentry()->m_vecexpireEntries.erase( + pfatentry()->m_vecexpireEntries.begin() + itr.m_idx); + } bool FGetPrimaryExpire(long long *pwhen) { @@ -2408,6 +2427,7 @@ int rewriteConfig(char *path); /* db.c -- Keyspace access API */ int removeExpire(redisDb *db, robj *key); int removeExpireCore(redisDb *db, robj *key, dictEntry *de); +int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey); void propagateExpire(redisDb *db, robj *key, int lazy); int expireIfNeeded(redisDb *db, robj *key); expireEntry *getExpire(redisDb *db, robj_roptr key);