This is an automated email from the ASF dual-hosted git repository.

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new c781be99c feat(ts): add the support of TS.REVRANGE command (#3236)
c781be99c is described below

commit c781be99cab409fdda9937759117e39a267b9f6b
Author: Raja Nandhan <[email protected]>
AuthorDate: Mon Oct 27 09:33:12 2025 -0500

    feat(ts): add the support of TS.REVRANGE command (#3236)
    
    TimeSeries::RevRange() calls TimeSeries::Range() internally.
    
    ---------
    
    Co-authored-by: var-nan <[email protected]>
    Co-authored-by: RX Xiao <[email protected]>
---
 src/commands/cmd_timeseries.cc                     | 23 ++++++++++++++++++++++
 src/types/redis_timeseries.cc                      |  7 +++++++
 src/types/redis_timeseries.h                       |  2 ++
 tests/cppunit/types/timeseries_test.cc             | 12 +++++++++++
 .../gocase/unit/type/timeseries/timeseries_test.go |  9 +++++++++
 5 files changed, 53 insertions(+)

diff --git a/src/commands/cmd_timeseries.cc b/src/commands/cmd_timeseries.cc
index b1ba01984..0602327f6 100644
--- a/src/commands/cmd_timeseries.cc
+++ b/src/commands/cmd_timeseries.cc
@@ -768,6 +768,28 @@ class CommandTSRange : public CommandTSRangeBase {
   }
 };
 
+class CommandTSRevRange : public CommandTSRangeBase {
+ public:
+  CommandTSRevRange() : CommandTSRangeBase(2) { registerDefaultHandlers(); }
+
+  Status Parse(const std::vector<std::string> &args) override {
+    if (args.size() < 4) return {Status::RedisParseErr, "wrong number of 
arguments for 'TS.REVRANGE' command"};
+    return CommandTSRangeBase::Parse(args);
+  }
+
+  Status Execute(engine::Context &ctx, Server *srv, Connection *conn, 
std::string *output) override {
+    auto timeseries_db = TimeSeries(srv->storage, conn->GetNamespace());
+    std::vector<TSSample> res;
+    auto s = timeseries_db.RevRange(ctx, args_[1], getRangeOption(), &res);
+    if (!s.ok()) return {Status::RedisExecErr, errKeyNotFound};
+    std::vector<std::string> reply;
+    reply.reserve(res.size());
+    for (auto &sample : res) 
reply.push_back(FormatTSSampleAsRedisReply(sample));
+    *output = redis::Array(reply);
+    return Status::OK();
+  }
+};
+
 class CommandTSCreateRule : public CommandTSAggregatorBase {
  public:
   explicit CommandTSCreateRule() { registerDefaultHandlers(); }
@@ -1155,6 +1177,7 @@ REDIS_REGISTER_COMMANDS(Timeseries, 
MakeCmdAttr<CommandTSCreate>("ts.create", -2
                         MakeCmdAttr<CommandTSAdd>("ts.add", -4, "write", 1, 1, 
1),
                         MakeCmdAttr<CommandTSMAdd>("ts.madd", -4, "write", 1, 
-3, 1),
                         MakeCmdAttr<CommandTSRange>("ts.range", -4, 
"read-only", 1, 1, 1),
+                        MakeCmdAttr<CommandTSRevRange>("ts.revrange", -4, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandTSInfo>("ts.info", -2, "read-only", 
1, 1, 1),
                         MakeCmdAttr<CommandTSGet>("ts.get", -2, "read-only", 
1, 1, 1),
                         MakeCmdAttr<CommandTSCreateRule>("ts.createrule", -6, 
"write", 1, 2, 1),
diff --git a/src/types/redis_timeseries.cc b/src/types/redis_timeseries.cc
index bf9f12d19..c5faae361 100644
--- a/src/types/redis_timeseries.cc
+++ b/src/types/redis_timeseries.cc
@@ -1984,6 +1984,13 @@ rocksdb::Status TimeSeries::Range(engine::Context &ctx, 
const Slice &user_key, c
   return s;
 }
 
+rocksdb::Status TimeSeries::RevRange(engine::Context &ctx, const Slice 
&user_key, const TSRangeOption &option,
+                                     std::vector<TSSample> *res) {
+  auto s = Range(ctx, user_key, option, res);
+  if (!res->empty()) std::reverse(res->begin(), res->end());
+  return s;
+}
+
 rocksdb::Status TimeSeries::Get(engine::Context &ctx, const Slice &user_key, 
bool is_return_latest,
                                 std::vector<TSSample> *res) {
   res->clear();
diff --git a/src/types/redis_timeseries.h b/src/types/redis_timeseries.h
index dda645f41..45a99bb0b 100644
--- a/src/types/redis_timeseries.h
+++ b/src/types/redis_timeseries.h
@@ -274,6 +274,8 @@ class TimeSeries : public SubKeyScanner {
   rocksdb::Status Info(engine::Context &ctx, const Slice &user_key, 
TSInfoResult *res);
   rocksdb::Status Range(engine::Context &ctx, const Slice &user_key, const 
TSRangeOption &option,
                         std::vector<TSSample> *res);
+  rocksdb::Status RevRange(engine::Context &ctx, const Slice &user_key, const 
TSRangeOption &option,
+                           std::vector<TSSample> *res);
   rocksdb::Status Get(engine::Context &ctx, const Slice &user_key, bool 
is_return_latest, std::vector<TSSample> *res);
   rocksdb::Status CreateRule(engine::Context &ctx, const Slice &src_key, const 
Slice &dst_key,
                              const TSAggregator &aggregator, 
TSCreateRuleResult *res);
diff --git a/tests/cppunit/types/timeseries_test.cc 
b/tests/cppunit/types/timeseries_test.cc
index 2d341b51a..a268e6706 100644
--- a/tests/cppunit/types/timeseries_test.cc
+++ b/tests/cppunit/types/timeseries_test.cc
@@ -148,6 +148,18 @@ TEST_F(TimeSeriesTest, Range) {
     EXPECT_EQ(res[i + samples1.size() + samples2.size()], samples3[i]);
   }
 
+  // Test RevRange query without aggregation
+  res.clear();
+  s = ts_db_->RevRange(*ctx_, key_, range_opt, &res);
+  EXPECT_TRUE(s.ok());
+  EXPECT_EQ(res.size(), 9);
+  size_t curr = 0;
+  for (auto current = samples3.rbegin(); current != samples3.rend(); 
++current) EXPECT_EQ(res[curr++], *current);
+
+  for (auto current = samples2.rbegin(); current != samples2.rend(); 
++current) EXPECT_EQ(res[curr++], *current);
+
+  for (auto current = samples1.rbegin(); current != samples1.rend(); 
++current) EXPECT_EQ(res[curr++], *current);
+
   // Test aggregation with min
   res.clear();
   range_opt.aggregator.type = redis::TSAggregatorType::MIN;
diff --git a/tests/gocase/unit/type/timeseries/timeseries_test.go 
b/tests/gocase/unit/type/timeseries/timeseries_test.go
index b4b11f850..ee116e536 100644
--- a/tests/gocase/unit/type/timeseries/timeseries_test.go
+++ b/tests/gocase/unit/type/timeseries/timeseries_test.go
@@ -317,6 +317,15 @@ func testTimeSeries(t *testing.T, configs 
util.KvrocksServerConfigs) {
                        assert.Equal(t, s.val, arr[1])
                }
 
+               // Test revrange without aggregation
+               res = rdb.Do(ctx, "ts.revrange", key, "-", 
"+").Val().([]interface{})
+               assert.Equal(t, len(samples), len(res))
+               for i, s := range samples {
+                       arr := res[len(samples)-i-1].([]interface{})
+                       assert.Equal(t, s.ts, arr[0])
+                       assert.Equal(t, s.val, arr[1])
+               }
+
                // Test MIN aggregation with 20ms bucket
                res = rdb.Do(ctx, "ts.range", key, "-", "+", "AGGREGATION", 
"MIN", 20).Val().([]interface{})
                assert.Equal(t, 6, len(res))

Reply via email to