1
0
Fork 0
mirror of https://github.com/dragonflydb/dragonfly.git synced 2024-12-14 11:58:02 +00:00

fix: compatibility around list,string and sort commands (#3568)

1. Fix corner cases around non existing keys
2. Fix matching logic for * glob, as well as '' glob.
3. Improve SORT option parsing.

Signed-off-by: Roman Gershman <roman@dragonflydb.io>
This commit is contained in:
Roman Gershman 2024-08-25 16:30:55 +03:00 committed by GitHub
parent 067fdd83b9
commit 20b8817148
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 43 additions and 11 deletions

2
.gitignore vendored
View file

@ -17,5 +17,5 @@ _deps
releases
.DS_Store
.idea/*
.hypothesis
.secrets

View file

@ -291,9 +291,9 @@ OpResult<ScanOpts> ScanOpts::TryFrom(CmdArgList args) {
else if (scan_opts.limit > 4096)
scan_opts.limit = 4096;
} else if (opt == "MATCH") {
scan_opts.pattern = ArgS(args, i + 1);
if (scan_opts.pattern == "*")
scan_opts.pattern = string_view{};
string_view pattern = ArgS(args, i + 1);
if (pattern != "*")
scan_opts.pattern = pattern;
} else if (opt == "TYPE") {
auto obj_type = ObjTypeFromString(ArgS(args, i + 1));
if (!obj_type) {
@ -312,9 +312,9 @@ OpResult<ScanOpts> ScanOpts::TryFrom(CmdArgList args) {
}
bool ScanOpts::Matches(std::string_view val_name) const {
if (pattern.empty())
if (!pattern)
return true;
return stringmatchlen(pattern.data(), pattern.size(), val_name.data(), val_name.size(), 0) == 1;
return stringmatchlen(pattern->data(), pattern->size(), val_name.data(), val_name.size(), 0) == 1;
}
GenericError::operator std::error_code() const {

View file

@ -295,7 +295,7 @@ class Context : protected Cancellation {
};
struct ScanOpts {
std::string_view pattern;
std::optional<std::string_view> pattern;
size_t limit = 10;
std::optional<CompactObjType> type_filter;
unsigned bucket_id = UINT_MAX;

View file

@ -907,7 +907,10 @@ void GenericFamily::Keys(CmdArgList args, ConnectionContext* cntx) {
StringVec keys;
ScanOpts scan_opts;
scan_opts.pattern = pattern;
if (pattern != "*") {
scan_opts.pattern = pattern;
}
scan_opts.limit = 512;
auto output_limit = absl::GetFlag(FLAGS_keys_output_limit);
@ -1091,9 +1094,12 @@ OpResultTyped<SortEntryList> OpFetchSortEntries(const OpArgs& op_args, std::stri
using namespace container_utils;
auto it = op_args.GetDbSlice().FindReadOnly(op_args.db_cntx, key).it;
if (!IsValid(it) || !IsContainer(it->second)) {
if (!IsValid(it)) {
return OpStatus::KEY_NOTFOUND;
}
if (!IsContainer(it->second)) {
return OpStatus::WRONG_TYPE;
}
auto result = MakeSortEntryList(alpha);
bool success = std::visit(
@ -1106,7 +1112,7 @@ OpResultTyped<SortEntryList> OpFetchSortEntries(const OpArgs& op_args, std::stri
result);
auto res = OpResultTyped{std::move(result)};
res.setType(it->second.ObjType());
return success ? res : OpStatus::WRONG_TYPE;
return success ? res : OpStatus::INVALID_NUMERIC_RESULT;
}
void GenericFamily::Sort(CmdArgList args, ConnectionContext* cntx) {
@ -1123,6 +1129,8 @@ void GenericFamily::Sort(CmdArgList args, ConnectionContext* cntx) {
alpha = true;
} else if (arg == "DESC") {
reversed = true;
} else if (arg == "ASC") {
reversed = false;
} else if (arg == "LIMIT") {
int offset, limit;
if (i + 2 >= args.size()) {
@ -1134,6 +1142,9 @@ void GenericFamily::Sort(CmdArgList args, ConnectionContext* cntx) {
}
bounds = {offset, limit};
i += 2;
} else {
LOG_EVERY_T(ERROR, 1) << "Unsupported option " << arg;
return cntx->SendError(kSyntaxErr, kSyntaxErrType);
}
}
@ -1142,7 +1153,10 @@ void GenericFamily::Sort(CmdArgList args, ConnectionContext* cntx) {
return OpFetchSortEntries(t->GetOpArgs(shard), key, alpha);
});
if (fetch_result.status() == OpStatus::WRONG_TYPE)
if (fetch_result == OpStatus::WRONG_TYPE)
return cntx->SendError(fetch_result.status());
if (fetch_result.status() == OpStatus::INVALID_NUMERIC_RESULT)
return cntx->SendError("One or more scores can't be converted into double");
auto* rb = static_cast<RedisReplyBuilder*>(cntx->reply_builder());

View file

@ -400,6 +400,15 @@ TEST_F(GenericFamilyTest, Scan) {
vec = StrArray(resp.GetVec()[1]);
EXPECT_EQ(10, vec.size());
EXPECT_THAT(vec, Each(StartsWith("zset")));
Run({"flushdb"});
Run({"set", "", "foo"});
Run({"set", "bar", "1"});
resp = Run({"keys", "*"});
EXPECT_THAT(resp, RespArray(ElementsAre("bar", "")));
resp = Run({"keys", ""});
EXPECT_EQ(resp, "");
}
TEST_F(GenericFamilyTest, Sort) {
@ -465,6 +474,10 @@ TEST_F(GenericFamilyTest, Sort) {
// Test not convertible to double
Run({"lpush", "list-2", "NOTADOUBLE"});
ASSERT_THAT(Run({"sort", "list-2"}), ErrArg("One or more scores can't be converted into double"));
Run({"set", "foo", "bar"});
ASSERT_THAT(Run({"sort", "foo"}), ErrArg("WRONGTYPE "));
;
}
TEST_F(GenericFamilyTest, TimeNoKeys) {

View file

@ -127,6 +127,7 @@ def test_sort_wrong_type(r: redis.Redis):
r.sort("string")
@pytest.mark.unsupported_server_types("dragonfly")
def test_sort_with_store_option(r: redis.Redis):
r.rpush("foo", "2")
r.rpush("foo", "1")
@ -137,6 +138,7 @@ def test_sort_with_store_option(r: redis.Redis):
assert r.lrange("bar", 0, -1) == [b"1", b"2", b"3", b"4"]
@pytest.mark.unsupported_server_types("dragonfly")
def test_sort_with_by_and_get_option(r: redis.Redis):
r.rpush("foo", "2")
r.rpush("foo", "1")
@ -186,6 +188,7 @@ def test_sort_with_by_and_get_option(r: redis.Redis):
]
@pytest.mark.unsupported_server_types("dragonfly")
def test_sort_with_hash(r: redis.Redis):
r.rpush("foo", "middle")
r.rpush("foo", "eldest")
@ -665,7 +668,9 @@ def test_key_patterns(r: redis.Redis):
assert sorted(r.keys()) == [b"four", b"one", b"three", b"two"]
# seems like a rather peculiar behavior of Redis, maybe a bug? Disabling for Dragonfly for now.
@pytest.mark.min_server("7")
@pytest.mark.unsupported_server_types("dragonfly")
def test_watch_when_setbit_does_not_change_value(r: redis.Redis):
r.set("foo", b"0")