vkdb
A time series database engine in C++.
Loading...
Searching...
No Matches
builder.h
1#ifndef QUERY_BUILDER_H
2#define QUERY_BUILDER_H
3
4#include <vkdb/concepts.h>
5#include <vkdb/lsm_tree.h>
6
7#include <algorithm>
8#include <cstdint>
9#include <ranges>
10#include <unordered_set>
11#include <variant>
12
13namespace vkdb {
18using TagColumns = std::unordered_set<TagKey>;
19
25template <ArithmeticNoCVRefQuals TValue>
27public:
28 using keytype = TimeSeriesKey;
29 using mapped_type = std::optional<TValue>;
30 using valuetype = std::pair<const keytype, mapped_type>;
31 using size_type = uint64_t;
32 using result_type = std::vector<valuetype>;
33
38 QueryBuilder() = delete;
39
48 explicit QueryBuilder(
49 LSMTree<TValue>& lsm_tree,
50 const TagColumns& tag_columns
51 ) noexcept
52 : lsm_tree_{lsm_tree}
53 , tag_columns_{tag_columns}
54 , query_type_{QueryType::NONE}
55 , filters_{TRUE_TIME_SERIES_KEY_FILTER} {}
56
61 QueryBuilder(QueryBuilder&&) noexcept = default;
62
67 QueryBuilder& operator=(QueryBuilder&&) noexcept = default;
68
73 QueryBuilder(const QueryBuilder&) noexcept = default;
74
79 QueryBuilder& operator=(const QueryBuilder&) noexcept = default;
80
85 ~QueryBuilder() noexcept = default;
86
97 [[nodiscard]] QueryBuilder& point(const keytype& key) {
98 validate_tags(key.tags());
99 set_query_type(QueryType::POINT);
100 query_params_ = QueryParams{PointParams{key}};
101 return *this;
102 }
103
116 [[nodiscard]] QueryBuilder& range(const keytype& start, const keytype& end) {
117 validate_tags(start.tags());
118 validate_tags(end.tags());
119 set_query_type(QueryType::RANGE);
120 query_params_ = QueryParams{RangeParams{start, end}};
121 return *this;
122 }
123
135 [[nodiscard]] QueryBuilder& filterByTag(
136 const TagKey& key,
137 const TagValue& value
138 ) {
139 validate_tags(Tag{key, value});
140 add_filter([key, value](const auto& k) {
141 return k.tags().contains(key) && k.tags().at(key) == value;
142 });
143 return *this;
144 }
145
157 template <typename... Tags>
159 [[nodiscard]] QueryBuilder& filterByAnyTags(const Tags&... tags) {
160 validate_tags(tags...);
161 add_filter([tags...](const auto& k) {
162 return ((k.tags().contains(tags.first) &&
163 k.tags().at(tags.first) == tags.second) || ...);
164 });
165 return *this;
166 }
167
179 template <typename... Tags>
181 [[nodiscard]] QueryBuilder& filterByAllTags(const Tags&... tags) {
182 validate_tags(tags...);
183 add_filter([tags...](const auto& k) {
184 return ((k.tags().contains(tags.first) &&
185 k.tags().at(tags.first) == tags.second) && ...);
186 });
187 return *this;
188 }
189
200 [[nodiscard]] QueryBuilder& filterByMetric(const Metric& metric) {
201 add_filter([metric](const auto& k) {
202 return k.metric() == metric;
203 });
204 return *this;
205 }
206
218 template <typename... Metrics>
220 [[nodiscard]] QueryBuilder& filterByAnyMetrics(const Metrics&... metrics) {
221 add_filter([metrics...](const auto& k) {
222 return ((k.metric() == metrics) || ...);
223 });
224 return *this;
225 }
226
237 [[nodiscard]] QueryBuilder& filterByTimestamp(const Timestamp& timestamp) {
238 add_filter([timestamp](const auto& k) {
239 return k.timestamp() == timestamp;
240 });
241 return *this;
242 }
243
255 template <typename... Timestamps>
258 const Timestamps&... timestamps
259 ) {
260 add_filter([timestamps...](const auto& k) {
261 return ((k.timestamp() == timestamps) || ...);
262 });
263 return *this;
264 }
265
276 [[nodiscard]] QueryBuilder& put(const keytype& key, const TValue& value) {
277 validate_tags(key.tags());
278 set_query_type(QueryType::PUT);
279 query_params_ = QueryParams{PutParams{key, value}};
280 return *this;
281 }
282
292 [[nodiscard]] QueryBuilder& remove(const keytype& key) {
293 validate_tags(key.tags());
294 set_query_type(QueryType::REMOVE);
295 query_params_ = QueryParams{RemoveParams{key}};
296 return *this;
297 }
298
308 [[nodiscard]] size_type count() {
309 setup_aggregate();
310 return std::ranges::distance(get_filtered_range());
311 }
312
323 [[nodiscard]] TValue sum() {
324 setup_aggregate();
325 auto range{get_nonempty_filtered_range()};
326 return std::accumulate(range.begin(), range.end(), TValue{},
327 [](const TValue& acc, const valuetype& entry) {
328 return acc + entry.second.value();
329 });
330 }
331
342 [[nodiscard]] double avg() {
343 setup_aggregate();
344 auto range{get_nonempty_filtered_range()};
345 auto sum{std::accumulate(range.begin(), range.end(), TValue{},
346 [](const auto& acc, const auto& entry) {
347 return acc + entry.second.value();
348 })};
349 return static_cast<double>(sum) / std::ranges::distance(range);
350 }
351
362 [[nodiscard]] TValue min() {
363 setup_aggregate();
364 auto range{get_nonempty_filtered_range()};
365 return std::ranges::min_element(range, {}, [](const auto& entry) {
366 return entry.second.value();
367 })->second.value();
368 }
369
380 [[nodiscard]] TValue max() {
381 setup_aggregate();
382 auto range{get_nonempty_filtered_range()};
383 return std::ranges::max_element(range, {}, [](const auto& entry) {
384 return entry.second.value();
385 })->second.value();
386 }
387
398 result_type execute() {
399 switch (query_type_) {
400 case QueryType::NONE:
401 if (filters_.size() == 1) {
402 throw std::runtime_error{
403 "QueryBuilder::execute(): No query type specified."
404 };
405 }
406 set_default_range_if_none();
407 return get_filtered_range();
408 case QueryType::POINT:
409 return execute_point_query();
410 case QueryType::RANGE:
411 return execute_range_query();
412 case QueryType::PUT:
413 return execute_put_query();
414 case QueryType::REMOVE:
415 return execute_remove_query();
416 }
417 throw std::runtime_error{
418 "QueryBuilder::execute(): Invalid query type."
419 };
420 }
421
422private:
427 enum class QueryType { NONE, POINT, RANGE, PUT, REMOVE };
428
433 struct PointParams {
438 keytype key;
439 };
440
445 struct RangeParams {
450 keytype start;
451
456 keytype end;
457 };
458
463 struct PutParams {
468 keytype key;
469
474 TValue value;
475 };
476
481 struct RemoveParams {
486 keytype key;
487 };
488
494 using QueryParams = std::variant<
495 std::monostate,
496 PointParams,
497 RangeParams,
498 PutParams,
499 RemoveParams
500 >;
501
509 void set_query_type(QueryType query_type) {
510 if (query_type_ != QueryType::NONE) {
511 throw std::runtime_error{
512 "QueryBuilder::set_query_type(): Query type already set."
513 };
514 }
515 query_type_ = query_type;
516 }
517
524 void set_default_range_if_none() noexcept {
525 if (query_type_ == QueryType::NONE) {
526 query_params_ = QueryParams{RangeParams{
527 MIN_TIME_SERIES_KEY,
528 MAX_TIME_SERIES_KEY
529 }};
530 query_type_ = QueryType::RANGE;
531 }
532 }
533
541 void check_if_aggregable() const {
542 if (query_type_ != QueryType::RANGE && query_type_ != QueryType::POINT) {
543 throw std::runtime_error{
544 "QueryBuilder::check_if_aggregable(): Query type must be aggregable."
545 };
546 }
547 }
548
556 void setup_aggregate() {
557 set_default_range_if_none();
558 check_if_aggregable();
559 }
560
566 void add_filter(TimeSeriesKeyFilter&& filter) {
567 filters_.push_back(std::move(filter));
568 }
569
576 [[nodiscard]] result_type get_filtered_range() const {
577 if (query_type_ == QueryType::POINT) {
578 return execute_point_query();
579 }
580 const auto& params{std::get<RangeParams>(query_params_)};
581 return lsm_tree_.getRange(params.start, params.end,
582 [this](const auto& k) {
583 return std::ranges::all_of(filters_, [&k](const auto& filter) {
584 return filter(k);
585 });
586 });
587 }
588
597 [[nodiscard]] result_type get_nonempty_filtered_range() const {
598 auto filtered_range{get_filtered_range()};
599 if (filtered_range.empty()) {
600 throw std::runtime_error{
601 "QueryBuilder::get_nonempty_filtered_range(): "
602 "Cannot aggregate on empty range."
603 };
604 }
605 return filtered_range;
606 }
607
615 [[nodiscard]] result_type execute_point_query() const {
616 const auto& params{std::get<PointParams>(query_params_)};
617 auto value{lsm_tree_.get(params.key)};
618 if (!value.has_value()) {
619 return {};
620 }
621 return {{params.key, value}};
622 }
623
631 [[nodiscard]] result_type execute_range_query() const {
632 auto range{get_filtered_range()};
633 return {range.begin(), range.end()};
634 }
635
643 [[nodiscard]] result_type execute_put_query() {
644 const auto& params{std::get<PutParams>(query_params_)};
645 lsm_tree_.put(params.key, params.value);
646 return {};
647 }
648
656 [[nodiscard]] result_type execute_remove_query() {
657 const auto& params{std::get<RemoveParams>(query_params_)};
658 lsm_tree_.remove(params.key);
659 return {};
660 }
661
670 void validate_tags(const TagTable& tag_table) const {
671 for (const auto& [key, value] : tag_table) {
672 if (!tag_columns_.contains(key)) {
673 throw std::runtime_error{
674 "QueryBuilder::validate_tags(): Tag '"
675 + key + "' not in tag columns."
676 };
677 }
678 }
679 }
680
690 template <typename... Tags>
692 void validate_tags(const Tags&... tags) const {
693 for (const auto& [key, value] : std::initializer_list<Tag>{tags...}) {
694 if (!tag_columns_.contains(key)) {
695 throw std::runtime_error{
696 "QueryBuilder::validate_tags(): Tag '"
697 + key + "' not in tag columns."
698 };
699 }
700 }
701 }
702
707 LSMTree<TValue>& lsm_tree_;
708
713 const TagColumns& tag_columns_;
714
719 QueryType query_type_;
720
725 QueryParams query_params_;
726
731 std::vector<TimeSeriesKeyFilter> filters_;
732};
733} // namespace vkdb
734
735#endif // QUERY_BUILDER_H
LSM tree on TimeSeriesKey.
Definition wal_lsm.h:8
Query builder for querying a Table.
Definition builder.h:26
QueryBuilder & put(const keytype &key, const TValue &value)
Put a key-value pair into the LSMTree.
Definition builder.h:276
QueryBuilder & range(const keytype &start, const keytype &end)
Configure builder for range query.
Definition builder.h:116
TValue min()
Calculate the minimum value in the range.
Definition builder.h:362
QueryBuilder & filterByMetric(const Metric &metric)
Filter the TimeSeriesKeys by a metric.
Definition builder.h:200
QueryBuilder & filterByAnyMetrics(const Metrics &... metrics)
Filter the TimeSeriesKeys by multiple metrics.
Definition builder.h:220
QueryBuilder & point(const keytype &key)
Configure builder for point query.
Definition builder.h:97
QueryBuilder & filterByAllTags(const Tags &... tags)
Filter the TimeSeriesKeys by multiple tags.
Definition builder.h:181
size_type count()
Count the number of entries in the range.
Definition builder.h:308
double avg()
Calculate the average of the values in the range.
Definition builder.h:342
result_type execute()
Execute the query.
Definition builder.h:398
QueryBuilder & filterByTag(const TagKey &key, const TagValue &value)
Filter the TimeSeriesKeys by a tag.
Definition builder.h:135
QueryBuilder & filterByAnyTimestamps(const Timestamps &... timestamps)
Filter the TimeSeriesKeys by multiple timestamps.
Definition builder.h:257
QueryBuilder & remove(const keytype &key)
Remove a key from the LSMTree.
Definition builder.h:292
TValue max()
Calculate the maximum value in the range.
Definition builder.h:380
QueryBuilder & filterByAnyTags(const Tags &... tags)
Filter the TimeSeriesKeys by multiple tags.
Definition builder.h:159
TValue sum()
Sum the values in the range.
Definition builder.h:323
QueryBuilder(QueryBuilder &&) noexcept=default
Move-construct a QueryBuilder object.
QueryBuilder()=delete
Deleted default constructor.
QueryBuilder & filterByTimestamp(const Timestamp &timestamp)
Filter the TimeSeriesKeys by a timestamp.
Definition builder.h:237
QueryBuilder(LSMTree< TValue > &lsm_tree, const TagColumns &tag_columns) noexcept
Construct a new QueryBuilder object.
Definition builder.h:48
Represents a key in vkdb.
const TagTable & tags() const noexcept
Get the tags.
Concept for types that are all convertible to another and have no cv- or ref-qualifiers.
Definition concepts.h:79