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))