robustness fixes plus improve support for auxillary commands so we could run tcl tests.

GETRANGE - fix out of bounds bug.
Add a decorator for "config get"
Add a decorator for "function flush"
This commit is contained in:
Roman Gershman 2022-04-03 22:55:53 +03:00
parent 579ba3149b
commit d5cea3f5f3
8 changed files with 47 additions and 9 deletions

View File

@ -223,7 +223,6 @@ API 2.0
- [ ] SCRIPT DEBUG/KILL/FLUSH/EXISTS - [ ] SCRIPT DEBUG/KILL/FLUSH/EXISTS
- [X] Set Family - [X] Set Family
- [X] SSCAN - [X] SSCAN
- [X] SMISMEMBER
- [X] Sorted Set Family - [X] Sorted Set Family
- [ ] ZCOUNT - [ ] ZCOUNT
- [ ] ZINTERSTORE - [ ] ZINTERSTORE
@ -258,6 +257,7 @@ exact use-cases for this API.
- [X] ROLE (2.8) decorator for for master withour replicas - [X] ROLE (2.8) decorator for for master withour replicas
- [X] UNLINK (4.0) decorator for DEL command - [X] UNLINK (4.0) decorator for DEL command
- [X] BGSAVE - [X] BGSAVE
- [X] FUNCTION FLUSH
## Milestone Nymph ## Milestone Nymph
API 2,3,4 without cluster support, without modules, without memory inspection commands. API 2,3,4 without cluster support, without modules, without memory inspection commands.
Without support for keyspace notifications. Without support for keyspace notifications.

12
doc/differences.md Normal file
View File

@ -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%.

View File

@ -937,6 +937,20 @@ void Service::Unsubscribe(CmdArgList args, ConnectionContext* cntx) {
cntx->ChangeSubscription(false, true, std::move(args)); 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 Service::GetVarzStats() {
VarzValue::Map res; VarzValue::Map res;
@ -963,13 +977,14 @@ void Service::RegisterCommands() {
registry_ << CI{"QUIT", CO::READONLY | CO::FAST, 1, 0, 0, 0}.HFUNC(Quit) 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{"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{"EVAL", CO::NOSCRIPT, -3, 0, 0, 0}.MFUNC(Eval).SetValidator(&EvalValidator)
<< CI{"EVALSHA", CO::NOSCRIPT, -3, 0, 0, 0}.MFUNC(EvalSha).SetValidator(&EvalValidator) << CI{"EVALSHA", CO::NOSCRIPT, -3, 0, 0, 0}.MFUNC(EvalSha).SetValidator(&EvalValidator)
<< CI{"EXEC", kExecMask, 1, 0, 0, 0}.MFUNC(Exec) << CI{"EXEC", kExecMask, 1, 0, 0, 0}.MFUNC(Exec)
<< CI{"PUBLISH", CO::LOADING | CO::FAST, 3, 0, 0, 0}.MFUNC(Publish) << 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{"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(&registry_); StringFamily::Register(&registry_);
GenericFamily::Register(&registry_); GenericFamily::Register(&registry_);

View File

@ -78,6 +78,7 @@ class Service : public facade::ServiceInterface {
void Publish(CmdArgList args, ConnectionContext* cntx); void Publish(CmdArgList args, ConnectionContext* cntx);
void Subscribe(CmdArgList args, ConnectionContext* cntx); void Subscribe(CmdArgList args, ConnectionContext* cntx);
void Unsubscribe(CmdArgList args, ConnectionContext* cntx); void Unsubscribe(CmdArgList args, ConnectionContext* cntx);
void Function(CmdArgList args, ConnectionContext* cntx);
struct EvalArgs { struct EvalArgs {
std::string_view sha; // only one of them is defined. std::string_view sha; // only one of them is defined.

View File

@ -245,6 +245,11 @@ void ServerFamily::Config(CmdArgList args, ConnectionContext* cntx) {
if (sub_cmd == "SET") { if (sub_cmd == "SET") {
return (*cntx)->SendOk(); 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 { } else {
string err = absl::StrCat("Unknown subcommand or wrong number of arguments for '", sub_cmd, string err = absl::StrCat("Unknown subcommand or wrong number of arguments for '", sub_cmd,
"'. Try CONFIG HELP."); "'. Try CONFIG HELP.");

View File

@ -64,6 +64,9 @@ TEST_F(SetFamilyTest, SDiff) {
resp = Run({"sdiffstore", "a", "b", "c"}); resp = Run({"sdiffstore", "a", "b", "c"});
EXPECT_THAT(resp[0], IntArg(3)); 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", "bar", "x", "a", "b", "c"});
Run({"sadd", "foo", "c"}); Run({"sadd", "foo", "c"});
Run({"sadd", "car", "a", "d"}); Run({"sadd", "car", "a", "d"});

View File

@ -514,26 +514,26 @@ void StringFamily::GetRange(CmdArgList args, ConnectionContext* cntx) {
if (!it_res.ok()) if (!it_res.ok())
return it_res.status(); return it_res.status();
if (start < 0 && end < 0 && start > end) {
return OpStatus::OK;
}
const CompactObj& co = it_res.value()->second; const CompactObj& co = it_res.value()->second;
size_t strlen = co.Size(); size_t strlen = co.Size();
if (start < 0) if (start < 0)
start = strlen + start; start = strlen + start;
else if (size_t(start) >= strlen)
return OpStatus::OK;
if (end < 0) if (end < 0)
end = strlen + end; end = strlen + end;
if (strlen == 0 || start > end || size_t(start) >= strlen) {
return OpStatus::OK;
}
if (start < 0) if (start < 0)
start = 0; start = 0;
if (end < 0) if (end < 0)
end = 0; end = 0;
if (size_t(end) >= strlen) if (size_t(end) >= strlen)
end = strlen - 1; end = strlen - 1;
string tmp; string tmp;
string_view slice = co.GetSlice(&tmp); string_view slice = co.GetSlice(&tmp);

View File

@ -267,6 +267,8 @@ TEST_F(StringFamilyTest, SetEx) {
TEST_F(StringFamilyTest, Range) { TEST_F(StringFamilyTest, Range) {
Run({"set", "key1", "Hello World"}); Run({"set", "key1", "Hello World"});
EXPECT_THAT(Run({"getrange", "key1", "5", "3"}), RespEq(""));
Run({"SETRANGE", "key1", "6", "Earth"}); Run({"SETRANGE", "key1", "6", "Earth"});
EXPECT_THAT(Run({"get", "key1"}), RespEq("Hello Earth")); EXPECT_THAT(Run({"get", "key1"}), RespEq("Hello Earth"));