From 62f50b365b254b3165c3a611d7012aa4e2523936 Mon Sep 17 00:00:00 2001 From: vikonix Date: Mon, 16 Mar 2026 10:53:10 +0100 Subject: [PATCH 1/7] add $timestamp column --- quasardb/detail/ts_column.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/quasardb/detail/ts_column.hpp b/quasardb/detail/ts_column.hpp index a3fd9849..93a51ff9 100644 --- a/quasardb/detail/ts_column.hpp +++ b/quasardb/detail/ts_column.hpp @@ -147,9 +147,13 @@ static inline std::vector convert_columns( static inline std::vector convert_columns_ex( const std::vector & columns) { - std::vector res(columns.size()); + std::vector res; + res.reserve(columns.size() + 1); + // TODO(valeriy): add mandatory column $timestamp + // Remove this code when we add support for creating tables without $timestamp + res.emplace_back("$timestamp", qdb_ts_column_timestamp, nullptr); - std::transform(columns.cbegin(), columns.cend(), res.begin(), + std::transform(columns.cbegin(), columns.cend(), res.end(), [](const column_info & ci) -> qdb_ts_column_info_ex_t { return ci; }); return res; From c67874f6687fcb725d752855d9a7d31afaa54e2b Mon Sep 17 00:00:00 2001 From: vikonix Date: Mon, 16 Mar 2026 11:42:09 +0100 Subject: [PATCH 2/7] step 1 --- quasardb/detail/ts_column.hpp | 98 ++++++++++++++++++++++++++++++++--- quasardb/table.hpp | 52 ++++++++----------- tests/test_query.py | 2 +- 3 files changed, 113 insertions(+), 39 deletions(-) diff --git a/quasardb/detail/ts_column.hpp b/quasardb/detail/ts_column.hpp index 93a51ff9..422064f3 100644 --- a/quasardb/detail/ts_column.hpp +++ b/quasardb/detail/ts_column.hpp @@ -32,8 +32,13 @@ #include "../error.hpp" #include +#include #include +#include +#include #include +#include +#include namespace qdb { @@ -41,6 +46,8 @@ namespace qdb namespace detail { +inline constexpr const char * timestamp_column_name = "$timestamp"; + inline std::string type_to_string(qdb_ts_column_type_t t) { switch (t) @@ -144,16 +151,82 @@ static inline std::vector convert_columns( return res; } -static inline std::vector convert_columns_ex( + +static inline bool is_timestamp_column(column_info const & column) +{ + return column.name == timestamp_column_name; +} + +static inline void validate_timestamp_column(column_info const & column, std::size_t idx) +{ + if (is_timestamp_column(column) == false) + { + return; + } + + if (column.type != qdb_ts_column_timestamp) [[unlikely]] + { + throw qdb::invalid_argument_exception{"column '" + column.name + "' must have type timestamp"}; + } + + if (column.symtable.empty() == false) [[unlikely]] + { + throw qdb::invalid_argument_exception{ + "column '" + column.name + "' cannot define a symbol table"}; + } + + if (idx != 0) [[unlikely]] + { + throw qdb::invalid_argument_exception{ + "column '$timestamp' must be the first column in the table schema"}; + } +} + +static inline std::vector convert_create_columns_ex( const std::vector & columns) { std::vector res; res.reserve(columns.size() + 1); - // TODO(valeriy): add mandatory column $timestamp - // Remove this code when we add support for creating tables without $timestamp - res.emplace_back("$timestamp", qdb_ts_column_timestamp, nullptr); - std::transform(columns.cbegin(), columns.cend(), res.end(), + bool has_timestamp_column = false; + for (std::size_t idx = 0; idx < columns.size(); ++idx) + { + auto const & column = columns[idx]; + validate_timestamp_column(column, idx); + has_timestamp_column = has_timestamp_column || is_timestamp_column(column); + } + + if (has_timestamp_column == false) + { + res.push_back(qdb_ts_column_info_ex_t{ + timestamp_column_name, + qdb_ts_column_timestamp, + nullptr, + }); + } + + std::transform(columns.cbegin(), columns.cend(), std::back_inserter(res), + [](const column_info & ci) -> qdb_ts_column_info_ex_t { return ci; }); + + return res; +} + +static inline std::vector convert_columns_ex( + const std::vector & columns) +{ + std::vector res; + res.reserve(columns.size()); + + for (auto const & column : columns) + { + if (is_timestamp_column(column)) [[unlikely]] + { + throw qdb::invalid_argument_exception{ + "column '$timestamp' is managed by the server and cannot be added explicitly"}; + } + } + + std::transform(columns.cbegin(), columns.cend(), std::back_inserter(res), [](const column_info & ci) -> qdb_ts_column_info_ex_t { return ci; }); return res; @@ -162,10 +235,19 @@ static inline std::vector convert_columns_ex( static inline std::vector convert_columns( const qdb_ts_column_info_ex_t * columns, size_t count) { - std::vector res(count); + std::vector res; + res.reserve(count); - std::transform(columns, columns + count, res.begin(), - [](const qdb_ts_column_info_ex_t & ci) { return column_info{ci}; }); + for (size_t i = 0; i < count; ++i) + { + column_info column{columns[i]}; + if (is_timestamp_column(column) && column.type == qdb_ts_column_timestamp) + { + continue; + } + + res.push_back(std::move(column)); + } return res; } diff --git a/quasardb/table.hpp b/quasardb/table.hpp index b8ff0b71..2bdca534 100644 --- a/quasardb/table.hpp +++ b/quasardb/table.hpp @@ -75,7 +75,7 @@ class table : public entry ttl_ = ttl.count(); } - const auto c_columns = detail::convert_columns_ex(columns); + const auto c_columns = detail::convert_create_columns_ex(columns); qdb::qdb_throw_if_error(*_handle, qdb_ts_create_ex(*_handle, _alias.c_str(), shard_size.count(), c_columns.data(), c_columns.size(), ttl_)); } @@ -325,14 +325,13 @@ static inline void register_table(Module & m) py::class_{m, "Table", "Table representation"} .def(py::init([](py::args, py::kwargs) { - throw qdb::direct_instantiation_exception{"conn.table(...)"}; - return nullptr; + throw qdb::direct_instantiation_exception{"conn.table(...)"}; + return nullptr; })) .def("__repr__", &qdb::table::repr) .def("create", &qdb::table::create, py::arg("columns"), py::arg("shard_size") = std::chrono::hours{24}, - py::arg("ttl") = std::chrono::milliseconds::zero() - ) + py::arg("ttl") = std::chrono::milliseconds::zero()) .def("retrieve_metadata", &qdb::table::retrieve_metadata) .def("column_index_by_id", &qdb::table::column_index_by_id) .def("column_type_by_id", &qdb::table::column_type_by_id) @@ -345,12 +344,10 @@ static inline void register_table(Module & m) .def("get_ttl", &qdb::table::get_ttl) .def("get_shard_size", &qdb::table::get_shard_size) - .def("reader", &qdb::table::reader, - py::kw_only(), + .def("reader", &qdb::table::reader, py::kw_only(), py::arg("column_names") = std::vector{}, - py::arg("batch_size") = std::size_t{0}, - py::arg("ranges") = std::vector{} - ) + py::arg("batch_size") = std::size_t{0}, // + py::arg("ranges") = std::vector{}) .def("subscribe", &qdb::table::subscribe) .def("erase_ranges", &qdb::table::erase_ranges) @@ -360,26 +357,21 @@ static inline void register_table(Module & m) .def("int64_insert", &qdb::table::int64_insert) .def("timestamp_insert", &qdb::table::timestamp_insert) - .def("blob_get_ranges", &qdb::table::blob_get_ranges, - py::arg("column"), - py::arg("ranges") = py::none{} - ) - .def("string_get_ranges", &qdb::table::string_get_ranges, - py::arg("column"), - py::arg("ranges") = py::none{} - ) - .def("double_get_ranges", &qdb::table::double_get_ranges, - py::arg("column"), - py::arg("ranges") = py::none{} - ) - .def("int64_get_ranges", &qdb::table::int64_get_ranges, - py::arg("column"), - py::arg("ranges") = py::none{} - ) - .def("timestamp_get_ranges", &qdb::table::timestamp_get_ranges, - py::arg("column"), - py::arg("ranges") = py::none{} - ); + .def("blob_get_ranges", &qdb::table::blob_get_ranges, // + py::arg("column"), // + py::arg("ranges") = py::none{}) + .def("string_get_ranges", &qdb::table::string_get_ranges, // + py::arg("column"), // + py::arg("ranges") = py::none{}) + .def("double_get_ranges", &qdb::table::double_get_ranges, // + py::arg("column"), // + py::arg("ranges") = py::none{}) + .def("int64_get_ranges", &qdb::table::int64_get_ranges, // + py::arg("column"), // + py::arg("ranges") = py::none{}) + .def("timestamp_get_ranges", &qdb::table::timestamp_get_ranges, // + py::arg("column"), // + py::arg("ranges") = py::none{}); } } // namespace qdb diff --git a/tests/test_query.py b/tests/test_query.py index 23c51c0b..9593adde 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -415,5 +415,5 @@ def test_returns_inserted_multi_data_with_star_select( def test_create_table(qdbd_connection, entry_name): - query = 'create table "{}" (col int64)'.format(entry_name) + query = 'create table "{}" ($timestamp timestamp, col int64)'.format(entry_name) _ = qdbd_connection.query(query) From 210daffbbd053454001910ba4cb91e1084ecb434 Mon Sep 17 00:00:00 2001 From: vikonix Date: Mon, 16 Mar 2026 11:55:27 +0100 Subject: [PATCH 3/7] fix empty table --- quasardb/detail/ts_column.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/quasardb/detail/ts_column.hpp b/quasardb/detail/ts_column.hpp index 422064f3..7b3fd82b 100644 --- a/quasardb/detail/ts_column.hpp +++ b/quasardb/detail/ts_column.hpp @@ -188,6 +188,11 @@ static inline std::vector convert_create_columns_ex( std::vector res; res.reserve(columns.size() + 1); + if (columns.empty()) + { + return res; + } + bool has_timestamp_column = false; for (std::size_t idx = 0; idx < columns.size(); ++idx) { From 2ebcde55246a321cb12dc9c52cfa70628918c4e3 Mon Sep 17 00:00:00 2001 From: vikonix Date: Mon, 16 Mar 2026 19:51:43 +0100 Subject: [PATCH 4/7] Revert "fix empty table" This reverts commit 210daffbbd053454001910ba4cb91e1084ecb434. --- quasardb/detail/ts_column.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/quasardb/detail/ts_column.hpp b/quasardb/detail/ts_column.hpp index 7b3fd82b..422064f3 100644 --- a/quasardb/detail/ts_column.hpp +++ b/quasardb/detail/ts_column.hpp @@ -188,11 +188,6 @@ static inline std::vector convert_create_columns_ex( std::vector res; res.reserve(columns.size() + 1); - if (columns.empty()) - { - return res; - } - bool has_timestamp_column = false; for (std::size_t idx = 0; idx < columns.size(); ++idx) { From 12c0445c28f99b9a975965d1c8298e7941b66a60 Mon Sep 17 00:00:00 2001 From: vikonix Date: Mon, 16 Mar 2026 21:59:34 +0100 Subject: [PATCH 5/7] formatting --- quasardb/table.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/quasardb/table.hpp b/quasardb/table.hpp index 2bdca534..5e3541be 100644 --- a/quasardb/table.hpp +++ b/quasardb/table.hpp @@ -329,7 +329,8 @@ static inline void register_table(Module & m) return nullptr; })) .def("__repr__", &qdb::table::repr) - .def("create", &qdb::table::create, py::arg("columns"), + .def("create", &qdb::table::create, // + py::arg("columns"), // py::arg("shard_size") = std::chrono::hours{24}, py::arg("ttl") = std::chrono::milliseconds::zero()) .def("retrieve_metadata", &qdb::table::retrieve_metadata) @@ -344,9 +345,10 @@ static inline void register_table(Module & m) .def("get_ttl", &qdb::table::get_ttl) .def("get_shard_size", &qdb::table::get_shard_size) - .def("reader", &qdb::table::reader, py::kw_only(), - py::arg("column_names") = std::vector{}, - py::arg("batch_size") = std::size_t{0}, // + .def("reader", &qdb::table::reader, // + py::kw_only(), // + py::arg("column_names") = std::vector{}, // + py::arg("batch_size") = std::size_t{0}, // py::arg("ranges") = std::vector{}) .def("subscribe", &qdb::table::subscribe) From c43db1e9a202d1992d2843847ca9cbc76e0c74ca Mon Sep 17 00:00:00 2001 From: vikonix Date: Tue, 17 Mar 2026 09:50:53 +0100 Subject: [PATCH 6/7] fix 1 --- quasardb/detail/ts_column.hpp | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/quasardb/detail/ts_column.hpp b/quasardb/detail/ts_column.hpp index 422064f3..1803ce84 100644 --- a/quasardb/detail/ts_column.hpp +++ b/quasardb/detail/ts_column.hpp @@ -214,19 +214,8 @@ static inline std::vector convert_create_columns_ex( static inline std::vector convert_columns_ex( const std::vector & columns) { - std::vector res; - res.reserve(columns.size()); - - for (auto const & column : columns) - { - if (is_timestamp_column(column)) [[unlikely]] - { - throw qdb::invalid_argument_exception{ - "column '$timestamp' is managed by the server and cannot be added explicitly"}; - } - } - - std::transform(columns.cbegin(), columns.cend(), std::back_inserter(res), + std::vector res(columns.size()); + std::transform(columns.cbegin(), columns.cend(), res.begin(), [](const column_info & ci) -> qdb_ts_column_info_ex_t { return ci; }); return res; @@ -235,19 +224,9 @@ static inline std::vector convert_columns_ex( static inline std::vector convert_columns( const qdb_ts_column_info_ex_t * columns, size_t count) { - std::vector res; - res.reserve(count); - - for (size_t i = 0; i < count; ++i) - { - column_info column{columns[i]}; - if (is_timestamp_column(column) && column.type == qdb_ts_column_timestamp) - { - continue; - } - - res.push_back(std::move(column)); - } + std::vector res(count); + std::transform(columns, columns + count, res.begin(), + [](const qdb_ts_column_info_ex_t & ci) { return column_info{ci}; }); return res; } From 623ef44625accf1776279498167555b2859ca0dc Mon Sep 17 00:00:00 2001 From: vikonix Date: Tue, 17 Mar 2026 10:19:05 +0100 Subject: [PATCH 7/7] fix 2 --- quasardb/detail/ts_column.hpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/quasardb/detail/ts_column.hpp b/quasardb/detail/ts_column.hpp index 1803ce84..8f7cf913 100644 --- a/quasardb/detail/ts_column.hpp +++ b/quasardb/detail/ts_column.hpp @@ -159,11 +159,6 @@ static inline bool is_timestamp_column(column_info const & column) static inline void validate_timestamp_column(column_info const & column, std::size_t idx) { - if (is_timestamp_column(column) == false) - { - return; - } - if (column.type != qdb_ts_column_timestamp) [[unlikely]] { throw qdb::invalid_argument_exception{"column '" + column.name + "' must have type timestamp"}; @@ -182,20 +177,26 @@ static inline void validate_timestamp_column(column_info const & column, std::si } } -static inline std::vector convert_create_columns_ex( - const std::vector & columns) +static inline bool find_timestamp_column(const std::vector & columns) { - std::vector res; - res.reserve(columns.size() + 1); - - bool has_timestamp_column = false; for (std::size_t idx = 0; idx < columns.size(); ++idx) { auto const & column = columns[idx]; + if (!is_timestamp_column(column)) continue; + validate_timestamp_column(column, idx); - has_timestamp_column = has_timestamp_column || is_timestamp_column(column); + return true; } + return false; +} + +static inline std::vector convert_create_columns_ex( + const std::vector & columns) +{ + std::vector res; + res.reserve(columns.size() + 1); + bool has_timestamp_column = find_timestamp_column(columns); if (has_timestamp_column == false) { res.push_back(qdb_ts_column_info_ex_t{