PokIsemaine commented on code in PR #2262:
URL: https://github.com/apache/kvrocks/pull/2262#discussion_r1574206237
##########
src/storage/redis_db.h:
##########
@@ -23,13 +23,33 @@
#include <map>
#include <string>
#include <utility>
+#include <variant>
#include <vector>
#include "redis_metadata.h"
+#include "server/redis_reply.h"
#include "storage.h"
namespace redis {
+struct SortArgument {
+ std::string sortby; // BY
+ bool dontsort = false; // DONT SORT
+ int offset = 0; // LIMIT OFFSET
+ int count = -1; // LIMIT COUNT
+ std::vector<std::string> getpatterns; // GET
+ bool desc = false; // ASC/DESC
+ bool alpha = false; // ALPHA
+ std::string storekey; // STORE
+};
+
+struct RedisSortObject {
+ std::string obj;
+ std::variant<double, std::string> v;
+};
+
+bool SortCompare(const RedisSortObject &a, const RedisSortObject &b, const
SortArgument &args);
Review Comment:
> What does the bool return value in "SortCompare" means? Would you mind add
a doc string or change the function name?
This function is used to assist in implementing the third parameter `Compare
comp` of `std::sort`, so that `RedisSortObject` can be sorted based on
`SortArgument`
##########
src/commands/cmd_key.cc:
##########
@@ -424,6 +424,111 @@ class CommandCopy : public Commander {
bool replace_ = false;
};
+template <bool ReadOnly>
+class CommandSort : public Commander {
+ public:
+ Status Parse(const std::vector<std::string> &args) override {
+ CommandParser parser(args, 2);
+ while (parser.Good()) {
+ if (parser.EatEqICase("BY")) {
+ if (parser.Remains() < 1) {
+ return parser.InvalidSyntax();
+ }
+ sort_argument_.sortby = GET_OR_RET(parser.TakeStr());
+
+ if (sort_argument_.sortby.find('*') == std::string::npos) {
+ sort_argument_.dontsort = true;
+ } else {
+ /* TODO:
+ * If BY is specified with a real pattern, we can't accept it in
cluster mode,
+ * unless we can make sure the keys formed by the pattern are in the
same slot
+ * as the key to sort.
+ * If BY is specified with a real pattern, we can't accept
+ * it if no full ACL key access is applied for this command. */
+ }
+ } else if (parser.EatEqICase("LIMIT")) {
+ if (parser.Remains() < 2) {
+ return parser.InvalidSyntax();
+ }
+ sort_argument_.offset = GET_OR_RET(parser.template TakeInt<int>());
+ sort_argument_.count = GET_OR_RET(parser.template TakeInt<int>());
+ } else if (parser.EatEqICase("GET") && parser.Remains() >= 1) {
+ if (parser.Remains() < 1) {
+ return parser.InvalidSyntax();
+ }
+ /* TODO:
Review Comment:
Sorry, this was written by mistake
##########
src/commands/cmd_key.cc:
##########
@@ -424,6 +424,111 @@ class CommandCopy : public Commander {
bool replace_ = false;
};
+template <bool ReadOnly>
+class CommandSort : public Commander {
+ public:
+ Status Parse(const std::vector<std::string> &args) override {
+ CommandParser parser(args, 2);
+ while (parser.Good()) {
+ if (parser.EatEqICase("BY")) {
+ if (parser.Remains() < 1) {
+ return parser.InvalidSyntax();
+ }
+ sort_argument_.sortby = GET_OR_RET(parser.TakeStr());
+
+ if (sort_argument_.sortby.find('*') == std::string::npos) {
+ sort_argument_.dontsort = true;
+ } else {
+ /* TODO:
+ * If BY is specified with a real pattern, we can't accept it in
cluster mode,
+ * unless we can make sure the keys formed by the pattern are in the
same slot
+ * as the key to sort.
+ * If BY is specified with a real pattern, we can't accept
+ * it if no full ACL key access is applied for this command. */
Review Comment:
How to propose it specifically? This part actually has some checks.
https://github.com/redis/redis/blob/804110a487f048669aa9d9412e5789ec43f4fe39/src/sort.c#L218
Should we remind users in the code or in the kvrocks documentation?
##########
src/storage/redis_db.cc:
##########
@@ -777,4 +782,208 @@ rocksdb::Status Database::Copy(const std::string &key,
const std::string &new_ke
return storage_->Write(storage_->DefaultWriteOptions(),
batch->GetWriteBatch());
}
+std::string Database::lookupKeyByPattern(const std::string &pattern, const
std::string &subst) {
+ if (pattern == "#") {
+ return subst;
+ }
+
+ auto match_pos = pattern.find('*');
+ if (match_pos == std::string::npos) {
+ return "";
+ }
+
+ // hash field
+ std::string field;
+ auto arrow_pos = pattern.find("->", match_pos + 1);
+ if (arrow_pos != std::string::npos && arrow_pos + 2 < pattern.size()) {
+ field = pattern.substr(arrow_pos + 2);
+ }
+
+ std::string key = pattern.substr(0, match_pos + 1);
+ key.replace(match_pos, 1, subst);
+
+ std::string value;
+ if (!field.empty()) {
+ auto hash_db = redis::Hash(storage_, namespace_);
+ RedisType type = RedisType::kRedisNone;
+ if (auto s = hash_db.Type(key, &type); !s.ok() || type >=
RedisTypeNames.size()) {
+ return "";
+ }
+
+ hash_db.Get(key, field, &value);
+ } else {
+ auto string_db = redis::String(storage_, namespace_);
+ RedisType type = RedisType::kRedisNone;
+ if (auto s = string_db.Type(key, &type); !s.ok() || type >=
RedisTypeNames.size()) {
+ return "";
+ }
+ string_db.Get(key, &value);
+ }
+ return value;
+}
+
+rocksdb::Status Database::Sort(const RedisType &type, const std::string &key,
SortArgument &args, const RESP &version,
+ std::vector<std::string> *output_vec,
SortResult *res) {
+ /* When sorting a set with no sort specified, we must sort the output
+ * so the result is consistent across scripting and replication.
+ *
+ * The other types (list, sorted set) will retain their native order
+ * even if no sort order is requested, so they remain stable across
+ * scripting and replication.
+ *
+ * TODO: support CLIENT_SCRIPT flag, (!storekey_.empty() || c->flags &
CLIENT_SCRIPT)) */
+ if (args.dontsort && type == RedisType::kRedisSet &&
(!args.storekey.empty())) {
+ /* Force ALPHA sorting */
+ args.dontsort = false;
+ args.alpha = true;
+ args.sortby = "";
+ }
+
+ // Obtain the length of the object to sort.
+ const std::string ns_key = AppendNamespacePrefix(key);
+ Metadata metadata(type, false);
+ auto s = GetMetadata(GetOptions{}, {type}, ns_key, &metadata);
+ if (!s.ok()) {
+ return s;
+ }
+
+ int vectorlen = (int)metadata.size;
+
+ // Adjust the offset and count of the limit
+ int offset = args.offset >= vectorlen ? 0 : std::clamp(args.offset, 0,
vectorlen - 1);
+ int count = args.offset >= vectorlen ? 0 : std::clamp(args.count, -1,
vectorlen - offset);
+ if (count == -1) count = vectorlen - offset;
+
+ // Get the elements that need to be sorted
+ std::vector<std::string> str_vec;
+ if (count != 0) {
+ if (type == RedisType::kRedisList) {
+ auto list_db = redis::List(storage_, namespace_);
+
+ if (args.dontsort) {
+ if (args.desc) {
+ list_db.Range(key, -count - offset, -1 - offset, &str_vec);
+ std::reverse(str_vec.begin(), str_vec.end());
+ } else {
+ list_db.Range(key, offset, offset + count - 1, &str_vec);
+ }
+ } else {
+ list_db.Range(key, 0, -1, &str_vec);
+ }
+ } else if (type == RedisType::kRedisSet) {
+ auto set_db = redis::Set(storage_, namespace_);
+ set_db.Members(key, &str_vec);
+
+ if (args.dontsort) {
+ str_vec = std::vector(str_vec.begin() + offset, str_vec.begin() +
offset + count);
+ }
+ } else if (type == RedisType::kRedisZSet) {
+ auto zset_db = redis::ZSet(storage_, namespace_);
+ std::vector<MemberScore> member_scores;
+
+ if (args.dontsort) {
+ RangeRankSpec spec;
+ spec.start = offset;
+ spec.stop = offset + count - 1;
+ spec.reversed = args.desc;
+ zset_db.RangeByRank(key, spec, &member_scores, nullptr);
+
+ for (auto &member_score : member_scores) {
+ str_vec.emplace_back(member_score.member);
+ }
+ } else {
+ zset_db.GetAllMemberScores(key, &member_scores);
+
+ for (auto &member_score : member_scores) {
+ str_vec.emplace_back(member_score.member);
+ }
+ }
+ } else {
+ *res = SortResult::UNKNOWN_TYPE;
+ return s;
+ }
+ }
+
+ std::vector<RedisSortObject> sort_vec(str_vec.size());
+ for (size_t i = 0; i < str_vec.size(); ++i) {
+ sort_vec[i].obj = str_vec[i];
+ }
+
+ // Sort by BY, ALPHA, ASC/DESC
+ if (!args.dontsort) {
+ for (size_t i = 0; i < sort_vec.size(); ++i) {
+ std::string byval;
+ if (!args.sortby.empty()) {
+ byval = lookupKeyByPattern(args.sortby, str_vec[i]);
+ if (byval.empty()) continue;
+ } else {
+ byval = str_vec[i];
+ }
+
+ if (args.alpha) {
+ if (!args.sortby.empty()) {
+ sort_vec[i].v = byval;
+ }
+ } else {
+ try {
+ sort_vec[i].v = std::stod(byval);
Review Comment:
Can you please indicate the specific location? Thanks
##########
src/storage/redis_db.cc:
##########
@@ -777,4 +782,208 @@ rocksdb::Status Database::Copy(const std::string &key,
const std::string &new_ke
return storage_->Write(storage_->DefaultWriteOptions(),
batch->GetWriteBatch());
}
+std::string Database::lookupKeyByPattern(const std::string &pattern, const
std::string &subst) {
+ if (pattern == "#") {
+ return subst;
+ }
+
+ auto match_pos = pattern.find('*');
+ if (match_pos == std::string::npos) {
+ return "";
+ }
+
+ // hash field
+ std::string field;
+ auto arrow_pos = pattern.find("->", match_pos + 1);
+ if (arrow_pos != std::string::npos && arrow_pos + 2 < pattern.size()) {
+ field = pattern.substr(arrow_pos + 2);
+ }
+
+ std::string key = pattern.substr(0, match_pos + 1);
+ key.replace(match_pos, 1, subst);
+
+ std::string value;
+ if (!field.empty()) {
+ auto hash_db = redis::Hash(storage_, namespace_);
+ RedisType type = RedisType::kRedisNone;
+ if (auto s = hash_db.Type(key, &type); !s.ok() || type >=
RedisTypeNames.size()) {
+ return "";
+ }
+
+ hash_db.Get(key, field, &value);
+ } else {
+ auto string_db = redis::String(storage_, namespace_);
+ RedisType type = RedisType::kRedisNone;
+ if (auto s = string_db.Type(key, &type); !s.ok() || type >=
RedisTypeNames.size()) {
+ return "";
+ }
+ string_db.Get(key, &value);
+ }
+ return value;
+}
+
+rocksdb::Status Database::Sort(const RedisType &type, const std::string &key,
SortArgument &args, const RESP &version,
+ std::vector<std::string> *output_vec,
SortResult *res) {
+ /* When sorting a set with no sort specified, we must sort the output
+ * so the result is consistent across scripting and replication.
+ *
+ * The other types (list, sorted set) will retain their native order
+ * even if no sort order is requested, so they remain stable across
+ * scripting and replication.
+ *
+ * TODO: support CLIENT_SCRIPT flag, (!storekey_.empty() || c->flags &
CLIENT_SCRIPT)) */
+ if (args.dontsort && type == RedisType::kRedisSet &&
(!args.storekey.empty())) {
+ /* Force ALPHA sorting */
+ args.dontsort = false;
+ args.alpha = true;
+ args.sortby = "";
+ }
+
+ // Obtain the length of the object to sort.
+ const std::string ns_key = AppendNamespacePrefix(key);
+ Metadata metadata(type, false);
+ auto s = GetMetadata(GetOptions{}, {type}, ns_key, &metadata);
+ if (!s.ok()) {
+ return s;
+ }
+
+ int vectorlen = (int)metadata.size;
+
+ // Adjust the offset and count of the limit
+ int offset = args.offset >= vectorlen ? 0 : std::clamp(args.offset, 0,
vectorlen - 1);
+ int count = args.offset >= vectorlen ? 0 : std::clamp(args.count, -1,
vectorlen - offset);
+ if (count == -1) count = vectorlen - offset;
+
+ // Get the elements that need to be sorted
+ std::vector<std::string> str_vec;
+ if (count != 0) {
+ if (type == RedisType::kRedisList) {
+ auto list_db = redis::List(storage_, namespace_);
+
+ if (args.dontsort) {
+ if (args.desc) {
+ list_db.Range(key, -count - offset, -1 - offset, &str_vec);
+ std::reverse(str_vec.begin(), str_vec.end());
+ } else {
+ list_db.Range(key, offset, offset + count - 1, &str_vec);
+ }
+ } else {
+ list_db.Range(key, 0, -1, &str_vec);
+ }
+ } else if (type == RedisType::kRedisSet) {
+ auto set_db = redis::Set(storage_, namespace_);
+ set_db.Members(key, &str_vec);
+
+ if (args.dontsort) {
+ str_vec = std::vector(str_vec.begin() + offset, str_vec.begin() +
offset + count);
+ }
+ } else if (type == RedisType::kRedisZSet) {
+ auto zset_db = redis::ZSet(storage_, namespace_);
+ std::vector<MemberScore> member_scores;
+
+ if (args.dontsort) {
+ RangeRankSpec spec;
+ spec.start = offset;
+ spec.stop = offset + count - 1;
+ spec.reversed = args.desc;
+ zset_db.RangeByRank(key, spec, &member_scores, nullptr);
+
+ for (auto &member_score : member_scores) {
+ str_vec.emplace_back(member_score.member);
+ }
+ } else {
+ zset_db.GetAllMemberScores(key, &member_scores);
+
+ for (auto &member_score : member_scores) {
+ str_vec.emplace_back(member_score.member);
+ }
+ }
+ } else {
+ *res = SortResult::UNKNOWN_TYPE;
+ return s;
+ }
+ }
+
+ std::vector<RedisSortObject> sort_vec(str_vec.size());
+ for (size_t i = 0; i < str_vec.size(); ++i) {
+ sort_vec[i].obj = str_vec[i];
+ }
+
+ // Sort by BY, ALPHA, ASC/DESC
+ if (!args.dontsort) {
+ for (size_t i = 0; i < sort_vec.size(); ++i) {
+ std::string byval;
+ if (!args.sortby.empty()) {
+ byval = lookupKeyByPattern(args.sortby, str_vec[i]);
+ if (byval.empty()) continue;
+ } else {
+ byval = str_vec[i];
+ }
+
+ if (args.alpha) {
+ if (!args.sortby.empty()) {
+ sort_vec[i].v = byval;
+ }
+ } else {
+ try {
+ sort_vec[i].v = std::stod(byval);
+ } catch (const std::exception &e) {
+ *res = SortResult::DOUBLE_CONVERT_ERROR;
+ return rocksdb::Status::OK();
+ }
+ }
+ }
+
+ std::sort(sort_vec.begin(), sort_vec.end(),
+ [args](const RedisSortObject &a, const RedisSortObject &b) {
return SortCompare(a, b, args); });
+
+ // Gets the element specified by Limit
+ if (offset != 0 || count != vectorlen) {
+ sort_vec = std::vector(sort_vec.begin() + offset, sort_vec.begin() +
offset + count);
+ }
+ }
+
+ // Get the output
+ for (auto &elem : sort_vec) {
+ if (args.getpatterns.empty()) {
+ output_vec->emplace_back(elem.obj);
+ }
+ for (const std::string &pattern : args.getpatterns) {
+ std::string val = lookupKeyByPattern(pattern, elem.obj);
+ if (val.empty()) {
+ output_vec->emplace_back(redis::NilString(version));
+ } else {
+ output_vec->emplace_back(val);
+ }
+ }
+ }
Review Comment:
Do subsequent store operations also need to be placed in `Execute`? It
relies on `output_vec` while operating on rocksdb
```
Perform storage
if (!args.storekey.empty()) {
redis::List list_db(storage_, namespace_);
std::vector<Slice> elems(output_vec->begin(), output_vec->end());
list_db.Trim(args.storekey, 0, -1);
uint64_t new_size = 0;
list_db.Push(args.storekey, elems, false, &new_size);
}
```
Maybe we can replace a variable name instead of using `output_vec`. What do
you think is better?
##########
src/storage/redis_db.cc:
##########
@@ -777,4 +782,208 @@ rocksdb::Status Database::Copy(const std::string &key,
const std::string &new_ke
return storage_->Write(storage_->DefaultWriteOptions(),
batch->GetWriteBatch());
}
+std::string Database::lookupKeyByPattern(const std::string &pattern, const
std::string &subst) {
+ if (pattern == "#") {
+ return subst;
+ }
+
+ auto match_pos = pattern.find('*');
+ if (match_pos == std::string::npos) {
+ return "";
+ }
+
+ // hash field
+ std::string field;
+ auto arrow_pos = pattern.find("->", match_pos + 1);
+ if (arrow_pos != std::string::npos && arrow_pos + 2 < pattern.size()) {
+ field = pattern.substr(arrow_pos + 2);
+ }
+
+ std::string key = pattern.substr(0, match_pos + 1);
+ key.replace(match_pos, 1, subst);
+
+ std::string value;
+ if (!field.empty()) {
+ auto hash_db = redis::Hash(storage_, namespace_);
+ RedisType type = RedisType::kRedisNone;
+ if (auto s = hash_db.Type(key, &type); !s.ok() || type >=
RedisTypeNames.size()) {
+ return "";
+ }
+
+ hash_db.Get(key, field, &value);
+ } else {
+ auto string_db = redis::String(storage_, namespace_);
+ RedisType type = RedisType::kRedisNone;
+ if (auto s = string_db.Type(key, &type); !s.ok() || type >=
RedisTypeNames.size()) {
+ return "";
+ }
+ string_db.Get(key, &value);
+ }
+ return value;
+}
+
+rocksdb::Status Database::Sort(const RedisType &type, const std::string &key,
SortArgument &args, const RESP &version,
+ std::vector<std::string> *output_vec,
SortResult *res) {
+ /* When sorting a set with no sort specified, we must sort the output
+ * so the result is consistent across scripting and replication.
+ *
+ * The other types (list, sorted set) will retain their native order
+ * even if no sort order is requested, so they remain stable across
+ * scripting and replication.
+ *
+ * TODO: support CLIENT_SCRIPT flag, (!storekey_.empty() || c->flags &
CLIENT_SCRIPT)) */
+ if (args.dontsort && type == RedisType::kRedisSet &&
(!args.storekey.empty())) {
+ /* Force ALPHA sorting */
+ args.dontsort = false;
+ args.alpha = true;
+ args.sortby = "";
+ }
+
+ // Obtain the length of the object to sort.
+ const std::string ns_key = AppendNamespacePrefix(key);
+ Metadata metadata(type, false);
+ auto s = GetMetadata(GetOptions{}, {type}, ns_key, &metadata);
+ if (!s.ok()) {
+ return s;
+ }
+
+ int vectorlen = (int)metadata.size;
+
+ // Adjust the offset and count of the limit
+ int offset = args.offset >= vectorlen ? 0 : std::clamp(args.offset, 0,
vectorlen - 1);
+ int count = args.offset >= vectorlen ? 0 : std::clamp(args.count, -1,
vectorlen - offset);
+ if (count == -1) count = vectorlen - offset;
+
+ // Get the elements that need to be sorted
+ std::vector<std::string> str_vec;
+ if (count != 0) {
+ if (type == RedisType::kRedisList) {
+ auto list_db = redis::List(storage_, namespace_);
+
+ if (args.dontsort) {
+ if (args.desc) {
+ list_db.Range(key, -count - offset, -1 - offset, &str_vec);
+ std::reverse(str_vec.begin(), str_vec.end());
+ } else {
+ list_db.Range(key, offset, offset + count - 1, &str_vec);
+ }
+ } else {
+ list_db.Range(key, 0, -1, &str_vec);
+ }
+ } else if (type == RedisType::kRedisSet) {
+ auto set_db = redis::Set(storage_, namespace_);
+ set_db.Members(key, &str_vec);
+
+ if (args.dontsort) {
+ str_vec = std::vector(str_vec.begin() + offset, str_vec.begin() +
offset + count);
+ }
+ } else if (type == RedisType::kRedisZSet) {
+ auto zset_db = redis::ZSet(storage_, namespace_);
+ std::vector<MemberScore> member_scores;
+
+ if (args.dontsort) {
+ RangeRankSpec spec;
+ spec.start = offset;
+ spec.stop = offset + count - 1;
+ spec.reversed = args.desc;
+ zset_db.RangeByRank(key, spec, &member_scores, nullptr);
+
+ for (auto &member_score : member_scores) {
+ str_vec.emplace_back(member_score.member);
+ }
+ } else {
+ zset_db.GetAllMemberScores(key, &member_scores);
+
+ for (auto &member_score : member_scores) {
+ str_vec.emplace_back(member_score.member);
Review Comment:
Like this?
```
for (auto &member_score : member_scores) {
str_vec.emplace_back(std::move(member_score.member));
}
```
##########
src/storage/redis_db.cc:
##########
@@ -777,4 +782,208 @@ rocksdb::Status Database::Copy(const std::string &key,
const std::string &new_ke
return storage_->Write(storage_->DefaultWriteOptions(),
batch->GetWriteBatch());
}
+std::string Database::lookupKeyByPattern(const std::string &pattern, const
std::string &subst) {
+ if (pattern == "#") {
+ return subst;
+ }
+
+ auto match_pos = pattern.find('*');
+ if (match_pos == std::string::npos) {
+ return "";
+ }
+
+ // hash field
+ std::string field;
+ auto arrow_pos = pattern.find("->", match_pos + 1);
+ if (arrow_pos != std::string::npos && arrow_pos + 2 < pattern.size()) {
+ field = pattern.substr(arrow_pos + 2);
+ }
+
+ std::string key = pattern.substr(0, match_pos + 1);
+ key.replace(match_pos, 1, subst);
+
+ std::string value;
+ if (!field.empty()) {
+ auto hash_db = redis::Hash(storage_, namespace_);
+ RedisType type = RedisType::kRedisNone;
+ if (auto s = hash_db.Type(key, &type); !s.ok() || type >=
RedisTypeNames.size()) {
+ return "";
+ }
+
+ hash_db.Get(key, field, &value);
+ } else {
+ auto string_db = redis::String(storage_, namespace_);
+ RedisType type = RedisType::kRedisNone;
+ if (auto s = string_db.Type(key, &type); !s.ok() || type >=
RedisTypeNames.size()) {
+ return "";
+ }
+ string_db.Get(key, &value);
+ }
+ return value;
+}
+
+rocksdb::Status Database::Sort(const RedisType &type, const std::string &key,
SortArgument &args, const RESP &version,
+ std::vector<std::string> *output_vec,
SortResult *res) {
+ /* When sorting a set with no sort specified, we must sort the output
+ * so the result is consistent across scripting and replication.
+ *
+ * The other types (list, sorted set) will retain their native order
+ * even if no sort order is requested, so they remain stable across
+ * scripting and replication.
+ *
+ * TODO: support CLIENT_SCRIPT flag, (!storekey_.empty() || c->flags &
CLIENT_SCRIPT)) */
+ if (args.dontsort && type == RedisType::kRedisSet &&
(!args.storekey.empty())) {
+ /* Force ALPHA sorting */
+ args.dontsort = false;
+ args.alpha = true;
+ args.sortby = "";
+ }
Review Comment:
How about moving it after the type check in the Execute function?
```
Status Execute(Server *srv, Connection *conn, std::string *output) override {
redis::Database redis(srv->storage, conn->GetNamespace());
RedisType type = kRedisNone;
auto s = redis.Type(args_[1], &type);
if (s.ok()) {
if (type >= RedisTypeNames.size()) {
return {Status::RedisExecErr, "Invalid type"};
} else if (type != RedisType::kRedisList && type != RedisType::kRedisSet
&& type != RedisType::kRedisZSet) {
*output = Error("WRONGTYPE Operation against a key holding the wrong
kind of value");
return Status::OK();
}
} else {
return {Status::RedisExecErr, s.ToString()};
}
// MOVE TO HERE
std::vector<std::string> output_vec;
```
##########
src/storage/redis_db.cc:
##########
@@ -777,4 +782,208 @@ rocksdb::Status Database::Copy(const std::string &key,
const std::string &new_ke
return storage_->Write(storage_->DefaultWriteOptions(),
batch->GetWriteBatch());
}
+std::string Database::lookupKeyByPattern(const std::string &pattern, const
std::string &subst) {
Review Comment:
Agree
##########
src/storage/redis_db.cc:
##########
@@ -777,4 +782,208 @@ rocksdb::Status Database::Copy(const std::string &key,
const std::string &new_ke
return storage_->Write(storage_->DefaultWriteOptions(),
batch->GetWriteBatch());
}
+std::string Database::lookupKeyByPattern(const std::string &pattern, const
std::string &subst) {
+ if (pattern == "#") {
+ return subst;
+ }
+
+ auto match_pos = pattern.find('*');
+ if (match_pos == std::string::npos) {
+ return "";
+ }
+
+ // hash field
+ std::string field;
+ auto arrow_pos = pattern.find("->", match_pos + 1);
+ if (arrow_pos != std::string::npos && arrow_pos + 2 < pattern.size()) {
+ field = pattern.substr(arrow_pos + 2);
+ }
+
+ std::string key = pattern.substr(0, match_pos + 1);
+ key.replace(match_pos, 1, subst);
+
+ std::string value;
+ if (!field.empty()) {
+ auto hash_db = redis::Hash(storage_, namespace_);
+ RedisType type = RedisType::kRedisNone;
+ if (auto s = hash_db.Type(key, &type); !s.ok() || type >=
RedisTypeNames.size()) {
+ return "";
+ }
+
+ hash_db.Get(key, field, &value);
+ } else {
+ auto string_db = redis::String(storage_, namespace_);
+ RedisType type = RedisType::kRedisNone;
+ if (auto s = string_db.Type(key, &type); !s.ok() || type >=
RedisTypeNames.size()) {
+ return "";
+ }
+ string_db.Get(key, &value);
+ }
+ return value;
+}
+
+rocksdb::Status Database::Sort(const RedisType &type, const std::string &key,
SortArgument &args, const RESP &version,
+ std::vector<std::string> *output_vec,
SortResult *res) {
+ /* When sorting a set with no sort specified, we must sort the output
+ * so the result is consistent across scripting and replication.
+ *
+ * The other types (list, sorted set) will retain their native order
+ * even if no sort order is requested, so they remain stable across
+ * scripting and replication.
+ *
+ * TODO: support CLIENT_SCRIPT flag, (!storekey_.empty() || c->flags &
CLIENT_SCRIPT)) */
+ if (args.dontsort && type == RedisType::kRedisSet &&
(!args.storekey.empty())) {
+ /* Force ALPHA sorting */
+ args.dontsort = false;
+ args.alpha = true;
+ args.sortby = "";
+ }
+
+ // Obtain the length of the object to sort.
+ const std::string ns_key = AppendNamespacePrefix(key);
+ Metadata metadata(type, false);
+ auto s = GetMetadata(GetOptions{}, {type}, ns_key, &metadata);
+ if (!s.ok()) {
+ return s;
+ }
+
+ int vectorlen = (int)metadata.size;
+
+ // Adjust the offset and count of the limit
+ int offset = args.offset >= vectorlen ? 0 : std::clamp(args.offset, 0,
vectorlen - 1);
+ int count = args.offset >= vectorlen ? 0 : std::clamp(args.count, -1,
vectorlen - offset);
+ if (count == -1) count = vectorlen - offset;
+
+ // Get the elements that need to be sorted
+ std::vector<std::string> str_vec;
+ if (count != 0) {
+ if (type == RedisType::kRedisList) {
+ auto list_db = redis::List(storage_, namespace_);
+
+ if (args.dontsort) {
+ if (args.desc) {
+ list_db.Range(key, -count - offset, -1 - offset, &str_vec);
+ std::reverse(str_vec.begin(), str_vec.end());
+ } else {
+ list_db.Range(key, offset, offset + count - 1, &str_vec);
+ }
+ } else {
+ list_db.Range(key, 0, -1, &str_vec);
+ }
+ } else if (type == RedisType::kRedisSet) {
+ auto set_db = redis::Set(storage_, namespace_);
+ set_db.Members(key, &str_vec);
+
+ if (args.dontsort) {
+ str_vec = std::vector(str_vec.begin() + offset, str_vec.begin() +
offset + count);
Review Comment:
ASC/DESC is not valid for `dontsort` Set key.
ref:
https://github.com/redis/redis/blob/804110a487f048669aa9d9412e5789ec43f4fe39/src/sort.c#L383
In Redis:
```
127.0.0.1:6379> sadd skey 2 3 4 1
(integer) 4
127.0.0.1:6379> SMEMBERS skey
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> sort skey by not-exist-key ASC
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> sort skey by not-exist-key DESC
1) "1"
2) "2"
3) "3"
4) "4"
```
##########
src/commands/cmd_key.cc:
##########
@@ -424,6 +424,111 @@ class CommandCopy : public Commander {
bool replace_ = false;
};
+template <bool ReadOnly>
+class CommandSort : public Commander {
+ public:
+ Status Parse(const std::vector<std::string> &args) override {
+ CommandParser parser(args, 2);
+ while (parser.Good()) {
+ if (parser.EatEqICase("BY")) {
+ if (parser.Remains() < 1) {
+ return parser.InvalidSyntax();
+ }
+ sort_argument_.sortby = GET_OR_RET(parser.TakeStr());
+
+ if (sort_argument_.sortby.find('*') == std::string::npos) {
+ sort_argument_.dontsort = true;
+ } else {
+ /* TODO:
+ * If BY is specified with a real pattern, we can't accept it in
cluster mode,
+ * unless we can make sure the keys formed by the pattern are in the
same slot
+ * as the key to sort.
+ * If BY is specified with a real pattern, we can't accept
+ * it if no full ACL key access is applied for this command. */
+ }
+ } else if (parser.EatEqICase("LIMIT")) {
+ if (parser.Remains() < 2) {
+ return parser.InvalidSyntax();
+ }
+ sort_argument_.offset = GET_OR_RET(parser.template TakeInt<int>());
+ sort_argument_.count = GET_OR_RET(parser.template TakeInt<int>());
+ } else if (parser.EatEqICase("GET") && parser.Remains() >= 1) {
+ if (parser.Remains() < 1) {
+ return parser.InvalidSyntax();
+ }
+ /* TODO:
+ * If GET is specified with a real pattern, we can't accept it in
cluster mode,
+ * unless we can make sure the keys formed by the pattern are in the
same slot
+ * as the key to sort. */
+ sort_argument_.getpatterns.push_back(GET_OR_RET(parser.TakeStr()));
+ } else if (parser.EatEqICase("ASC")) {
+ sort_argument_.desc = false;
+ } else if (parser.EatEqICase("DESC")) {
+ sort_argument_.desc = true;
+ } else if (parser.EatEqICase("ALPHA")) {
+ sort_argument_.alpha = true;
+ } else if (parser.EatEqICase("STORE")) {
+ if constexpr (ReadOnly) {
+ return parser.InvalidSyntax();
+ }
+ if (parser.Remains() < 1) {
+ return parser.InvalidSyntax();
+ }
+ sort_argument_.storekey = GET_OR_RET(parser.TakeStr());
+ } else {
+ return parser.InvalidSyntax();
+ }
+ }
+
+ return Status::OK();
+ }
+
+ Status Execute(Server *srv, Connection *conn, std::string *output) override {
+ redis::Database redis(srv->storage, conn->GetNamespace());
+ RedisType type = kRedisNone;
+ auto s = redis.Type(args_[1], &type);
+ if (s.ok()) {
+ if (type >= RedisTypeNames.size()) {
+ return {Status::RedisExecErr, "Invalid type"};
+ } else if (type != RedisType::kRedisList && type != RedisType::kRedisSet
&& type != RedisType::kRedisZSet) {
Review Comment:
SORT only supports sorting Keys of List/Set/ZSet type
ref:
* https://redis.io/docs/latest/commands/sort/
*
https://github.com/redis/redis/blob/804110a487f048669aa9d9412e5789ec43f4fe39/src/sort.c#L271
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]