From 52b3cd46c76fd4be18aeb8422a08a073484b9fad Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Sat, 29 Aug 2026 12:01:42 +0200 Subject: More module implementation partition units --- server/src/sqlite3.cppm | 248 ++++++++++-------------------------------------- 1 file changed, 50 insertions(+), 198 deletions(-) (limited to 'server/src/sqlite3.cppm') diff --git a/server/src/sqlite3.cppm b/server/src/sqlite3.cppm index 4623cdc..b771c49 100644 --- a/server/src/sqlite3.cppm +++ b/server/src/sqlite3.cppm @@ -1,20 +1,34 @@ -module; - -#include - export module routemon:sqlite3; import std; import :util; +extern "C" +{ + using sqlite3 = struct sqlite3; + using sqlite3_mutex = struct sqlite3_mutex; + using sqlite3_stmt = struct sqlite3_stmt; +} + namespace routemon::sqlite3 { +template concept C> +concept optional_of = requires { + typename T::value_type; + requires std::same_as>; + requires C; +}; + +template +concept scannable_prim = std::same_as || std::same_as + || std::same_as; + +template +concept scannable = scannable_prim || optional_of; + class mutex_guard { - explicit mutex_guard(::sqlite3_mutex* mut) noexcept : mut_{mut} - { - ::sqlite3_mutex_enter(mut_); - } + explicit mutex_guard(::sqlite3_mutex* mut) noexcept; friend auto do_guarded(::sqlite3_mutex* mut, std::invocable auto f) @@ -22,111 +36,54 @@ class mutex_guard public: mutex_guard(mutex_guard const&) = delete; - ~mutex_guard() { ::sqlite3_mutex_leave(mut_); } + ~mutex_guard(); private: ::sqlite3_mutex* mut_; }; -auto do_guarded(::sqlite3_mutex* mut, std::invocable auto f) - -> decltype(f(std::declval())) -{ - return f(mutex_guard{mut}); -} - -auto do_guarded(::sqlite3* dbc, std::invocable auto f) - -> decltype(f(std::declval())) -{ - return do_guarded(::sqlite3_db_mutex(dbc), f); -} - class error : public std::exception { int code_; std::string message_; public: - explicit error(mutex_guard const&, int code, ::sqlite3* dbc) - : code_{code}, message_{::sqlite3_errmsg(dbc)} - { - } - - explicit error(int code) : code_{code}, message_{::sqlite3_errstr(code)} {} - - [[nodiscard]] auto what() const noexcept -> char const* override - { - return message_.c_str(); - } + explicit error(mutex_guard const&, int code, ::sqlite3* dbc); + explicit error(int code); - [[nodiscard]] auto code() const noexcept -> int { return code_; } -}; - -template concept C> -concept optional_of = requires { - typename T::value_type; - requires std::same_as>; - requires C; + [[nodiscard]] auto what() const noexcept -> char const* override; + [[nodiscard]] auto code() const noexcept -> int; }; -template -concept scannable_prim = std::same_as || std::same_as - || std::same_as; - -template -concept scannable = scannable_prim || optional_of; - class statement { ::sqlite3_stmt* stmt_; public: - explicit statement(::sqlite3_stmt* stmt) : stmt_{stmt} {} + explicit statement(::sqlite3_stmt* stmt); statement(statement const&) = delete; - statement(statement&& s) noexcept - { - stmt_ = s.stmt_; - s.stmt_ = nullptr; - } - ~statement() { ::sqlite3_finalize(stmt_); } - auto get() -> ::sqlite3_stmt* { return stmt_; } + statement(statement&& s) noexcept; + ~statement(); + auto get() -> ::sqlite3_stmt*; }; class row_reader { statement stmt_; - explicit row_reader(statement stmt) : stmt_{std::move(stmt)} {} + explicit row_reader(statement stmt); friend class connection; - void scan(int col, std::string& s) - { - if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_TEXT) - throw std::invalid_argument{"invalid type for scan"}; - unsigned char const* chs = ::sqlite3_column_text(stmt_.get(), col); - auto size = util::size_from_int(::sqlite3_column_bytes(stmt_.get(), col)); - if (!size.has_value()) - throw std::logic_error{"unexpected negative amount of bytes in column"}; - s = std::string{reinterpret_cast(chs), *size}; - } + auto is_null(int col) -> bool; + auto ncols() -> std::size_t; - void scan(int col, double& v) + auto scan(int col, std::string& s) -> void; + auto scan(int col, double& v) -> void; + auto scan(int col, std::int64_t& v) -> void; + auto scan(int col, optional_of auto& v) -> void { - if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_FLOAT) - throw std::invalid_argument{"invalid type for scan"}; - v = ::sqlite3_column_double(stmt_.get(), col); - } - - void scan(int col, std::int64_t& v) - { - if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_INTEGER) - throw std::invalid_argument{"invalid type for scan"}; - v = ::sqlite3_column_int64(stmt_.get(), col); - } - - void scan(int col, optional_of auto& v) - { - if (::sqlite3_column_type(stmt_.get(), col) == SQLITE_NULL) + if (is_null(col)) { v.reset(); } @@ -139,28 +96,11 @@ class row_reader } public: - auto next() -> bool - { - ::sqlite3* dbc = ::sqlite3_db_handle(stmt_.get()); - return do_guarded( - dbc, - [&](auto const& guard) -> bool - { - auto const s = ::sqlite3_step(stmt_.get()); - if (s == SQLITE_ROW) - return true; - if (s == SQLITE_DONE) - return false; - throw error{guard, s, dbc}; - }); - } + auto next() -> bool; auto scan(scannable auto&... args) -> void { - auto const ncols = util::size_from_int(::sqlite3_data_count(stmt_.get())); - if (!ncols.has_value()) - throw std::logic_error{"got unexpected negative amount of columns"}; - if (sizeof...(args) > *ncols) + if (sizeof...(args) > ncols()) throw std::invalid_argument{ "more scanning arguments provided than columns in result set" }; @@ -184,30 +124,14 @@ class binder { statement& stmt_; - explicit binder(statement& stmt) : stmt_{stmt} {} + explicit binder(statement& stmt); friend class connection; public: - auto text(std::string const& param_name, std::string_view str) -> void - { - int const i = - ::sqlite3_bind_parameter_index(stmt_.get(), param_name.c_str()); - if (i == 0) - throw std::invalid_argument{std::format( - "bind: no parameter with name {} found", param_name)}; - auto str_size = util::int_from_size(str.size()); - if (!str_size.has_value()) - throw std::invalid_argument{"bind: provided text is too long"}; - if (auto s = ::sqlite3_bind_text( - stmt_.get(), i, str.data(), *str_size, SQLITE_TRANSIENT); - s != SQLITE_OK) - { - throw error{s}; - } - } + auto text(std::string const& param_name, std::string_view str) -> void; - static auto noop(binder&) -> void {} + static auto noop(binder&) -> void; }; export class connection @@ -215,97 +139,25 @@ export class connection ::sqlite3* dbc_; ::sqlite3_mutex* mut_; - explicit connection(::sqlite3* dbc) : dbc_{dbc}, mut_{::sqlite3_db_mutex(dbc)} - { - } + explicit connection(::sqlite3* dbc); friend auto open(std::string const& filename) -> connection; public: connection(connection const&) = delete; - connection(connection&& c) noexcept - { - dbc_ = c.dbc_; - mut_ = c.mut_; - c.dbc_ = nullptr; - c.mut_ = nullptr; - } + connection(connection&& c) noexcept; [[nodiscard]] auto query( std::string const& sql, - std::function const& bf = binder::noop) -> row_reader - { - ::sqlite3_stmt* pstmt = nullptr; - char const* sql_tail = nullptr; - auto sql_size = util::int_from_size(sql.size()); - if (!sql_size.has_value() - || *sql_size >= std::numeric_limits::max() - 1) - throw std::invalid_argument{"provided input text too large"}; - do_guarded( - mut_, - [&](auto const& guard) -> void - { - if (auto s = ::sqlite3_prepare_v2( - dbc_, sql.data(), *sql_size + 1, &pstmt, &sql_tail); - s != SQLITE_OK) - { - if (pstmt != nullptr) - { - // Use contract_assert when having a compiler with - // contracts available - ::sqlite3_finalize(pstmt); - throw std::logic_error{ - "expected stmt to be null after failed preparation" - }; - } - throw error{guard, s, dbc_}; - } - }); - if (!pstmt) - throw std::invalid_argument{"provided input text contains no SQL"}; - auto stmt = statement{pstmt}; - if (sql_tail && std::strlen(sql_tail) > 0) - throw std::invalid_argument{ - "provided input text contains more than one SQL statement" - }; - auto b = binder{stmt}; - bf(b); - return row_reader{std::move(stmt)}; - } + std::function const& bf = binder::noop) -> row_reader; auto exec( std::string const& sql, - std::function const& bf = binder::noop) -> void - { - auto reader = query(sql, bf); - while (reader.next()) - ; - } + std::function const& bf = binder::noop) -> void; - ~connection() { std::ignore = ::sqlite3_close(std::exchange(dbc_, nullptr)); } + ~connection(); }; -export auto open(std::string const& filename) -> connection -{ - ::sqlite3* dbc = nullptr; - auto s = ::sqlite3_open_v2( - filename.c_str(), &dbc, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX - | SQLITE_OPEN_EXRESCODE, - nullptr); - if (s != SQLITE_OK) - { - if (dbc) - { - do_guarded( - dbc, [&](auto const& guard) -> void { throw error{guard, s, dbc}; }); - } - else - { - throw error{s}; - } - } - return connection{dbc}; -} +export auto open(std::string const& filename) -> connection; } // namespace routemon::sqlite3 -- cgit v1.3