@@ -126,6 +126,17 @@ std::string FormatCreateRuleResAsRedisReply(TSCreateRuleResult res) {
126
126
return " " ;
127
127
}
128
128
129
+ std::string FormatTSLabelListAsRedisReply (const redis::LabelKVList &labels) {
130
+ std::vector<std::string> labels_str;
131
+ labels_str.reserve (labels.size ());
132
+ for (const auto &label : labels) {
133
+ auto str = redis::Array (
134
+ {redis::BulkString (label.k ), label.v .size () ? redis::BulkString (label.v ) : redis::NilString (redis::RESP::v3)});
135
+ labels_str.push_back (str);
136
+ }
137
+ return redis::Array (labels_str);
138
+ }
139
+
129
140
} // namespace
130
141
131
142
namespace redis {
@@ -137,23 +148,14 @@ class KeywordCommandBase : public Commander {
137
148
Status Parse (const std::vector<std::string> &args) override {
138
149
TSOptionsParser parser (std::next (args.begin (), static_cast <std::ptrdiff_t >(skip_num_)),
139
150
std::prev (args.end (), static_cast <std::ptrdiff_t >(tail_skip_num_)));
140
-
141
151
while (parser.Good ()) {
142
- bool handled = false ;
143
- for (const auto &handler : handlers_) {
144
- if (parser.EatEqICase (handler.first )) {
145
- Status s = handler.second (parser);
146
- if (!s.IsOK ()) return s;
147
- handled = true ;
148
- break ;
149
- }
150
- }
151
-
152
- if (!handled) {
153
- parser.Skip (1 );
152
+ auto &value = parser.RawTake ();
153
+ auto value_upper = util::ToUpper (value);
154
+ if (containsKeyword (value_upper, true )) {
155
+ Status s = handlers_[value_upper](parser);
156
+ if (!s.IsOK ()) return s;
154
157
}
155
158
}
156
-
157
159
return Commander::Parse (args);
158
160
}
159
161
@@ -162,20 +164,24 @@ class KeywordCommandBase : public Commander {
162
164
163
165
template <typename Handler>
164
166
void registerHandler (const std::string &keyword, Handler &&handler) {
165
- handlers_.emplace_back ( keyword, std::forward<Handler>(handler));
167
+ handlers_.emplace ( util::ToUpper ( keyword) , std::forward<Handler>(handler));
166
168
}
167
-
168
169
virtual void registerDefaultHandlers () = 0;
169
170
170
171
void setSkipNum (size_t num) { skip_num_ = num; }
171
-
172
172
void setTailSkipNum (size_t num) { tail_skip_num_ = num; }
173
+ bool containsKeyword (const std::string &keyword, bool is_upper = false ) const {
174
+ if (is_upper) {
175
+ return handlers_.count (keyword);
176
+ } else {
177
+ return handlers_.count (util::ToUpper (keyword));
178
+ }
179
+ }
173
180
174
181
private:
175
182
size_t skip_num_ = 0 ;
176
183
size_t tail_skip_num_ = 0 ;
177
-
178
- std::vector<std::pair<std::string, std::function<Status(TSOptionsParser &)>>> handlers_;
184
+ std::unordered_map<std::string, std::function<Status(TSOptionsParser &)>> handlers_;
179
185
};
180
186
181
187
class CommandTSCreateBase : public KeywordCommandBase {
@@ -313,13 +319,7 @@ class CommandTSInfo : public Commander {
313
319
*output += redis::SimpleString (" duplicatePolicy" );
314
320
*output += redis::SimpleString (FormatDuplicatePolicyAsRedisReply (info.metadata .duplicate_policy ));
315
321
*output += redis::SimpleString (" labels" );
316
- std::vector<std::string> labels_str;
317
- labels_str.reserve (info.labels .size ());
318
- for (const auto &label : info.labels ) {
319
- auto str = redis::Array ({redis::BulkString (label.k ), redis::BulkString (label.v )});
320
- labels_str.push_back (str);
321
- }
322
- *output += redis::Array (labels_str);
322
+ *output += FormatTSLabelListAsRedisReply (info.labels );
323
323
*output += redis::SimpleString (" sourceKey" );
324
324
*output += info.metadata .source_key .empty () ? redis::NilString (redis::RESP::v3)
325
325
: redis::BulkString (info.metadata .source_key );
@@ -784,12 +784,93 @@ class CommandTSGet : public CommandTSAggregatorBase {
784
784
std::string user_key_;
785
785
};
786
786
787
+ class CommandTSMGetBase : public CommandTSAggregatorBase {
788
+ public:
789
+ CommandTSMGetBase (size_t skip_num, size_t tail_skip_num) : CommandTSAggregatorBase(skip_num, tail_skip_num) {}
790
+
791
+ protected:
792
+ static Status handleWithLabels ([[maybe_unused]] TSOptionsParser &parser, bool &with_labels) {
793
+ with_labels = true ;
794
+ return Status::OK ();
795
+ }
796
+ Status handleSelectedLabels (TSOptionsParser &parser, std::set<std::string> &selected_labels) {
797
+ while (parser.Good ()) {
798
+ auto &value = parser.RawPeek ();
799
+ if (containsKeyword (value)) {
800
+ break ;
801
+ }
802
+ selected_labels.emplace (parser.TakeStr ().GetValue ());
803
+ }
804
+ return Status::OK ();
805
+ }
806
+ Status handleFilterExpr (TSOptionsParser &parser, TSMGetOption::FilterOption &filter_option) {
807
+ auto filter_parser = TSMQueryFilterParser (filter_option);
808
+ while (parser.Good ()) {
809
+ auto &value = parser.RawPeek ();
810
+ if (containsKeyword (value)) {
811
+ break ;
812
+ }
813
+ auto s = filter_parser.Parse (parser.TakeStr ().GetValue ());
814
+ if (!s.IsOK ()) return s;
815
+ }
816
+ return filter_parser.Check ();
817
+ }
818
+ };
819
+
820
+ class CommandTSMGet : public CommandTSMGetBase {
821
+ public:
822
+ CommandTSMGet () : CommandTSMGetBase(0 , 0 ) { registerDefaultHandlers (); }
823
+ Status Parse (const std::vector<std::string> &args) override {
824
+ if (args.size () < 3 ) {
825
+ return {Status::RedisParseErr, " wrong number of arguments for 'ts.mget' command" };
826
+ }
827
+ return CommandTSMGetBase::Parse (args);
828
+ }
829
+ Status Execute (engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
830
+ auto timeseries_db = TimeSeries (srv->storage , conn->GetNamespace ());
831
+ std::vector<TSMGetResult> results;
832
+ auto s = timeseries_db.MGet (ctx, option_, is_return_latest_, &results);
833
+ if (!s.ok ()) return {Status::RedisExecErr, s.ToString ()};
834
+ std::vector<std::string> reply;
835
+ reply.reserve (results.size ());
836
+ for (auto &result : results) {
837
+ std::vector<std::string> entry (3 );
838
+ entry[0 ] = redis::BulkString (result.name );
839
+ entry[1 ] = FormatTSLabelListAsRedisReply (result.labels );
840
+ std::vector<std::string> temp;
841
+ for (auto &sample : result.samples ) {
842
+ temp.push_back (FormatTSSampleAsRedisReply (sample));
843
+ }
844
+ entry[2 ] = redis::Array (temp);
845
+ reply.push_back (redis::Array (entry));
846
+ }
847
+ *output = redis::Array (reply);
848
+ return Status::OK ();
849
+ }
850
+
851
+ protected:
852
+ void registerDefaultHandlers () override {
853
+ CommandTSAggregatorBase::registerDefaultHandlers ();
854
+ registerHandler (" LATEST" , [this ](TSOptionsParser &parser) { return handleLatest (parser, is_return_latest_); });
855
+ registerHandler (" WITHLABELS" ,
856
+ [this ](TSOptionsParser &parser) { return handleWithLabels (parser, option_.with_labels ); });
857
+ registerHandler (" SELECTED_LABELS" ,
858
+ [this ](TSOptionsParser &parser) { return handleSelectedLabels (parser, option_.selected_labels ); });
859
+ registerHandler (" FILTER" , [this ](TSOptionsParser &parser) { return handleFilterExpr (parser, option_.filter ); });
860
+ }
861
+
862
+ private:
863
+ TSMGetOption option_;
864
+ bool is_return_latest_ = false ;
865
+ };
866
+
787
867
REDIS_REGISTER_COMMANDS (Timeseries, MakeCmdAttr<CommandTSCreate>(" ts.create" , -2 , " write" , 1 , 1 , 1 ),
788
868
MakeCmdAttr<CommandTSAdd>(" ts.add" , -4 , " write" , 1 , 1 , 1 ),
789
869
MakeCmdAttr<CommandTSMAdd>(" ts.madd" , -4 , " write" , 1 , -3 , 1 ),
790
870
MakeCmdAttr<CommandTSRange>(" ts.range" , -4 , " read-only" , 1 , 1 , 1 ),
791
871
MakeCmdAttr<CommandTSInfo>(" ts.info" , -2 , " read-only" , 1 , 1 , 1 ),
792
872
MakeCmdAttr<CommandTSGet>(" ts.get" , -2 , " read-only" , 1 , 1 , 1 ),
793
- MakeCmdAttr<CommandTSCreateRule>(" ts.createrule" , -6 , " write" , 1 , 2 , 1 ), );
873
+ MakeCmdAttr<CommandTSCreateRule>(" ts.createrule" , -6 , " write" , 1 , 2 , 1 ),
874
+ MakeCmdAttr<CommandTSMGet>(" ts.mget" , -3 , " read-only" , NO_KEY), );
794
875
795
876
} // namespace redis
0 commit comments