Implement TTL and PERSIST commands for subkeys. Also ensure expiremember replaces any old subkey expiries

Former-commit-id: 16f96efbe7c6c27f2a79f5b472447407f905df15
This commit is contained in:
John Sully 2019-10-13 12:03:25 -04:00
parent 3670376457
commit 0a00341a80
5 changed files with 101 additions and 16 deletions

View File

@ -1227,6 +1227,39 @@ int removeExpireCore(redisDb *db, robj *key, dictEntry *de) {
return 1; 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 /* 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 * 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 * to NULL. The 'when' parameter is the absolute unix time in milliseconds

View File

@ -544,11 +544,32 @@ void ttlGenericCommand(client *c, int output_ms) {
addReplyLongLong(c,-2); addReplyLongLong(c,-2);
return; return;
} }
/* The key exists. Return -1 if it has no expire, or the actual /* 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]); expireEntry *pexpire = getExpire(c->db,c->argv[1]);
if (c->argc == 2) {
// primary expire
if (pexpire != nullptr) if (pexpire != nullptr)
pexpire->FGetPrimaryExpire(&expire); 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) { if (expire != -1) {
ttl = expire-mstime(); ttl = expire-mstime();
@ -574,12 +595,23 @@ void pttlCommand(client *c) {
/* PERSIST key */ /* PERSIST key */
void persistCommand(client *c) { void persistCommand(client *c) {
if (lookupKeyWrite(c->db,c->argv[1])) { if (lookupKeyWrite(c->db,c->argv[1])) {
if (c->argc == 2) {
if (removeExpire(c->db,c->argv[1])) { if (removeExpire(c->db,c->argv[1])) {
addReply(c,shared.cone); addReply(c,shared.cone);
g_pserver->dirty++; g_pserver->dirty++;
} else { } else {
addReply(c,shared.czero); 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 {
addReplyError(c, "Invalid arguments");
}
} else { } else {
addReply(c,shared.czero); addReply(c,shared.czero);
} }

View File

@ -632,8 +632,8 @@ struct commandHelp {
0, 0,
"2.2.3" }, "2.2.3" },
{ "PERSIST", { "PERSIST",
"key", "key [subkey]",
"Remove the expiration from a key", "Remove the expiration from a key or subkey",
0, 0,
"2.2.0" }, "2.2.0" },
{ "PEXPIRE", { "PEXPIRE",
@ -677,8 +677,8 @@ struct commandHelp {
6, 6,
"2.0.0" }, "2.0.0" },
{ "PTTL", { "PTTL",
"key", "key [subkey]",
"Get the time to live for a key in milliseconds", "Get the time to live for a key or subkey in milliseconds",
0, 0,
"2.6.0" }, "2.6.0" },
{ "PUBLISH", { "PUBLISH",
@ -952,8 +952,8 @@ struct commandHelp {
0, 0,
"3.2.1" }, "3.2.1" },
{ "TTL", { "TTL",
"key", "key [subkey]",
"Get the time to live for a key", "Get the time to live for a key or subkey",
0, 0,
"1.0.0" }, "1.0.0" },
{ "TYPE", { "TYPE",

View File

@ -730,7 +730,7 @@ struct redisCommand redisCommandTable[] = {
"admin no-script", "admin no-script",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"ttl",ttlCommand,2, {"ttl",ttlCommand,-2,
"read-only fast random @keyspace", "read-only fast random @keyspace",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},
@ -738,11 +738,11 @@ struct redisCommand redisCommandTable[] = {
"read-only fast @keyspace", "read-only fast @keyspace",
0,NULL,1,-1,1,0,0,0}, 0,NULL,1,-1,1,0,0,0},
{"pttl",pttlCommand,2, {"pttl",pttlCommand,-2,
"read-only fast random @keyspace", "read-only fast random @keyspace",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},
{"persist",persistCommand,2, {"persist",persistCommand,-2,
"write fast @keyspace", "write fast @keyspace",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,1,1,0,0,0},

View File

@ -800,6 +800,16 @@ public:
void expireSubKey(const char *szSubkey, long long when) 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); auto itrInsert = std::lower_bound(m_vecexpireEntries.begin(), m_vecexpireEntries.end(), when);
const char *subkey = (szSubkey) ? sdsdup(szSubkey) : nullptr; const char *subkey = (szSubkey) ? sdsdup(szSubkey) : nullptr;
m_vecexpireEntries.emplace(itrInsert, when, subkey); m_vecexpireEntries.emplace(itrInsert, when, subkey);
@ -823,6 +833,7 @@ class expireEntry {
public: public:
class iter class iter
{ {
friend class expireEntry;
expireEntry *m_pentry = nullptr; expireEntry *m_pentry = nullptr;
size_t m_idx = 0; size_t m_idx = 0;
@ -959,6 +970,14 @@ public:
return iter(this, 1); 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) bool FGetPrimaryExpire(long long *pwhen)
{ {
*pwhen = -1; *pwhen = -1;
@ -2408,6 +2427,7 @@ int rewriteConfig(char *path);
/* db.c -- Keyspace access API */ /* db.c -- Keyspace access API */
int removeExpire(redisDb *db, robj *key); int removeExpire(redisDb *db, robj *key);
int removeExpireCore(redisDb *db, robj *key, dictEntry *de); int removeExpireCore(redisDb *db, robj *key, dictEntry *de);
int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey);
void propagateExpire(redisDb *db, robj *key, int lazy); void propagateExpire(redisDb *db, robj *key, int lazy);
int expireIfNeeded(redisDb *db, robj *key); int expireIfNeeded(redisDb *db, robj *key);
expireEntry *getExpire(redisDb *db, robj_roptr key); expireEntry *getExpire(redisDb *db, robj_roptr key);