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.cpp | 218 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 server/src/sqlite3.cpp (limited to 'server/src/sqlite3.cpp') diff --git a/server/src/sqlite3.cpp b/server/src/sqlite3.cpp new file mode 100644 index 0000000..3e0d64a --- /dev/null +++ b/server/src/sqlite3.cpp @@ -0,0 +1,218 @@ +module; + +#include + +module routemon:sqlite3$impl; + +import :sqlite3; + +namespace routemon::sqlite3 { + +mutex_guard::mutex_guard(::sqlite3_mutex* mut) noexcept : mut_{mut} {} + +mutex_guard::~mutex_guard() { ::sqlite3_mutex_leave(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); +} + +error::error(mutex_guard const&, int code, ::sqlite3* dbc) + : code_{code}, message_{::sqlite3_errmsg(dbc)} +{ +} + +error::error(int code) : code_{code}, message_{::sqlite3_errstr(code)} {} + +[[nodiscard]] auto error::what() const noexcept -> char const* +{ + return message_.c_str(); +} + +[[nodiscard]] auto error::code() const noexcept -> int { return code_; } + +statement::statement(::sqlite3_stmt* stmt) : stmt_{stmt} {} +statement::statement(statement&& s) noexcept +{ + stmt_ = s.stmt_; + s.stmt_ = nullptr; +} +statement::~statement() { ::sqlite3_finalize(stmt_); } +auto statement::get() -> ::sqlite3_stmt* { return stmt_; } + +row_reader::row_reader(statement stmt) : stmt_{std::move(stmt)} {} + +auto row_reader::is_null(int col) -> bool +{ + return ::sqlite3_column_type(stmt_.get(), col) == SQLITE_NULL; +} + +auto row_reader::ncols() -> std::size_t +{ + auto const mncols = util::size_from_int(::sqlite3_data_count(stmt_.get())); + if (!mncols.has_value()) + throw std::logic_error{"got unexpected negative amount of columns"}; + return *mncols; +} + +auto row_reader::scan(int col, std::string& s) -> void +{ + 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 row_reader::scan(int col, double& 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); +} + +auto row_reader::scan(int col, std::int64_t& v) -> void +{ + if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_INTEGER) + throw std::invalid_argument{"invalid type for scan"}; + v = ::sqlite3_column_int64(stmt_.get(), col); +} + +auto row_reader::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}; + }); +} + +binder::binder(statement& stmt) : stmt_{stmt} {} + +auto binder::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 binder::noop(binder&) -> void {} + +connection::connection(::sqlite3* dbc) + : dbc_{dbc}, mut_{::sqlite3_db_mutex(dbc)} +{ +} + +connection::connection(connection const&) = delete; +connection::connection(connection&& c) noexcept +{ + dbc_ = c.dbc_; + mut_ = c.mut_; + c.dbc_ = nullptr; + c.mut_ = nullptr; +} + +auto connection::query( + std::string const& sql, std::function const& bf) + -> 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)}; +} + +auto connection::exec( + std::string const& sql, std::function const& bf) -> void +{ + auto reader = query(sql, bf); + while (reader.next()) + ; +} + +connection::~connection() +{ + std::ignore = ::sqlite3_close(std::exchange(dbc_, nullptr)); +} + +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}; +} + +} // namespace routemon::sqlite3 -- cgit v1.3