diff --git a/README.md b/README.md index 045cff4..d7afbec 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,6 @@ API 2.0 - [ ] SCRIPT DEBUG/KILL/FLUSH/EXISTS - [X] Set Family - [X] SSCAN - - [X] SMISMEMBER - [X] Sorted Set Family - [ ] ZCOUNT - [ ] ZINTERSTORE @@ -258,6 +257,7 @@ exact use-cases for this API. - [X] ROLE (2.8) decorator for for master withour replicas - [X] UNLINK (4.0) decorator for DEL command - [X] BGSAVE + - [X] FUNCTION FLUSH ## Milestone Nymph API 2,3,4 without cluster support, without modules, without memory inspection commands. Without support for keyspace notifications. diff --git a/doc/differences.md b/doc/differences.md new file mode 100644 index 0000000..6372663 --- /dev/null +++ b/doc/differences.md @@ -0,0 +1,12 @@ +# Differences with Redis + +## String lengths, indices. + +String sizes are limited to 256MB. +Indices (say in GETRANGE and SETRANGE commands) should be signed 32 bit integers in range +[-2147483647, 2147483648]. + +## Expiry ranges. +Expirations are limited to 365 days. For commands with millisecond precision like PEXPIRE or PSETEX, +expirations greater than 2^25ms are quietly rounded to the nearest second loosing precision of ~0.002%. + diff --git a/src/server/main_service.cc b/src/server/main_service.cc index c6b2a8a..2824ee6 100644 --- a/src/server/main_service.cc +++ b/src/server/main_service.cc @@ -937,6 +937,20 @@ void Service::Unsubscribe(CmdArgList args, ConnectionContext* cntx) { cntx->ChangeSubscription(false, true, std::move(args)); } +// Not a real implementation. Serves as a decorator to accept some function commands +// for testing. +void Service::Function(CmdArgList args, ConnectionContext* cntx) { + ToUpper(&args[1]); + string_view sub_cmd = ArgS(args, 1); + + if (sub_cmd == "FLUSH") { + return (*cntx)->SendOk(); + } + + string err = absl::StrCat("Unknown subcommand '", sub_cmd, "'. Try FUNCTION HELP."); + return (*cntx)->SendError(err, kSyntaxErr); +} + VarzValue::Map Service::GetVarzStats() { VarzValue::Map res; @@ -963,13 +977,14 @@ void Service::RegisterCommands() { registry_ << CI{"QUIT", CO::READONLY | CO::FAST, 1, 0, 0, 0}.HFUNC(Quit) << CI{"MULTI", CO::NOSCRIPT | CO::FAST | CO::LOADING, 1, 0, 0, 0}.HFUNC(Multi) - << CI{"DISCARD", CO::NOSCRIPT | CO::FAST| CO::LOADING, 1, 0, 0, 0}.MFUNC(Discard) + << CI{"DISCARD", CO::NOSCRIPT | CO::FAST | CO::LOADING, 1, 0, 0, 0}.MFUNC(Discard) << CI{"EVAL", CO::NOSCRIPT, -3, 0, 0, 0}.MFUNC(Eval).SetValidator(&EvalValidator) << CI{"EVALSHA", CO::NOSCRIPT, -3, 0, 0, 0}.MFUNC(EvalSha).SetValidator(&EvalValidator) << CI{"EXEC", kExecMask, 1, 0, 0, 0}.MFUNC(Exec) << CI{"PUBLISH", CO::LOADING | CO::FAST, 3, 0, 0, 0}.MFUNC(Publish) << CI{"SUBSCRIBE", CO::NOSCRIPT | CO::LOADING, -2, 0, 0, 0}.MFUNC(Subscribe) - << CI{"UNSUBSCRIBE", CO::NOSCRIPT | CO::LOADING, -2, 0, 0, 0}.MFUNC(Unsubscribe); + << CI{"UNSUBSCRIBE", CO::NOSCRIPT | CO::LOADING, -2, 0, 0, 0}.MFUNC(Unsubscribe) + << CI{"FUNCTION", CO::NOSCRIPT, 2, 0, 0, 0}.MFUNC(Function); StringFamily::Register(®istry_); GenericFamily::Register(®istry_); diff --git a/src/server/main_service.h b/src/server/main_service.h index 1d083bf..f02caf8 100644 --- a/src/server/main_service.h +++ b/src/server/main_service.h @@ -78,6 +78,7 @@ class Service : public facade::ServiceInterface { void Publish(CmdArgList args, ConnectionContext* cntx); void Subscribe(CmdArgList args, ConnectionContext* cntx); void Unsubscribe(CmdArgList args, ConnectionContext* cntx); + void Function(CmdArgList args, ConnectionContext* cntx); struct EvalArgs { std::string_view sha; // only one of them is defined. diff --git a/src/server/server_family.cc b/src/server/server_family.cc index 5287b50..875f9a6 100644 --- a/src/server/server_family.cc +++ b/src/server/server_family.cc @@ -245,6 +245,11 @@ void ServerFamily::Config(CmdArgList args, ConnectionContext* cntx) { if (sub_cmd == "SET") { return (*cntx)->SendOk(); + } else if (sub_cmd == "GET" && args.size() == 3) { + string_view param = ArgS(args, 2); + string_view res[2] = {param, "tbd"}; + + return (*cntx)->SendStringArr(res); } else { string err = absl::StrCat("Unknown subcommand or wrong number of arguments for '", sub_cmd, "'. Try CONFIG HELP."); diff --git a/src/server/set_family_test.cc b/src/server/set_family_test.cc index c2aeffc..953b42e 100644 --- a/src/server/set_family_test.cc +++ b/src/server/set_family_test.cc @@ -64,6 +64,9 @@ TEST_F(SetFamilyTest, SDiff) { resp = Run({"sdiffstore", "a", "b", "c"}); EXPECT_THAT(resp[0], IntArg(3)); + Run({"set", "str", "foo"}); + EXPECT_THAT(Run({"sdiff", "b", "str"}), ElementsAre(ErrArg("WRONGTYPE "))); + Run({"sadd", "bar", "x", "a", "b", "c"}); Run({"sadd", "foo", "c"}); Run({"sadd", "car", "a", "d"}); diff --git a/src/server/string_family.cc b/src/server/string_family.cc index 0c0f68c..a507cba 100644 --- a/src/server/string_family.cc +++ b/src/server/string_family.cc @@ -514,26 +514,26 @@ void StringFamily::GetRange(CmdArgList args, ConnectionContext* cntx) { if (!it_res.ok()) return it_res.status(); - if (start < 0 && end < 0 && start > end) { - return OpStatus::OK; - } const CompactObj& co = it_res.value()->second; size_t strlen = co.Size(); if (start < 0) start = strlen + start; - else if (size_t(start) >= strlen) - return OpStatus::OK; - if (end < 0) end = strlen + end; + if (strlen == 0 || start > end || size_t(start) >= strlen) { + return OpStatus::OK; + } + if (start < 0) start = 0; if (end < 0) end = 0; + if (size_t(end) >= strlen) end = strlen - 1; + string tmp; string_view slice = co.GetSlice(&tmp); diff --git a/src/server/string_family_test.cc b/src/server/string_family_test.cc index 7adae0d..db4108a 100644 --- a/src/server/string_family_test.cc +++ b/src/server/string_family_test.cc @@ -267,6 +267,8 @@ TEST_F(StringFamilyTest, SetEx) { TEST_F(StringFamilyTest, Range) { Run({"set", "key1", "Hello World"}); + EXPECT_THAT(Run({"getrange", "key1", "5", "3"}), RespEq("")); + Run({"SETRANGE", "key1", "6", "Earth"}); EXPECT_THAT(Run({"get", "key1"}), RespEq("Hello Earth"));