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; friend auto do_guarded(::sqlite3_mutex* mut, std::invocable auto f) -> decltype(f(std::declval())); public: mutex_guard(mutex_guard const&) = delete; ~mutex_guard(); private: ::sqlite3_mutex* mut_; }; class error : public std::exception { int code_; std::string message_; public: explicit error(mutex_guard const&, int code, ::sqlite3* dbc); explicit error(int code); [[nodiscard]] auto what() const noexcept -> char const* override; [[nodiscard]] auto code() const noexcept -> int; }; class statement { ::sqlite3_stmt* stmt_; public: explicit statement(::sqlite3_stmt* stmt); statement(statement const&) = delete; statement(statement&& s) noexcept; ~statement(); auto get() -> ::sqlite3_stmt*; }; class row_reader { statement stmt_; explicit row_reader(statement stmt); friend class connection; auto is_null(int col) -> bool; auto ncols() -> std::size_t; 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 (is_null(col)) { v.reset(); } else { typename std::remove_cvref_t::value_type tmp; scan(col, tmp); v = std::move(tmp); } } public: auto next() -> bool; auto scan(scannable auto&... args) -> void { if (sizeof...(args) > ncols()) throw std::invalid_argument{ "more scanning arguments provided than columns in result set" }; auto col = 0; (..., scan(col++, args)); } auto scan_single(scannable auto&... args) -> void { if (!next()) throw std::logic_error{"no row in result set"}; scan(args...); if (next()) { throw std::logic_error{"more than one row in result set"}; } } }; class binder { statement& stmt_; explicit binder(statement& stmt); friend class connection; public: auto text(std::string const& param_name, std::string_view str) -> void; static auto noop(binder&) -> void; }; export class connection { ::sqlite3* dbc_; ::sqlite3_mutex* mut_; explicit connection(::sqlite3* dbc); friend auto open(std::string const& filename) -> connection; public: connection(connection const&) = delete; connection(connection&& c) noexcept; [[nodiscard]] auto query( std::string const& sql, std::function const& bf = binder::noop) -> row_reader; auto exec( std::string const& sql, std::function const& bf = binder::noop) -> void; ~connection(); }; export auto open(std::string const& filename) -> connection; } // namespace routemon::sqlite3