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;
}
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

View File

@ -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);

View File

@ -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",

View File

@ -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},

View File

@ -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);