diff --git a/docs/api_status.md b/docs/api_status.md index 4b1ca993a..0d91bba54 100644 --- a/docs/api_status.md +++ b/docs/api_status.md @@ -177,7 +177,7 @@ with respect to Memcached and Redis APIs. - [X] EVAL - [X] EVALSHA - [ ] OBJECT - - [ ] PERSIST + - [x] PERSIST - [X] PTTL - [ ] RESTORE - [X] SCRIPT LOAD/EXISTS diff --git a/src/server/generic_family.cc b/src/server/generic_family.cc index 4a649a755..535fd73e2 100644 --- a/src/server/generic_family.cc +++ b/src/server/generic_family.cc @@ -29,6 +29,8 @@ using namespace facade; namespace { +OpStatus OpPersist(const OpArgs& op_args, string_view key); + class Renamer { public: Renamer(ShardId source_id) : src_sid_(source_id) { @@ -177,6 +179,22 @@ struct ScanOpts { unsigned bucket_id = UINT_MAX; }; +OpStatus OpPersist(const OpArgs& op_args, string_view key) { + auto& db_slice = op_args.shard->db_slice(); + auto [it, expire_it] = db_slice.FindExt(op_args.db_cntx, key); + + if (!IsValid(it)) { + return OpStatus::KEY_NOTFOUND; + } else { + if (IsValid(expire_it)) { + // The SKIPPED not really used, just placeholder for error + return db_slice.UpdateExpire(op_args.db_cntx.db_index, it, 0) ? OpStatus::OK + : OpStatus::SKIPPED; + } + return OpStatus::OK; // fall though - this is the default + } +} + bool ScanCb(const OpArgs& op_args, PrimeIterator it, const ScanOpts& opts, StringVec* res) { auto& db_slice = op_args.shard->db_slice(); if (it->second.HasExpire()) { @@ -348,6 +366,15 @@ void GenericFamily::Exists(CmdArgList args, ConnectionContext* cntx) { return (*cntx)->SendLong(result.load(memory_order_release)); } +void GenericFamily::Persist(CmdArgList args, ConnectionContext* cntx) { + string_view key = ArgS(args, 1); + + auto cb = [&](Transaction* t, EngineShard* shard) { return OpPersist(t->GetOpArgs(shard), key); }; + + OpStatus status = cntx->transaction->ScheduleSingleHop(move(cb)); + (*cntx)->SendLong(status == OpStatus::OK); +} + void GenericFamily::Expire(CmdArgList args, ConnectionContext* cntx) { string_view key = ArgS(args, 1); string_view sec = ArgS(args, 2); @@ -1036,6 +1063,7 @@ void GenericFamily::Register(CommandRegistry* registry) { << CI{"EXISTS", CO::READONLY | CO::FAST, -2, 1, -1, 1}.HFUNC(Exists) << CI{"EXPIRE", CO::WRITE | CO::FAST, 3, 1, 1, 1}.HFUNC(Expire) << CI{"EXPIREAT", CO::WRITE | CO::FAST, 3, 1, 1, 1}.HFUNC(ExpireAt) + << CI{"PERSIST", CO::WRITE | CO::FAST, 2, 1, 1, 1}.HFUNC(Persist) << CI{"KEYS", CO::READONLY, 2, 0, 0, 0}.HFUNC(Keys) << CI{"PEXPIREAT", CO::WRITE | CO::FAST, 3, 1, 1, 1}.HFUNC(PexpireAt) << CI{"RENAME", CO::WRITE, 3, 1, 2, 1}.HFUNC(Rename) diff --git a/src/server/generic_family.h b/src/server/generic_family.h index a2c9e1a55..37d009492 100644 --- a/src/server/generic_family.h +++ b/src/server/generic_family.h @@ -46,6 +46,7 @@ class GenericFamily { static void Exists(CmdArgList args, ConnectionContext* cntx); static void Expire(CmdArgList args, ConnectionContext* cntx); static void ExpireAt(CmdArgList args, ConnectionContext* cntx); + static void Persist(CmdArgList args, ConnectionContext* cntx); static void Keys(CmdArgList args, ConnectionContext* cntx); static void PexpireAt(CmdArgList args, ConnectionContext* cntx); static void Stick(CmdArgList args, ConnectionContext* cntx); diff --git a/src/server/generic_family_test.cc b/src/server/generic_family_test.cc index f2348d8f9..91f08d21b 100644 --- a/src/server/generic_family_test.cc +++ b/src/server/generic_family_test.cc @@ -282,21 +282,28 @@ TEST_F(GenericFamilyTest, Sort) { // numeric ASSERT_THAT(Run({"sort", "list-1"}).GetVec(), ElementsAre("1.2", "2.20", "3.5", "10.1", "200")); // string - ASSERT_THAT(Run({"sort", "list-1", "ALPHA"}).GetVec(), ElementsAre("1.2", "10.1", "2.20", "200", "3.5")); + ASSERT_THAT(Run({"sort", "list-1", "ALPHA"}).GetVec(), + ElementsAre("1.2", "10.1", "2.20", "200", "3.5")); // desc numeric - ASSERT_THAT(Run({"sort", "list-1", "DESC"}).GetVec(), ElementsAre("200", "10.1", "3.5", "2.20", "1.2")); + ASSERT_THAT(Run({"sort", "list-1", "DESC"}).GetVec(), + ElementsAre("200", "10.1", "3.5", "2.20", "1.2")); // desc strig - ASSERT_THAT(Run({"sort", "list-1", "DESC", "ALPHA"}).GetVec(), ElementsAre("3.5", "200", "2.20", "10.1", "1.2")); + ASSERT_THAT(Run({"sort", "list-1", "DESC", "ALPHA"}).GetVec(), + ElementsAre("3.5", "200", "2.20", "10.1", "1.2")); // limits - ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "0", "5"}).GetVec(), ElementsAre("1.2", "2.20", "3.5", "10.1", "200")); - ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "0", "10"}).GetVec(), ElementsAre("1.2", "2.20", "3.5", "10.1", "200")); + ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "0", "5"}).GetVec(), + ElementsAre("1.2", "2.20", "3.5", "10.1", "200")); + ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "0", "10"}).GetVec(), + ElementsAre("1.2", "2.20", "3.5", "10.1", "200")); ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "2", "2"}).GetVec(), ElementsAre("3.5", "10.1")); ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "1", "1"}), "2.20"); ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "4", "2"}), "200"); ASSERT_THAT(Run({"sort", "list-1", "LIMIT", "5", "2"}), ArrLen(0)); // limits desc - ASSERT_THAT(Run({"sort", "list-1", "DESC", "LIMIT", "0", "5"}).GetVec(), ElementsAre("200", "10.1", "3.5", "2.20", "1.2")); - ASSERT_THAT(Run({"sort", "list-1", "DESC", "LIMIT", "2", "2"}).GetVec(), ElementsAre("3.5", "2.20")); + ASSERT_THAT(Run({"sort", "list-1", "DESC", "LIMIT", "0", "5"}).GetVec(), + ElementsAre("200", "10.1", "3.5", "2.20", "1.2")); + ASSERT_THAT(Run({"sort", "list-1", "DESC", "LIMIT", "2", "2"}).GetVec(), + ElementsAre("3.5", "2.20")); ASSERT_THAT(Run({"sort", "list-1", "DESC", "LIMIT", "1", "1"}), "10.1"); ASSERT_THAT(Run({"sort", "list-1", "DESC", "LIMIT", "5", "2"}), ArrLen(0)); @@ -304,9 +311,12 @@ TEST_F(GenericFamilyTest, Sort) { Run({"del", "set-1"}); Run({"sadd", "set-1", "5.3", "4.4", "60", "99.9", "100", "9"}); ASSERT_THAT(Run({"sort", "set-1"}).GetVec(), ElementsAre("4.4", "5.3", "9", "60", "99.9", "100")); - ASSERT_THAT(Run({"sort", "set-1", "ALPHA"}).GetVec(), ElementsAre("100", "4.4", "5.3", "60", "9", "99.9")); - ASSERT_THAT(Run({"sort", "set-1", "DESC"}).GetVec(), ElementsAre("100", "99.9", "60", "9", "5.3", "4.4")); - ASSERT_THAT(Run({"sort", "set-1", "DESC", "ALPHA"}).GetVec(), ElementsAre("99.9", "9", "60", "5.3", "4.4", "100")); + ASSERT_THAT(Run({"sort", "set-1", "ALPHA"}).GetVec(), + ElementsAre("100", "4.4", "5.3", "60", "9", "99.9")); + ASSERT_THAT(Run({"sort", "set-1", "DESC"}).GetVec(), + ElementsAre("100", "99.9", "60", "9", "5.3", "4.4")); + ASSERT_THAT(Run({"sort", "set-1", "DESC", "ALPHA"}).GetVec(), + ElementsAre("99.9", "9", "60", "5.3", "4.4", "100")); // Test intset sort Run({"del", "intset-1"}); @@ -354,4 +364,16 @@ TEST_F(GenericFamilyTest, Time) { } } +TEST_F(GenericFamilyTest, Persist) { + auto resp = Run({"set", "mykey", "somevalue"}); + EXPECT_EQ(resp, "OK"); + // Key without expiration time - return 1 + EXPECT_EQ(1, CheckedInt({"persist", "mykey"})); + // set expiration time and try again + resp = Run({"EXPIRE", "mykey", "10"}); + EXPECT_EQ(10, CheckedInt({"TTL", "mykey"})); + EXPECT_EQ(1, CheckedInt({"persist", "mykey"})); + EXPECT_EQ(-1, CheckedInt({"TTL", "mykey"})); +} + } // namespace dfly