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'

Reply via email to