This is an automated email from the ASF dual-hosted git repository.
tison pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/incubator-kvrocks.git
The following commit(s) were added to refs/heads/unstable by this push:
new a66ad9ac feat: Catch up `ZRANGE` options (#1428)
a66ad9ac is described below
commit a66ad9ace318a25d3e13180d42fa491baf844573
Author: tison <[email protected]>
AuthorDate: Sun May 7 16:23:09 2023 +0800
feat: Catch up `ZRANGE` options (#1428)
Signed-off-by: tison <[email protected]>
---
src/commands/cmd_zset.cc | 295 ++++++++++++++++---------------
src/common/range_spec.cc | 51 ++++++
src/common/range_spec.h | 34 +++-
src/types/redis_geo.cc | 2 +-
src/types/redis_zset.cc | 83 +++------
src/types/redis_zset.h | 45 +++--
tests/cppunit/types/zset_test.cc | 17 +-
tests/gocase/unit/type/zset/zset_test.go | 112 ++++++++++++
x.py | 4 +-
9 files changed, 406 insertions(+), 237 deletions(-)
diff --git a/src/commands/cmd_zset.cc b/src/commands/cmd_zset.cc
index 4d5816b2..c58fa0a7 100644
--- a/src/commands/cmd_zset.cc
+++ b/src/commands/cmd_zset.cc
@@ -18,6 +18,7 @@
*
*/
+#include "command_parser.h"
#include "commander.h"
#include "commands/scan_base.h"
#include "error_constants.h"
@@ -125,7 +126,7 @@ Status CommandZAdd::validateFlags() const {
class CommandZCount : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
- Status s = redis::ZSet::ParseRangeSpec(args[2], args[3], &spec_);
+ Status s = ParseRangeScoreSpec(args[2], args[3], &spec_);
if (!s.IsOK()) {
return {Status::RedisParseErr, s.Msg()};
}
@@ -145,7 +146,7 @@ class CommandZCount : public Commander {
}
private:
- ZRangeSpec spec_;
+ CommonRangeScoreSpec spec_;
};
class CommandZCard : public Commander {
@@ -265,170 +266,190 @@ class CommandZPopMax : public CommandZPop {
CommandZPopMax() : CommandZPop(false) {}
};
-class CommandZRange : public Commander {
+class CommandZRangeGeneric : public Commander {
public:
- explicit CommandZRange(bool reversed = false) : reversed_(reversed) {}
+ explicit CommandZRangeGeneric(ZRangeType range_type = kZRangeAuto,
ZRangeDirection direction = kZRangeDirectionAuto)
+ : range_type_(range_type), direction_(direction) {}
Status Parse(const std::vector<std::string> &args) override {
- auto parse_start = ParseInt<int>(args[2], 10);
- auto parse_stop = ParseInt<int>(args[3], 10);
- if (!parse_start || !parse_stop) {
- return {Status::RedisParseErr, errValueNotInteger};
+ key_ = args[1];
+
+ int64_t offset = 0;
+ int64_t count = -1;
+ // skip the <CMD> <src> <min> <max> args and parse remaining optional
arguments
+ CommandParser parser(args, 4);
+ while (parser.Good()) {
+ if (parser.EatEqICase("withscores")) {
+ with_scores_ = true;
+ } else if (parser.EatEqICase("limit")) {
+ auto parse_offset = parser.TakeInt<int64_t>();
+ auto parse_count = parser.TakeInt<int64_t>();
+ if (!parse_offset || !parse_count) {
+ return {Status::RedisParseErr, errValueNotInteger};
+ }
+ offset = *parse_offset;
+ count = *parse_count;
+ } else if (range_type_ == kZRangeAuto && parser.EatEqICase("bylex")) {
+ range_type_ = kZRangeLex;
+ } else if (range_type_ == kZRangeAuto && parser.EatEqICase("byscore")) {
+ range_type_ = kZRangeScore;
+ } else if (direction_ == kZRangeDirectionAuto &&
parser.EatEqICase("rev")) {
+ direction_ = kZRangeDirectionReverse;
+ } else {
+ return parser.InvalidSyntax();
+ }
}
- start_ = *parse_start;
- stop_ = *parse_stop;
- if (args.size() > 4 && (util::ToLower(args[4]) == "withscores")) {
- with_scores_ = true;
+ // use defaults if not overridden by arguments
+ if (range_type_ == kZRangeAuto) {
+ range_type_ = kZRangeRank;
+ }
+ if (direction_ == kZRangeDirectionAuto) {
+ direction_ = kZRangeDirectionForward;
}
- return Commander::Parse(args);
- }
-
- Status Execute(Server *svr, Connection *conn, std::string *output) override {
- redis::ZSet zset_db(svr->storage, conn->GetNamespace());
- std::vector<MemberScore> member_scores;
- uint8_t flags = !reversed_ ? 0 : kZSetReversed;
- auto s = zset_db.Range(args_[1], start_, stop_, flags, &member_scores);
- if (!s.ok()) {
- return {Status::RedisExecErr, s.ToString()};
+ // check for conflicting arguments
+ if (with_scores_ && range_type_ == kZRangeLex) {
+ return {Status::RedisParseErr, "syntax error, WITHSCORES not supported
in combination with BYLEX"};
+ }
+ if (count != -1 && range_type_ == kZRangeRank) {
+ return {Status::RedisParseErr,
+ "syntax error, LIMIT is only supported in combination with
either BYSCORE or BYLEX"};
}
- if (!with_scores_) {
- output->append(redis::MultiLen(member_scores.size()));
- } else {
- output->append(redis::MultiLen(member_scores.size() * 2));
+ // resolve index of <min> <max>
+ int min_idx = 2;
+ int max_idx = 3;
+ if (direction_ == kZRangeDirectionReverse && (range_type_ == kZRangeLex ||
range_type_ == kZRangeScore)) {
+ min_idx = 3;
+ max_idx = 2;
}
- for (const auto &ms : member_scores) {
- output->append(redis::BulkString(ms.member));
- if (with_scores_)
output->append(redis::BulkString(util::Float2String(ms.score)));
+ // parse range spec
+ switch (range_type_) {
+ case kZRangeAuto:
+ case kZRangeRank:
+ GET_OR_RET(ParseRangeRankSpec(args[min_idx], args[max_idx],
&rank_spec_));
+ if (direction_ == kZRangeDirectionReverse) {
+ rank_spec_.reversed = true;
+ }
+ break;
+ case kZRangeLex:
+ GET_OR_RET(ParseRangeLexSpec(args[min_idx], args[max_idx],
&lex_spec_));
+ lex_spec_.offset = offset;
+ lex_spec_.count = count;
+ if (direction_ == kZRangeDirectionReverse) {
+ lex_spec_.reversed = true;
+ }
+ break;
+ case kZRangeScore:
+ GET_OR_RET(ParseRangeScoreSpec(args[min_idx], args[max_idx],
&score_spec_));
+ score_spec_.offset = offset;
+ score_spec_.count = count;
+ if (direction_ == kZRangeDirectionReverse) {
+ score_spec_.reversed = true;
+ }
+ break;
}
return Status::OK();
}
- private:
- int start_ = 0;
- int stop_ = 0;
- bool reversed_;
- bool with_scores_ = false;
-};
+ Status Execute(Server *svr, Connection *conn, std::string *output) override {
+ if (range_type_ == kZRangeAuto || range_type_ == kZRangeRank) {
+ redis::ZSet zset_db(svr->storage, conn->GetNamespace());
+ std::vector<MemberScore> member_scores;
+ auto s = zset_db.RangeByRank(args_[1], rank_spec_, &member_scores);
+ if (!s.ok()) {
+ return {Status::RedisExecErr, s.ToString()};
+ }
-class CommandZRevRange : public CommandZRange {
- public:
- CommandZRevRange() : CommandZRange(true) {}
-};
+ if (!with_scores_) {
+ output->append(redis::MultiLen(member_scores.size()));
+ } else {
+ output->append(redis::MultiLen(member_scores.size() * 2));
+ }
-class CommandZRangeByLex : public Commander {
- public:
- explicit CommandZRangeByLex(bool reversed = false) { spec_.reversed =
reversed; }
+ for (const auto &ms : member_scores) {
+ output->append(redis::BulkString(ms.member));
+ if (with_scores_)
output->append(redis::BulkString(util::Float2String(ms.score)));
+ }
- Status Parse(const std::vector<std::string> &args) override {
- Status s;
- if (spec_.reversed) {
- s = ParseRangeLexSpec(args[3], args[2], &spec_);
- } else {
- s = ParseRangeLexSpec(args[2], args[3], &spec_);
- }
+ return Status::OK();
+ } else if (range_type_ == kZRangeLex) {
+ int size = 0;
+ redis::ZSet zset_db(svr->storage, conn->GetNamespace());
+ std::vector<std::string> members;
+ auto s = zset_db.RangeByLex(args_[1], lex_spec_, &members, &size);
+ if (!s.ok()) {
+ return {Status::RedisExecErr, s.ToString()};
+ }
- if (!s.IsOK()) {
- return {Status::RedisParseErr, s.Msg()};
- }
+ *output = redis::MultiBulkString(members, false);
+ return Status::OK();
+ } else { // range_type == kZRangeScore
+ int size = 0;
+ redis::ZSet zset_db(svr->storage, conn->GetNamespace());
+ std::vector<MemberScore> member_scores;
+ auto s = zset_db.RangeByScore(args_[1], score_spec_, &member_scores,
&size);
+ if (!s.ok()) {
+ return {Status::RedisExecErr, s.ToString()};
+ }
- if (args.size() == 7 && util::ToLower(args[4]) == "limit") {
- auto parse_offset = ParseInt<int>(args[5], 10);
- auto parse_count = ParseInt<int>(args[6], 10);
- if (!parse_offset || !parse_count) {
- return {Status::RedisParseErr, errValueNotInteger};
+ if (!with_scores_) {
+ output->append(redis::MultiLen(member_scores.size()));
+ } else {
+ output->append(redis::MultiLen(member_scores.size() * 2));
}
- spec_.offset = *parse_offset;
- spec_.count = *parse_count;
- }
- return Commander::Parse(args);
- }
+ for (const auto &ms : member_scores) {
+ output->append(redis::BulkString(ms.member));
+ if (with_scores_)
output->append(redis::BulkString(util::Float2String(ms.score)));
+ }
- Status Execute(Server *svr, Connection *conn, std::string *output) override {
- int size = 0;
- redis::ZSet zset_db(svr->storage, conn->GetNamespace());
- std::vector<std::string> members;
- auto s = zset_db.RangeByLex(args_[1], spec_, &members, &size);
- if (!s.ok()) {
- return {Status::RedisExecErr, s.ToString()};
+ return Status::OK();
}
-
- *output = redis::MultiBulkString(members, false);
- return Status::OK();
}
private:
- CommonRangeLexSpec spec_;
+ std::string key_;
+ ZRangeType range_type_;
+ ZRangeDirection direction_;
+ bool with_scores_ = false;
+
+ CommonRangeRankSpec rank_spec_;
+ CommonRangeLexSpec lex_spec_;
+ CommonRangeScoreSpec score_spec_;
};
-class CommandZRangeByScore : public Commander {
+class CommandZRange : public CommandZRangeGeneric {
public:
- explicit CommandZRangeByScore(bool reversed = false) { spec_.reversed =
reversed; }
- Status Parse(const std::vector<std::string> &args) override {
- Status s;
- if (spec_.reversed) {
- s = redis::ZSet::ParseRangeSpec(args[3], args[2], &spec_);
- } else {
- s = redis::ZSet::ParseRangeSpec(args[2], args[3], &spec_);
- }
-
- if (!s.IsOK()) {
- return {Status::RedisParseErr, s.Msg()};
- }
-
- size_t i = 4;
- while (i < args.size()) {
- if (util::ToLower(args[i]) == "withscores") {
- with_scores_ = true;
- i++;
- } else if (util::ToLower(args[i]) == "limit" && i + 2 < args.size()) {
- auto parse_offset = ParseInt<int>(args[i + 1], 10);
- auto parse_count = ParseInt<int>(args[i + 2], 10);
- if (!parse_offset || !parse_count) {
- return {Status::RedisParseErr, errValueNotInteger};
- }
-
- spec_.offset = *parse_offset;
- spec_.count = *parse_count;
- i += 3;
- } else {
- return {Status::RedisParseErr, errInvalidSyntax};
- }
- }
- return Commander::Parse(args);
- }
+ explicit CommandZRange() = default;
+};
- Status Execute(Server *svr, Connection *conn, std::string *output) override {
- int size = 0;
- redis::ZSet zset_db(svr->storage, conn->GetNamespace());
- std::vector<MemberScore> member_scores;
- auto s = zset_db.RangeByScore(args_[1], spec_, &member_scores, &size);
- if (!s.ok()) {
- return {Status::RedisExecErr, s.ToString()};
- }
+class CommandZRevRange : public CommandZRangeGeneric {
+ public:
+ CommandZRevRange() : CommandZRangeGeneric(kZRangeRank,
kZRangeDirectionReverse) {}
+};
- if (!with_scores_) {
- output->append(redis::MultiLen(member_scores.size()));
- } else {
- output->append(redis::MultiLen(member_scores.size() * 2));
- }
+class CommandZRangeByLex : public CommandZRangeGeneric {
+ public:
+ explicit CommandZRangeByLex() : CommandZRangeGeneric(kZRangeLex,
kZRangeDirectionForward) {}
+};
- for (const auto &ms : member_scores) {
- output->append(redis::BulkString(ms.member));
- if (with_scores_)
output->append(redis::BulkString(util::Float2String(ms.score)));
- }
+class CommandZRevRangeByLex : public CommandZRangeGeneric {
+ public:
+ CommandZRevRangeByLex() : CommandZRangeGeneric(kZRangeLex,
kZRangeDirectionReverse) {}
+};
- return Status::OK();
- }
+class CommandZRangeByScore : public CommandZRangeGeneric {
+ public:
+ explicit CommandZRangeByScore() : CommandZRangeGeneric(kZRangeScore,
kZRangeDirectionForward) {}
+};
- private:
- ZRangeSpec spec_;
- bool with_scores_ = false;
+class CommandZRevRangeByScore : public CommandZRangeGeneric {
+ public:
+ CommandZRevRangeByScore() : CommandZRangeGeneric(kZRangeScore,
kZRangeDirectionReverse) {}
};
class CommandZRank : public Commander {
@@ -459,16 +480,6 @@ class CommandZRevRank : public CommandZRank {
CommandZRevRank() : CommandZRank(true) {}
};
-class CommandZRevRangeByScore : public CommandZRangeByScore {
- public:
- CommandZRevRangeByScore() : CommandZRangeByScore(true) {}
-};
-
-class CommandZRevRangeByLex : public CommandZRangeByLex {
- public:
- CommandZRevRangeByLex() : CommandZRangeByLex(true) {}
-};
-
class CommandZRem : public Commander {
public:
Status Execute(Server *svr, Connection *conn, std::string *output) override {
@@ -524,7 +535,7 @@ class CommandZRemRangeByRank : public Commander {
class CommandZRemRangeByScore : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
- Status s = redis::ZSet::ParseRangeSpec(args[2], args[3], &spec_);
+ Status s = ParseRangeScoreSpec(args[2], args[3], &spec_);
if (!s.IsOK()) {
return {Status::RedisParseErr, s.Msg()};
}
@@ -544,7 +555,7 @@ class CommandZRemRangeByScore : public Commander {
}
private:
- ZRangeSpec spec_;
+ CommonRangeScoreSpec spec_;
};
class CommandZRemRangeByLex : public Commander {
diff --git a/src/common/range_spec.cc b/src/common/range_spec.cc
index b6ccdbb8..505f4d84 100644
--- a/src/common/range_spec.cc
+++ b/src/common/range_spec.cc
@@ -20,6 +20,9 @@
#include "range_spec.h"
+#include "commands/error_constants.h"
+#include "parse_util.h"
+
Status ParseRangeLexSpec(const std::string &min, const std::string &max,
CommonRangeLexSpec *spec) {
if (min == "+" || max == "-") {
return {Status::NotOK, "min > max"};
@@ -52,3 +55,51 @@ Status ParseRangeLexSpec(const std::string &min, const
std::string &max, CommonR
}
return Status::OK();
}
+
+Status ParseRangeRankSpec(const std::string &min, const std::string &max,
CommonRangeRankSpec *spec) {
+ auto parse_start = ParseInt<int>(min, 10);
+ auto parse_stop = ParseInt<int>(max, 10);
+ if (!parse_start || !parse_stop) {
+ return {Status::RedisParseErr, redis::errValueNotInteger};
+ }
+ spec->start = *parse_start;
+ spec->stop = *parse_stop;
+ return Status::OK();
+}
+
+Status ParseRangeScoreSpec(const std::string &min, const std::string &max,
CommonRangeScoreSpec *spec) {
+ char *eptr = nullptr;
+
+ if (min == "+inf" || max == "-inf") {
+ return {Status::NotOK, "min > max"};
+ }
+
+ if (min == "-inf") {
+ spec->min = kMinScore;
+ } else {
+ const char *sptr = min.data();
+ if (!min.empty() && min[0] == '(') {
+ spec->minex = true;
+ sptr++;
+ }
+ spec->min = strtod(sptr, &eptr);
+ if ((eptr && eptr[0] != '\0') || std::isnan(spec->min)) {
+ return {Status::NotOK, "the min isn't double"};
+ }
+ }
+
+ if (max == "+inf") {
+ spec->max = kMaxScore;
+ } else {
+ const char *sptr = max.data();
+ if (!max.empty() && max[0] == '(') {
+ spec->maxex = true;
+ sptr++;
+ }
+ spec->max = strtod(sptr, &eptr);
+ if ((eptr && eptr[0] != '\0') || std::isnan(spec->max)) {
+ return {Status::NotOK, "the max isn't double"};
+ }
+ }
+ return Status::OK();
+}
diff --git a/src/common/range_spec.h b/src/common/range_spec.h
index 9d4e236c..6bf99521 100644
--- a/src/common/range_spec.h
+++ b/src/common/range_spec.h
@@ -26,12 +26,34 @@
struct CommonRangeLexSpec {
std::string min, max;
- bool minex, maxex; /* are min or max exclusive */
- bool max_infinite; /* are max infinite */
- int64_t offset, count;
- bool removed, reversed;
- CommonRangeLexSpec()
- : minex(false), maxex(false), max_infinite(false), offset(-1),
count(-1), removed(false), reversed(false) {}
+ bool minex = false, maxex = false; /* are min or max exclusive */
+ bool max_infinite = false; /* are max infinite */
+ int64_t offset = -1, count = -1;
+ bool removed = false, reversed = false;
+ explicit CommonRangeLexSpec() = default;
};
Status ParseRangeLexSpec(const std::string &min, const std::string &max,
CommonRangeLexSpec *spec);
+
+struct CommonRangeRankSpec {
+ int start, stop;
+ bool removed = false, reversed = false;
+ explicit CommonRangeRankSpec() = default;
+};
+
+Status ParseRangeRankSpec(const std::string &min, const std::string &max,
CommonRangeRankSpec *spec);
+
+const double kMinScore = (std::numeric_limits<float>::is_iec559 ?
-std::numeric_limits<double>::infinity()
+ :
std::numeric_limits<double>::lowest());
+const double kMaxScore = (std::numeric_limits<float>::is_iec559 ?
std::numeric_limits<double>::infinity()
+ :
std::numeric_limits<double>::max());
+
+struct CommonRangeScoreSpec {
+ double min = kMinScore, max = kMaxScore;
+ bool minex = false, maxex = false; /* are min or max exclusive */
+ int64_t offset = -1, count = -1;
+ bool removed = false, reversed = false;
+ explicit CommonRangeScoreSpec() = default;
+};
+
+Status ParseRangeScoreSpec(const std::string &min, const std::string &max,
CommonRangeScoreSpec *spec);
diff --git a/src/types/redis_geo.cc b/src/types/redis_geo.cc
index 83db8b0d..888c05c6 100644
--- a/src/types/redis_geo.cc
+++ b/src/types/redis_geo.cc
@@ -301,7 +301,7 @@ int Geo::getPointsInRange(const Slice &user_key, double
min, double max, double
std::vector<GeoPoint> *geo_points) {
/* include min in range; exclude max in range */
/* That's: min <= val < max */
- ZRangeSpec spec;
+ CommonRangeScoreSpec spec;
spec.min = min;
spec.max = max;
spec.maxex = true;
diff --git a/src/types/redis_zset.cc b/src/types/redis_zset.cc
index 5a64a1f3..1a35bf60 100644
--- a/src/types/redis_zset.cc
+++ b/src/types/redis_zset.cc
@@ -144,7 +144,7 @@ rocksdb::Status ZSet::Card(const Slice &user_key, int *ret)
{
return rocksdb::Status::OK();
}
-rocksdb::Status ZSet::Count(const Slice &user_key, const ZRangeSpec &spec, int
*ret) {
+rocksdb::Status ZSet::Count(const Slice &user_key, const CommonRangeScoreSpec
&spec, int *ret) {
*ret = 0;
return RangeByScore(user_key, spec, nullptr, ret);
}
@@ -175,10 +175,10 @@ rocksdb::Status ZSet::Pop(const Slice &user_key, int
count, bool min, std::vecto
std::string score_bytes;
double score = min ? kMinScore : kMaxScore;
PutDouble(&score_bytes, score);
- std::string start_key, prefix_key, next_verison_prefix_key;
+ std::string start_key, prefix_key, next_version_prefix_key;
InternalKey(ns_key, score_bytes, metadata.version,
storage_->IsSlotIdEncoded()).Encode(&start_key);
InternalKey(ns_key, "", metadata.version,
storage_->IsSlotIdEncoded()).Encode(&prefix_key);
- InternalKey(ns_key, "", metadata.version + 1,
storage_->IsSlotIdEncoded()).Encode(&next_verison_prefix_key);
+ InternalKey(ns_key, "", metadata.version + 1,
storage_->IsSlotIdEncoded()).Encode(&next_version_prefix_key);
auto batch = storage_->GetWriteBatchBase();
WriteBatchLogData log_data(kRedisZSet);
@@ -187,7 +187,7 @@ rocksdb::Status ZSet::Pop(const Slice &user_key, int count,
bool min, std::vecto
rocksdb::ReadOptions read_options;
LatestSnapShot ss(storage_);
read_options.snapshot = ss.GetSnapShot();
- rocksdb::Slice upper_bound(next_verison_prefix_key);
+ rocksdb::Slice upper_bound(next_version_prefix_key);
read_options.iterate_upper_bound = &upper_bound;
rocksdb::Slice lower_bound(prefix_key);
read_options.iterate_lower_bound = &lower_bound;
@@ -220,21 +220,22 @@ rocksdb::Status ZSet::Pop(const Slice &user_key, int
count, bool min, std::vecto
return storage_->Write(storage_->DefaultWriteOptions(),
batch->GetWriteBatch());
}
-rocksdb::Status ZSet::Range(const Slice &user_key, int start, int stop,
uint8_t flags,
- std::vector<MemberScore> *mscores) {
+rocksdb::Status ZSet::RangeByRank(const Slice &user_key, const
CommonRangeRankSpec &spec,
+ std::vector<MemberScore> *mscores) {
mscores->clear();
std::string ns_key;
AppendNamespacePrefix(user_key, &ns_key);
- bool removed = (flags & (uint8_t)kZSetRemoved) != 0;
- bool reversed = (flags & (uint8_t)kZSetReversed) != 0;
-
std::unique_ptr<LockGuard> lock_guard;
- if (removed) lock_guard =
std::make_unique<LockGuard>(storage_->GetLockManager(), ns_key);
+ if (spec.removed) lock_guard =
std::make_unique<LockGuard>(storage_->GetLockManager(), ns_key);
ZSetMetadata metadata(false);
rocksdb::Status s = GetMetadata(ns_key, &metadata);
if (!s.ok()) return s.IsNotFound() ? rocksdb::Status::OK() : s;
+
+ int start = spec.start;
+ int stop = spec.stop;
+
if (start < 0) start += static_cast<int>(metadata.size);
if (stop < 0) stop += static_cast<int>(metadata.size);
if (start < 0) start = 0;
@@ -243,7 +244,7 @@ rocksdb::Status ZSet::Range(const Slice &user_key, int
start, int stop, uint8_t
}
std::string score_bytes;
- double score = !reversed ? kMinScore : kMaxScore;
+ double score = !(spec.reversed) ? kMinScore : kMaxScore;
PutDouble(&score_bytes, score);
std::string start_key, prefix_key, next_verison_prefix_key;
InternalKey(ns_key, score_bytes, metadata.version,
storage_->IsSlotIdEncoded()).Encode(&start_key);
@@ -265,16 +266,16 @@ rocksdb::Status ZSet::Range(const Slice &user_key, int
start, int stop, uint8_t
auto iter = util::UniqueIterator(storage_, read_options, score_cf_handle_);
iter->Seek(start_key);
// see comment in rangebyscore()
- if (reversed && (!iter->Valid() || !iter->key().starts_with(prefix_key))) {
+ if (spec.reversed && (!iter->Valid() ||
!iter->key().starts_with(prefix_key))) {
iter->SeekForPrev(start_key);
}
- for (; iter->Valid() && iter->key().starts_with(prefix_key); !reversed ?
iter->Next() : iter->Prev()) {
+ for (; iter->Valid() && iter->key().starts_with(prefix_key);
!(spec.reversed) ? iter->Next() : iter->Prev()) {
InternalKey ikey(iter->key(), storage_->IsSlotIdEncoded());
Slice score_key = ikey.GetSubKey();
GetDouble(&score_key, &score);
if (count >= start) {
- if (removed) {
+ if (spec.removed) {
std::string sub_key;
InternalKey(ns_key, score_key, metadata.version,
storage_->IsSlotIdEncoded()).Encode(&sub_key);
batch->Delete(sub_key);
@@ -296,8 +297,8 @@ rocksdb::Status ZSet::Range(const Slice &user_key, int
start, int stop, uint8_t
return rocksdb::Status::OK();
}
-rocksdb::Status ZSet::RangeByScore(const Slice &user_key, ZRangeSpec spec,
std::vector<MemberScore> *mscores,
- int *size) {
+rocksdb::Status ZSet::RangeByScore(const Slice &user_key, const
CommonRangeScoreSpec &spec,
+ std::vector<MemberScore> *mscores, int
*size) {
if (size) *size = 0;
if (mscores) mscores->clear();
@@ -557,7 +558,7 @@ rocksdb::Status ZSet::Remove(const Slice &user_key, const
std::vector<Slice> &me
return storage_->Write(storage_->DefaultWriteOptions(),
batch->GetWriteBatch());
}
-rocksdb::Status ZSet::RemoveRangeByScore(const Slice &user_key, ZRangeSpec
spec, int *ret) {
+rocksdb::Status ZSet::RemoveRangeByScore(const Slice &user_key,
CommonRangeScoreSpec &spec, int *ret) {
spec.removed = true;
return RangeByScore(user_key, spec, nullptr, ret);
}
@@ -568,9 +569,12 @@ rocksdb::Status ZSet::RemoveRangeByLex(const Slice
&user_key, CommonRangeLexSpec
}
rocksdb::Status ZSet::RemoveRangeByRank(const Slice &user_key, int start, int
stop, int *ret) {
- uint8_t flags = kZSetRemoved;
+ CommonRangeRankSpec rank_spec;
+ rank_spec.start = start;
+ rank_spec.stop = stop;
+ rank_spec.removed = true;
std::vector<MemberScore> mscores;
- rocksdb::Status s = Range(user_key, start, stop, flags, &mscores);
+ rocksdb::Status s = RangeByRank(user_key, rank_spec, &mscores);
*ret = static_cast<int>(mscores.size());
return s;
}
@@ -659,7 +663,7 @@ rocksdb::Status ZSet::InterStore(const Slice &dst, const
std::vector<KeyWeight>
std::map<std::string, size_t> member_counters;
std::vector<MemberScore> target_mscores;
int target_size = 0;
- ZRangeSpec spec;
+ CommonRangeScoreSpec spec;
auto s = RangeByScore(keys_weights[0].key, spec, &target_mscores,
&target_size);
if (!s.ok() || target_mscores.empty()) return s;
@@ -719,7 +723,7 @@ rocksdb::Status ZSet::UnionStore(const Slice &dst, const
std::vector<KeyWeight>
std::map<std::string, double> dst_zset;
std::vector<MemberScore> target_mscores;
int target_size = 0;
- ZRangeSpec spec;
+ CommonRangeScoreSpec spec;
for (const auto &key_weight : keys_weights) {
// get all member
auto s = RangeByScore(key_weight.key, spec, &target_mscores, &target_size);
@@ -762,43 +766,6 @@ rocksdb::Status ZSet::UnionStore(const Slice &dst, const
std::vector<KeyWeight>
return rocksdb::Status::OK();
}
-Status ZSet::ParseRangeSpec(const std::string &min, const std::string &max,
ZRangeSpec *spec) {
- char *eptr = nullptr;
-
- if (min == "+inf" || max == "-inf") {
- return {Status::NotOK, "min > max"};
- }
-
- if (min == "-inf") {
- spec->min = kMinScore;
- } else {
- const char *sptr = min.data();
- if (!min.empty() && min[0] == '(') {
- spec->minex = true;
- sptr++;
- }
- spec->min = strtod(sptr, &eptr);
- if ((eptr && eptr[0] != '\0') || std::isnan(spec->min)) {
- return {Status::NotOK, "the min isn't double"};
- }
- }
-
- if (max == "+inf") {
- spec->max = kMaxScore;
- } else {
- const char *sptr = max.data();
- if (!max.empty() && max[0] == '(') {
- spec->maxex = true;
- sptr++;
- }
- spec->max = strtod(sptr, &eptr);
- if ((eptr && eptr[0] != '\0') || std::isnan(spec->max)) {
- return {Status::NotOK, "the max isn't double"};
- }
- }
- return Status::OK();
-}
-
rocksdb::Status ZSet::Scan(const Slice &user_key, const std::string &cursor,
uint64_t limit,
const std::string &member_prefix,
std::vector<std::string> *members,
std::vector<double> *scores) {
diff --git a/src/types/redis_zset.h b/src/types/redis_zset.h
index 54ffb78b..905ed587 100644
--- a/src/types/redis_zset.h
+++ b/src/types/redis_zset.h
@@ -31,19 +31,6 @@
enum AggregateMethod { kAggregateSum, kAggregateMin, kAggregateMax };
-const double kMinScore = (std::numeric_limits<float>::is_iec559 ?
-std::numeric_limits<double>::infinity()
- :
std::numeric_limits<double>::lowest());
-const double kMaxScore = (std::numeric_limits<float>::is_iec559 ?
std::numeric_limits<double>::infinity()
- :
std::numeric_limits<double>::max());
-
-struct ZRangeSpec {
- double min = kMinScore, max = kMaxScore;
- bool minex = false, maxex = false; /* are min or max exclusive */
- int offset = -1, count = -1;
- bool removed = false, reversed = false;
- ZRangeSpec() = default;
-};
-
struct KeyWeight {
std::string key;
double weight;
@@ -54,15 +41,26 @@ struct MemberScore {
double score;
};
+enum ZRangeType {
+ kZRangeAuto,
+ kZRangeRank,
+ kZRangeScore,
+ kZRangeLex,
+};
+
+enum ZRangeDirection {
+ kZRangeDirectionAuto,
+ kZRangeDirectionForward,
+ kZRangeDirectionReverse,
+};
+
enum ZSetFlags {
kZSetIncr = 1,
kZSetNX = 1 << 1,
kZSetXX = 1 << 2,
- kZSetReversed = 1 << 3,
- kZSetRemoved = 1 << 4,
- kZSetGT = 1 << 5,
- kZSetLT = 1 << 6,
- kZSetCH = 1 << 7,
+ kZSetGT = 1 << 3,
+ kZSetLT = 1 << 4,
+ kZSetCH = 1 << 5,
};
class ZAddFlags {
@@ -95,20 +93,21 @@ class ZSet : public SubKeyScanner {
: SubKeyScanner(storage, ns),
score_cf_handle_(storage->GetCFHandle("zset_score")) {}
rocksdb::Status Add(const Slice &user_key, ZAddFlags flags,
std::vector<MemberScore> *mscores, int *ret);
rocksdb::Status Card(const Slice &user_key, int *ret);
- rocksdb::Status Count(const Slice &user_key, const ZRangeSpec &spec, int
*ret);
+ rocksdb::Status Count(const Slice &user_key, const CommonRangeScoreSpec
&spec, int *ret);
rocksdb::Status IncrBy(const Slice &user_key, const Slice &member, double
increment, double *score);
- rocksdb::Status Range(const Slice &user_key, int start, int stop, uint8_t
flags, std::vector<MemberScore> *mscores);
- rocksdb::Status RangeByScore(const Slice &user_key, ZRangeSpec spec,
std::vector<MemberScore> *mscores, int *size);
+ rocksdb::Status RangeByRank(const Slice &user_key, const CommonRangeRankSpec
&spec,
+ std::vector<MemberScore> *mscores);
+ rocksdb::Status RangeByScore(const Slice &user_key, const
CommonRangeScoreSpec &spec,
+ std::vector<MemberScore> *mscores, int *size);
rocksdb::Status RangeByLex(const Slice &user_key, const CommonRangeLexSpec
&spec, std::vector<std::string> *members,
int *size);
rocksdb::Status Rank(const Slice &user_key, const Slice &member, bool
reversed, int *ret);
rocksdb::Status Remove(const Slice &user_key, const std::vector<Slice>
&members, int *ret);
- rocksdb::Status RemoveRangeByScore(const Slice &user_key, ZRangeSpec spec,
int *ret);
+ rocksdb::Status RemoveRangeByScore(const Slice &user_key,
CommonRangeScoreSpec &spec, int *ret);
rocksdb::Status RemoveRangeByLex(const Slice &user_key, CommonRangeLexSpec
spec, int *ret);
rocksdb::Status RemoveRangeByRank(const Slice &user_key, int start, int
stop, int *ret);
rocksdb::Status Pop(const Slice &user_key, int count, bool min,
std::vector<MemberScore> *mscores);
rocksdb::Status Score(const Slice &user_key, const Slice &member, double
*score);
- static Status ParseRangeSpec(const std::string &min, const std::string &max,
ZRangeSpec *spec);
rocksdb::Status Scan(const Slice &user_key, const std::string &cursor,
uint64_t limit,
const std::string &member_prefix,
std::vector<std::string> *members,
std::vector<double> *scores = nullptr);
diff --git a/tests/cppunit/types/zset_test.cc b/tests/cppunit/types/zset_test.cc
index e97cf899..fc06090e 100644
--- a/tests/cppunit/types/zset_test.cc
+++ b/tests/cppunit/types/zset_test.cc
@@ -103,7 +103,10 @@ TEST_F(RedisZSetTest, Range) {
int count = static_cast<int>(mscores.size() - 1);
zset_->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
- zset_->Range(key_, 0, -2, 0, &mscores);
+ CommonRangeRankSpec rank_spec;
+ rank_spec.start = 0;
+ rank_spec.stop = -2;
+ zset_->RangeByRank(key_, rank_spec, &mscores);
EXPECT_EQ(mscores.size(), count);
for (size_t i = 0; i < mscores.size(); i++) {
EXPECT_EQ(mscores[i].member, fields_[i].ToString());
@@ -121,7 +124,11 @@ TEST_F(RedisZSetTest, RevRange) {
int count = static_cast<int>(mscores.size() - 1);
zset_->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(static_cast<int>(fields_.size()), ret);
- zset_->Range(key_, 0, -2, kZSetReversed, &mscores);
+ CommonRangeRankSpec rank_spec;
+ rank_spec.start = 0;
+ rank_spec.stop = -2;
+ rank_spec.reversed = true;
+ zset_->RangeByRank(key_, rank_spec, &mscores);
EXPECT_EQ(mscores.size(), count);
for (size_t i = 0; i < mscores.size(); i++) {
EXPECT_EQ(mscores[i].member, fields_[count - i].ToString());
@@ -232,7 +239,7 @@ TEST_F(RedisZSetTest, RangeByScore) {
EXPECT_EQ(fields_.size(), ret);
// test case: inclusive the min and max score
- ZRangeSpec spec;
+ CommonRangeScoreSpec spec;
spec.min = scores_[0];
spec.max = scores_[scores_.size() - 2];
zset_->RangeByScore(key_, spec, &mscores, nullptr);
@@ -279,7 +286,7 @@ TEST_F(RedisZSetTest, RangeByScoreWithLimit) {
zset_->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
- ZRangeSpec spec;
+ CommonRangeScoreSpec spec;
spec.offset = 1;
spec.count = 2;
zset_->RangeByScore(key_, spec, &mscores, nullptr);
@@ -299,7 +306,7 @@ TEST_F(RedisZSetTest, RemRangeByScore) {
}
zset_->Add(key_, ZAddFlags::Default(), &mscores, &ret);
EXPECT_EQ(fields_.size(), ret);
- ZRangeSpec spec;
+ CommonRangeScoreSpec spec;
spec.min = scores_[0];
spec.max = scores_[scores_.size() - 2];
zset_->RemoveRangeByScore(key_, spec, &ret);
diff --git a/tests/gocase/unit/type/zset/zset_test.go
b/tests/gocase/unit/type/zset/zset_test.go
index 7dae9e62..8a02d379 100644
--- a/tests/gocase/unit/type/zset/zset_test.go
+++ b/tests/gocase/unit/type/zset/zset_test.go
@@ -268,6 +268,22 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
{3, "c"},
{4, "d"},
}, rdb.ZRangeWithScores(ctx, "ztmp", 0, -1).Val())
+
+ // extend zrange commands
+ require.Equal(t, []string{"a", "b", "c", "d"},
rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "ztmp", Start: 0, Stop: -1, Offset:
0, Count: -1}).Val())
+ require.Equal(t, []string{"d", "c", "b", "a"},
rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "ztmp", Start: 0, Stop: -1, Offset:
0, Count: -1, Rev: true}).Val())
+ require.Equal(t, []redis.Z{
+ {1, "a"},
+ {2, "b"},
+ {3, "c"},
+ {4, "d"},
+ }, rdb.ZRangeArgsWithScores(ctx, redis.ZRangeArgs{Key: "ztmp",
Start: 0, Stop: -1, Offset: 0, Count: -1}).Val())
+ require.Equal(t, []redis.Z{
+ {4, "d"},
+ {3, "c"},
+ {2, "b"},
+ {1, "a"},
+ }, rdb.ZRangeArgsWithScores(ctx, redis.ZRangeArgs{Key: "ztmp",
Start: 0, Stop: -1, Offset: 0, Count: -1, Rev: true}).Val())
})
t.Run(fmt.Sprintf("ZREVRANGE basics - %s", encoding), func(t
*testing.T) {
@@ -363,6 +379,15 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
require.Equal(t, []string{"f", "e", "d"},
rdb.ZRevRangeByScore(ctx, "zset", &redis.ZRangeBy{Max: "6", Min: "3"}).Val())
require.Equal(t, []string{"g", "f", "e"},
rdb.ZRevRangeByScore(ctx, "zset", &redis.ZRangeBy{Max: "+inf", Min: "4"}).Val())
require.Equal(t, int64(3), rdb.ZCount(ctx, "zset", "0",
"3").Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []string{"a", "b", "c"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "-inf", Stop: "2", ByScore: true}).Val())
+ require.Equal(t, []string{"b", "c", "d"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "0", Stop: "3", ByScore: true}).Val())
+ require.Equal(t, []string{"d", "e", "f"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "3", Stop: "6", ByScore: true}).Val())
+ require.Equal(t, []string{"e", "f", "g"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "4", Stop: "+inf", ByScore: true}).Val())
+ require.Equal(t, []string{"c", "b", "a"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "2", Start: "-inf", ByScore: true, Rev:
true}).Val())
+ require.Equal(t, []string{"d", "c", "b"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "3", Start: "0", ByScore: true, Rev:
true}).Val())
+ require.Equal(t, []string{"f", "e", "d"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "6", Start: "3", ByScore: true, Rev:
true}).Val())
+ require.Equal(t, []string{"g", "f", "e"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "+inf", Start: "4", ByScore: true, Rev:
true}).Val())
// exclusive range
require.Equal(t, []string{"b"}, rdb.ZRangeByScore(ctx, "zset",
&redis.ZRangeBy{Min: "(-inf", Max: "(2"}).Val())
@@ -374,6 +399,15 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
require.Equal(t, []string{"f", "e"}, rdb.ZRevRangeByScore(ctx,
"zset", &redis.ZRangeBy{Max: "(6", Min: "(3"}).Val())
require.Equal(t, []string{"f"}, rdb.ZRevRangeByScore(ctx,
"zset", &redis.ZRangeBy{Max: "(+inf", Min: "(4"}).Val())
require.Equal(t, int64(2), rdb.ZCount(ctx, "zset", "(0",
"(3").Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []string{"b"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(-inf", Stop: "(2", ByScore: true}).Val())
+ require.Equal(t, []string{"b", "c"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(0", Stop: "(3", ByScore: true}).Val())
+ require.Equal(t, []string{"e", "f"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(3", Stop: "(6", ByScore: true}).Val())
+ require.Equal(t, []string{"f"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(4", Stop: "(+inf", ByScore: true}).Val())
+ require.Equal(t, []string{"b"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "(2", Start: "(-inf", ByScore: true, Rev:
true}).Val())
+ require.Equal(t, []string{"c", "b"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "(3", Start: "(0", ByScore: true, Rev:
true}).Val())
+ require.Equal(t, []string{"f", "e"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "(6", Start: "(3", ByScore: true, Rev:
true}).Val())
+ require.Equal(t, []string{"f"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "(+inf", Start: "(4", ByScore: true, Rev:
true}).Val())
// test empty ranges
rdb.ZRem(ctx, "zset", "a")
@@ -385,6 +419,12 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
require.Equal(t, []string{}, rdb.ZRangeByScore(ctx, "zset",
&redis.ZRangeBy{Min: "-inf", Max: "-6"}).Val())
require.Equal(t, []string{}, rdb.ZRevRangeByScore(ctx, "zset",
&redis.ZRangeBy{Max: "+inf", Min: "6"}).Val())
require.Equal(t, []string{}, rdb.ZRevRangeByScore(ctx, "zset",
&redis.ZRangeBy{Max: "-6", Min: "-inf"}).Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "4", Stop: "2", ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "6", Stop: "+inf", ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "-inf", Stop: "-6", ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "+inf", Start: "6", ByScore: true, Rev:
true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "-6", Start: "-inf", ByScore: true, Rev:
true}).Val())
// exclusive range
require.Equal(t, []string{}, rdb.ZRangeByScore(ctx, "zset",
&redis.ZRangeBy{Min: "(4", Max: "(2"}).Val())
@@ -394,18 +434,34 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
require.Equal(t, []string{}, rdb.ZRangeByScore(ctx, "zset",
&redis.ZRangeBy{Min: "(-inf", Max: "(-6"}).Val())
require.Equal(t, []string{}, rdb.ZRevRangeByScore(ctx, "zset",
&redis.ZRangeBy{Max: "(+inf", Min: "(6"}).Val())
require.Equal(t, []string{}, rdb.ZRevRangeByScore(ctx, "zset",
&redis.ZRangeBy{Max: "(-6", Min: "(-inf"}).Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(4", Stop: "(2", ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "2", Stop: "(2", ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(2", Stop: "2", ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(6", Stop: "(+inf", ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(-inf", Stop: "(-6", ByScore:
true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "(+inf", Start: "(6", ByScore: true, Rev:
true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Stop: "(-6", Start: "(-inf", ByScore: true, Rev:
true}).Val())
// empty inner range
require.Equal(t, []string{}, rdb.ZRangeByScore(ctx, "zset",
&redis.ZRangeBy{Min: "2.4", Max: "2.6"}).Val())
require.Equal(t, []string{}, rdb.ZRangeByScore(ctx, "zset",
&redis.ZRangeBy{Min: "(2.4", Max: "2.6"}).Val())
require.Equal(t, []string{}, rdb.ZRangeByScore(ctx, "zset",
&redis.ZRangeBy{Min: "2.4", Max: "(2.6"}).Val())
require.Equal(t, []string{}, rdb.ZRangeByScore(ctx, "zset",
&redis.ZRangeBy{Min: "(2.4", Max: "(2.6"}).Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "2.4", Stop: "2.6", ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(2.4", Stop: "2.6", ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "2.4", Stop: "(2.6", ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(2.4", Stop: "(2.6", ByScore:
true}).Val())
})
t.Run("ZRANGEBYSCORE with WITHSCORES", func(t *testing.T) {
createDefaultZset(rdb, ctx)
require.Equal(t, []redis.Z{{1, "b"}, {2, "c"}, {3, "d"}},
rdb.ZRangeByScoreWithScores(ctx, "zset", &redis.ZRangeBy{Min: "0", Max:
"3"}).Val())
require.Equal(t, []redis.Z{{3, "d"}, {2, "c"}, {1, "b"}},
rdb.ZRevRangeByScoreWithScores(ctx, "zset", &redis.ZRangeBy{Min: "0", Max:
"3"}).Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []redis.Z{{1, "b"}, {2, "c"}, {3, "d"}},
rdb.ZRangeArgsWithScores(ctx, redis.ZRangeArgs{Key: "zset", Start: "0", Stop:
"3", ByScore: true}).Val())
+ require.Equal(t, []redis.Z{{3, "d"}, {2, "c"}, {1, "b"}},
rdb.ZRangeArgsWithScores(ctx, redis.ZRangeArgs{Key: "zset", Start: "0", Stop:
"3", ByScore: true, Rev: true}).Val())
})
t.Run("ZRANGEBYSCORE with LIMIT", func(t *testing.T) {
@@ -418,18 +474,34 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
require.Equal(t, []string{"d", "c", "b"},
rdb.ZRevRangeByScore(ctx, "zset", &redis.ZRangeBy{Min: "0", Max: "10", Offset:
2, Count: 3}).Val())
require.Equal(t, []string{"d", "c", "b"},
rdb.ZRevRangeByScore(ctx, "zset", &redis.ZRangeBy{Min: "0", Max: "10", Offset:
2, Count: 10}).Val())
require.Equal(t, []string{}, rdb.ZRevRangeByScore(ctx, "zset",
&redis.ZRangeBy{Min: "0", Max: "10", Offset: 20, Count: 10}).Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []string{"b", "c"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "0", Stop: "10", Offset: 0, Count: 2,
ByScore: true}).Val())
+ require.Equal(t, []string{"d", "e", "f"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "0", Stop: "10", Offset: 2, Count: 3,
ByScore: true}).Val())
+ require.Equal(t, []string{"d", "e", "f"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "0", Stop: "10", Offset: 2, Count: 10,
ByScore: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "0", Stop: "10", Offset: 20, Count: 10,
ByScore: true}).Val())
+ require.Equal(t, []string{"f", "e"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "0", Stop: "10", Offset: 0, Count: 2,
ByScore: true, Rev: true}).Val())
+ require.Equal(t, []string{"d", "c", "b"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "0", Stop: "10", Offset: 2, Count: 3,
ByScore: true, Rev: true}).Val())
+ require.Equal(t, []string{"d", "c", "b"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "0", Stop: "10", Offset: 2, Count: 10,
ByScore: true, Rev: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "0", Stop: "10", Offset: 20, Count: 10,
ByScore: true, Rev: true}).Val())
})
t.Run("ZRANGEBYSCORE with LIMIT and WITHSCORES", func(t *testing.T) {
createDefaultZset(rdb, ctx)
require.Equal(t, []redis.Z{{4, "e"}, {5, "f"}},
rdb.ZRangeByScoreWithScores(ctx, "zset", &redis.ZRangeBy{Min: "2", Max: "5",
Offset: 2, Count: 3}).Val())
require.Equal(t, []redis.Z{{3, "d"}, {2, "c"}},
rdb.ZRevRangeByScoreWithScores(ctx, "zset", &redis.ZRangeBy{Min: "2", Max: "5",
Offset: 2, Count: 3}).Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []redis.Z{{4, "e"}, {5, "f"}},
rdb.ZRangeArgsWithScores(ctx, redis.ZRangeArgs{Key: "zset", Start: "2", Stop:
"5", Offset: 2, Count: 3, ByScore: true}).Val())
+ require.Equal(t, []redis.Z{{3, "d"}, {2, "c"}},
rdb.ZRangeArgsWithScores(ctx, redis.ZRangeArgs{Key: "zset", Start: "2", Stop:
"5", Offset: 2, Count: 3, ByScore: true, Rev: true}).Val())
})
t.Run("ZRANGEBYSCORE with non-value min or max", func(t *testing.T) {
util.ErrorRegexp(t, rdb.ZRangeByScore(ctx, "fooz",
&redis.ZRangeBy{Min: "str", Max: "1"}).Err(), ".*double.*")
util.ErrorRegexp(t, rdb.ZRangeByScore(ctx, "fooz",
&redis.ZRangeBy{Min: "1", Max: "str"}).Err(), ".*double.*")
util.ErrorRegexp(t, rdb.ZRangeByScore(ctx, "fooz",
&redis.ZRangeBy{Min: "1", Max: "NaN"}).Err(), ".*double.*")
+ // .. in zrange extension syntax
+ util.ErrorRegexp(t, rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key:
"fooz", Start: "str", Stop: "1", ByScore: true}).Err(), ".*double.*")
+ util.ErrorRegexp(t, rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key:
"fooz", Start: "1", Stop: "str", ByScore: true}).Err(), ".*double.*")
+ util.ErrorRegexp(t, rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key:
"fooz", Start: "1", Stop: "NaN", ByScore: true}).Err(), ".*double.*")
})
t.Run("ZRANGEBYSCORE for min/max score with multi member", func(t
*testing.T) {
@@ -443,8 +515,10 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
{math.Inf(1), "g"}}
createZset(rdb, ctx, "mzset", zsetInt)
require.Equal(t, zsetInt, rdb.ZRangeByScoreWithScores(ctx,
"mzset", &redis.ZRangeBy{Min: "-inf", Max: "+inf"}).Val())
+ require.Equal(t, zsetInt, rdb.ZRangeArgsWithScores(ctx,
redis.ZRangeArgs{Key: "mzset", Start: "-inf", Stop: "+inf", ByScore:
true}).Val())
util.ReverseSlice(zsetInt)
require.Equal(t, zsetInt, rdb.ZRevRangeByScoreWithScores(ctx,
"mzset", &redis.ZRangeBy{Min: "-inf", Max: "+inf"}).Val())
+ require.Equal(t, zsetInt, rdb.ZRangeArgsWithScores(ctx,
redis.ZRangeArgs{Key: "mzset", Start: "-inf", Stop: "+inf", ByScore: true, Rev:
true}).Val())
zsetDouble := []redis.Z{
{-1.004, "a"},
@@ -455,8 +529,10 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
{1.004, "f"}}
createZset(rdb, ctx, "mzset", zsetDouble)
require.Equal(t, zsetDouble, rdb.ZRangeByScoreWithScores(ctx,
"mzset", &redis.ZRangeBy{Min: "-inf", Max: "+inf"}).Val())
+ require.Equal(t, zsetDouble, rdb.ZRangeArgsWithScores(ctx,
redis.ZRangeArgs{Key: "mzset", Start: "-inf", Stop: "+inf", ByScore:
true}).Val())
util.ReverseSlice(zsetDouble)
require.Equal(t, zsetDouble,
rdb.ZRevRangeByScoreWithScores(ctx, "mzset", &redis.ZRangeBy{Min: "-inf", Max:
"+inf"}).Val())
+ require.Equal(t, zsetDouble, rdb.ZRangeArgsWithScores(ctx,
redis.ZRangeArgs{Key: "mzset", Start: "-inf", Stop: "+inf", ByScore: true, Rev:
true}).Val())
})
t.Run("ZRANGEBYLEX/ZREVRANGEBYLEX/ZLEXCOUNT basics", func(t *testing.T)
{
@@ -469,6 +545,13 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
require.Equal(t, []string{"cool", "bar", "alpha"},
rdb.ZRevRangeByLex(ctx, "zset", &redis.ZRangeBy{Min: "-", Max: "[cool"}).Val())
require.Equal(t, []string{"down", "cool", "bar"},
rdb.ZRevRangeByLex(ctx, "zset", &redis.ZRangeBy{Min: "[bar", Max:
"[down"}).Val())
require.Equal(t, []string{"omega", "hill", "great", "foo",
"elephant", "down"}, rdb.ZRevRangeByLex(ctx, "zset", &redis.ZRangeBy{Min: "[d",
Max: "+"}).Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []string{"alpha", "bar", "cool"},
rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "zset", Start: "-", Stop: "[cool",
ByLex: true}).Val())
+ require.Equal(t, []string{"bar", "cool", "down"},
rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "zset", Start: "[bar", Stop: "[down",
ByLex: true}).Val())
+ require.Equal(t, []string{"great", "hill", "omega"},
rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "zset", Start: "[g", Stop: "+",
ByLex: true}).Val())
+ require.Equal(t, []string{"cool", "bar", "alpha"},
rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "zset", Start: "-", Stop: "[cool",
ByLex: true, Rev: true}).Val())
+ require.Equal(t, []string{"down", "cool", "bar"},
rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "zset", Start: "[bar", Stop: "[down",
ByLex: true, Rev: true}).Val())
+ require.Equal(t, []string{"omega", "hill", "great", "foo",
"elephant", "down"}, rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "zset", Start:
"[d", Stop: "+", ByLex: true, Rev: true}).Val())
// exclusive range
require.Equal(t, []string{"alpha", "bar"}, rdb.ZRangeByLex(ctx,
"zset", &redis.ZRangeBy{Min: "-", Max: "(cool"}).Val())
@@ -477,6 +560,13 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
require.Equal(t, []string{"bar", "alpha"},
rdb.ZRevRangeByLex(ctx, "zset", &redis.ZRangeBy{Min: "-", Max: "(cool"}).Val())
require.Equal(t, []string{"cool"}, rdb.ZRevRangeByLex(ctx,
"zset", &redis.ZRangeBy{Min: "(bar", Max: "(down"}).Val())
require.Equal(t, []string{"omega", "hill"},
rdb.ZRevRangeByLex(ctx, "zset", &redis.ZRangeBy{Min: "(great", Max: "+"}).Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []string{"alpha", "bar"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "-", Stop: "(cool", ByLex: true}).Val())
+ require.Equal(t, []string{"cool"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(bar", Stop: "(down", ByLex: true}).Val())
+ require.Equal(t, []string{"hill", "omega"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(great", Stop: "+", ByLex: true}).Val())
+ require.Equal(t, []string{"bar", "alpha"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "-", Stop: "(cool", ByLex: true, Rev:
true}).Val())
+ require.Equal(t, []string{"cool"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(bar", Stop: "(down", ByLex: true, Rev:
true}).Val())
+ require.Equal(t, []string{"omega", "hill"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(great", Stop: "+", ByLex: true, Rev:
true}).Val())
// inclusive and exclusive
require.Equal(t, []string{}, rdb.ZRangeByLex(ctx, "zset",
&redis.ZRangeBy{Min: "(az", Max: "(b"}).Val())
@@ -484,6 +574,12 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
require.Equal(t, []string{}, rdb.ZRangeByLex(ctx, "zset",
&redis.ZRangeBy{Min: "-", Max: "[aaaa"}).Val())
require.Equal(t, []string{}, rdb.ZRevRangeByLex(ctx, "zset",
&redis.ZRangeBy{Min: "[elez", Max: "[elex"}).Val())
require.Equal(t, []string{}, rdb.ZRangeByLex(ctx, "zset",
&redis.ZRangeBy{Min: "(hill", Max: "(omega"}).Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(az", Stop: "(b", ByLex: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(z", Stop: "+", ByLex: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "-", Stop: "[aaaa", ByLex: true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "[elez", Stop: "[elex", ByLex: true, Rev:
true}).Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "(hill", Stop: "(omega", ByLex:
true}).Val())
})
t.Run("ZRANGEBYLEX with LIMIT", func(t *testing.T) {
@@ -497,6 +593,16 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
require.Equal(t, []string{"bar", "cool", "down"},
rdb.ZRangeByLex(ctx, "zset", &redis.ZRangeBy{Min: "[bar", Max: "[down", Offset:
0, Count: 100}).Val())
require.Equal(t, []string{"omega", "hill", "great", "foo",
"elephant"}, rdb.ZRevRangeByLex(ctx, "zset", &redis.ZRangeBy{Min: "[d", Max:
"+", Offset: 0, Count: 5}).Val())
require.Equal(t, []string{"omega", "hill", "great", "foo"},
rdb.ZRevRangeByLex(ctx, "zset", &redis.ZRangeBy{Min: "[d", Max: "+", Offset: 0,
Count: 4}).Val())
+ // .. in zrange extension syntax
+ require.Equal(t, []string{"alpha", "bar"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "-", Stop: "[cool", Offset: 0, Count: 2,
ByLex: true}).Val())
+ require.Equal(t, []string{"bar", "cool"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "-", Stop: "[cool", Offset: 1, Count: 2,
ByLex: true}).Val())
+ require.Equal(t, []interface{}{}, rdb.Do(ctx, "zrange", "zset",
"[bar", "[down", "bylex", "limit", "0", "0").Val())
+ require.Equal(t, []string{}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "[bar", Stop: "[down", Offset: 2, Count:
0, ByLex: true}).Val())
+ require.Equal(t, []string{"bar"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "[bar", Stop: "[down", Offset: 0, Count:
1, ByLex: true}).Val())
+ require.Equal(t, []string{"cool"}, rdb.ZRangeArgs(ctx,
redis.ZRangeArgs{Key: "zset", Start: "[bar", Stop: "[down", Offset: 1, Count:
1, ByLex: true}).Val())
+ require.Equal(t, []string{"bar", "cool", "down"},
rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "zset", Start: "[bar", Stop: "[down",
Offset: 0, Count: 100, ByLex: true}).Val())
+ require.Equal(t, []string{"omega", "hill", "great", "foo",
"elephant"}, rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "zset", Start: "[d",
Stop: "+", Offset: 0, Count: 5, ByLex: true, Rev: true}).Val())
+ require.Equal(t, []string{"omega", "hill", "great", "foo"},
rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key: "zset", Start: "[d", Stop: "+",
Offset: 0, Count: 4, ByLex: true, Rev: true}).Val())
})
t.Run("ZRANGEBYLEX with invalid lex range specifiers", func(t
*testing.T) {
@@ -505,6 +611,12 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx
context.Context, encoding s
util.ErrorRegexp(t, rdb.ZRangeByLex(ctx, "fooz",
&redis.ZRangeBy{Min: "foo", Max: "[bar"}).Err(), ".*illegal.*")
util.ErrorRegexp(t, rdb.ZRangeByLex(ctx, "fooz",
&redis.ZRangeBy{Min: "+x", Max: "[bar"}).Err(), ".*illegal.*")
util.ErrorRegexp(t, rdb.ZRangeByLex(ctx, "fooz",
&redis.ZRangeBy{Min: "-x", Max: "[bar"}).Err(), ".*illegal.*")
+ // .. in zrange extension syntax
+ util.ErrorRegexp(t, rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key:
"fooz", Start: "foo", Stop: "bar", ByLex: true}).Err(), ".*illegal.*")
+ util.ErrorRegexp(t, rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key:
"fooz", Start: "[foo", Stop: "bar", ByLex: true}).Err(), ".*illegal.*")
+ util.ErrorRegexp(t, rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key:
"fooz", Start: "foo", Stop: "[bar", ByLex: true}).Err(), ".*illegal.*")
+ util.ErrorRegexp(t, rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key:
"fooz", Start: "+x", Stop: "[bar", ByLex: true}).Err(), ".*illegal.*")
+ util.ErrorRegexp(t, rdb.ZRangeArgs(ctx, redis.ZRangeArgs{Key:
"fooz", Start: "-x", Stop: "[bar", ByLex: true}).Err(), ".*illegal.*")
})
t.Run("ZREMRANGEBYSCORE basics", func(t *testing.T) {
diff --git a/x.py b/x.py
index 70246000..71f1589f 100755
--- a/x.py
+++ b/x.py
@@ -182,7 +182,7 @@ def clang_tidy(dir: str, jobs: Optional[int],
clang_tidy_path: str, run_clang_ti
options = ['-p', dir, '-clang-tidy-binary', tidy_command]
if jobs is not None:
options.append(f'-j{jobs}')
-
+
options.extend(['-fix'] if fix else [])
regexes = ['kvrocks/src/', 'utils/kvrocks2redis/', 'tests/cppunit/']
@@ -227,7 +227,7 @@ def package_source(release_version: str,
release_candidate_number: Optional[int]
run(git, 'tag', '-a', f'v{version}', '-m', f'[source-release] copy for
tag v{version}')
else:
run(git, 'tag', '-a', f'v{version}-rc{release_candidate_number}',
'-m', f'[source-release] copy for tag v{version}-rc{release_candidate_number}')
-
+
# 2. Create the source tarball
folder = f'apache-kvrocks-{version}-incubating-src'
tarball = f'apache-kvrocks-{version}-incubating-src.tar.gz'