diff options
| author | Rutger Broekhoff | 2026-08-29 12:01:42 +0200 |
|---|---|---|
| committer | Rutger Broekhoff | 2026-08-29 12:01:42 +0200 |
| commit | 52b3cd46c76fd4be18aeb8422a08a073484b9fad (patch) | |
| tree | b4f23957c096e6bce5369bb94a4e8e23de71761a /server/src/sqlite3.cpp | |
| parent | 35878b76049b9c3e752480e9f298b7e2da6fdf1f (diff) | |
| download | routemon-52b3cd46c76fd4be18aeb8422a08a073484b9fad.tar.gz routemon-52b3cd46c76fd4be18aeb8422a08a073484b9fad.zip | |
More module implementation partition units
Diffstat (limited to 'server/src/sqlite3.cpp')
| -rw-r--r-- | server/src/sqlite3.cpp | 218 |
1 files changed, 218 insertions, 0 deletions
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 @@ | |||
| 1 | module; | ||
| 2 | |||
| 3 | #include <sqlite3.h> | ||
| 4 | |||
| 5 | module routemon:sqlite3$impl; | ||
| 6 | |||
| 7 | import :sqlite3; | ||
| 8 | |||
| 9 | namespace routemon::sqlite3 { | ||
| 10 | |||
| 11 | mutex_guard::mutex_guard(::sqlite3_mutex* mut) noexcept : mut_{mut} {} | ||
| 12 | |||
| 13 | mutex_guard::~mutex_guard() { ::sqlite3_mutex_leave(mut_); } | ||
| 14 | |||
| 15 | auto do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f) | ||
| 16 | -> decltype(f(std::declval<mutex_guard const&>())) | ||
| 17 | { | ||
| 18 | return f(mutex_guard{mut}); | ||
| 19 | } | ||
| 20 | |||
| 21 | auto do_guarded(::sqlite3* dbc, std::invocable<mutex_guard const&> auto f) | ||
| 22 | -> decltype(f(std::declval<mutex_guard const&>())) | ||
| 23 | { | ||
| 24 | return do_guarded(::sqlite3_db_mutex(dbc), f); | ||
| 25 | } | ||
| 26 | |||
| 27 | error::error(mutex_guard const&, int code, ::sqlite3* dbc) | ||
| 28 | : code_{code}, message_{::sqlite3_errmsg(dbc)} | ||
| 29 | { | ||
| 30 | } | ||
| 31 | |||
| 32 | error::error(int code) : code_{code}, message_{::sqlite3_errstr(code)} {} | ||
| 33 | |||
| 34 | [[nodiscard]] auto error::what() const noexcept -> char const* | ||
| 35 | { | ||
| 36 | return message_.c_str(); | ||
| 37 | } | ||
| 38 | |||
| 39 | [[nodiscard]] auto error::code() const noexcept -> int { return code_; } | ||
| 40 | |||
| 41 | statement::statement(::sqlite3_stmt* stmt) : stmt_{stmt} {} | ||
| 42 | statement::statement(statement&& s) noexcept | ||
| 43 | { | ||
| 44 | stmt_ = s.stmt_; | ||
| 45 | s.stmt_ = nullptr; | ||
| 46 | } | ||
| 47 | statement::~statement() { ::sqlite3_finalize(stmt_); } | ||
| 48 | auto statement::get() -> ::sqlite3_stmt* { return stmt_; } | ||
| 49 | |||
| 50 | row_reader::row_reader(statement stmt) : stmt_{std::move(stmt)} {} | ||
| 51 | |||
| 52 | auto row_reader::is_null(int col) -> bool | ||
| 53 | { | ||
| 54 | return ::sqlite3_column_type(stmt_.get(), col) == SQLITE_NULL; | ||
| 55 | } | ||
| 56 | |||
| 57 | auto row_reader::ncols() -> std::size_t | ||
| 58 | { | ||
| 59 | auto const mncols = util::size_from_int(::sqlite3_data_count(stmt_.get())); | ||
| 60 | if (!mncols.has_value()) | ||
| 61 | throw std::logic_error{"got unexpected negative amount of columns"}; | ||
| 62 | return *mncols; | ||
| 63 | } | ||
| 64 | |||
| 65 | auto row_reader::scan(int col, std::string& s) -> void | ||
| 66 | { | ||
| 67 | if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_TEXT) | ||
| 68 | throw std::invalid_argument{"invalid type for scan"}; | ||
| 69 | unsigned char const* chs = ::sqlite3_column_text(stmt_.get(), col); | ||
| 70 | auto size = util::size_from_int(::sqlite3_column_bytes(stmt_.get(), col)); | ||
| 71 | if (!size.has_value()) | ||
| 72 | throw std::logic_error{"unexpected negative amount of bytes in column"}; | ||
| 73 | s = std::string{reinterpret_cast<char const*>(chs), *size}; | ||
| 74 | } | ||
| 75 | |||
| 76 | auto row_reader::scan(int col, double& v) -> void | ||
| 77 | { | ||
| 78 | if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_FLOAT) | ||
| 79 | throw std::invalid_argument{"invalid type for scan"}; | ||
| 80 | v = ::sqlite3_column_double(stmt_.get(), col); | ||
| 81 | } | ||
| 82 | |||
| 83 | auto row_reader::scan(int col, std::int64_t& v) -> void | ||
| 84 | { | ||
| 85 | if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_INTEGER) | ||
| 86 | throw std::invalid_argument{"invalid type for scan"}; | ||
| 87 | v = ::sqlite3_column_int64(stmt_.get(), col); | ||
| 88 | } | ||
| 89 | |||
| 90 | auto row_reader::next() -> bool | ||
| 91 | { | ||
| 92 | ::sqlite3* dbc = ::sqlite3_db_handle(stmt_.get()); | ||
| 93 | return do_guarded( | ||
| 94 | dbc, | ||
| 95 | [&](auto const& guard) -> bool | ||
| 96 | { | ||
| 97 | auto const s = ::sqlite3_step(stmt_.get()); | ||
| 98 | if (s == SQLITE_ROW) | ||
| 99 | return true; | ||
| 100 | if (s == SQLITE_DONE) | ||
| 101 | return false; | ||
| 102 | throw error{guard, s, dbc}; | ||
| 103 | }); | ||
| 104 | } | ||
| 105 | |||
| 106 | binder::binder(statement& stmt) : stmt_{stmt} {} | ||
| 107 | |||
| 108 | auto binder::text(std::string const& param_name, std::string_view str) -> void | ||
| 109 | { | ||
| 110 | int const i = ::sqlite3_bind_parameter_index(stmt_.get(), param_name.c_str()); | ||
| 111 | if (i == 0) | ||
| 112 | throw std::invalid_argument{std::format( | ||
| 113 | "bind: no parameter with name {} found", param_name)}; | ||
| 114 | auto str_size = util::int_from_size(str.size()); | ||
| 115 | if (!str_size.has_value()) | ||
| 116 | throw std::invalid_argument{"bind: provided text is too long"}; | ||
| 117 | if (auto s = ::sqlite3_bind_text( | ||
| 118 | stmt_.get(), i, str.data(), *str_size, SQLITE_TRANSIENT); | ||
| 119 | s != SQLITE_OK) | ||
| 120 | { | ||
| 121 | throw error{s}; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | auto binder::noop(binder&) -> void {} | ||
| 126 | |||
| 127 | connection::connection(::sqlite3* dbc) | ||
| 128 | : dbc_{dbc}, mut_{::sqlite3_db_mutex(dbc)} | ||
| 129 | { | ||
| 130 | } | ||
| 131 | |||
| 132 | connection::connection(connection const&) = delete; | ||
| 133 | connection::connection(connection&& c) noexcept | ||
| 134 | { | ||
| 135 | dbc_ = c.dbc_; | ||
| 136 | mut_ = c.mut_; | ||
| 137 | c.dbc_ = nullptr; | ||
| 138 | c.mut_ = nullptr; | ||
| 139 | } | ||
| 140 | |||
| 141 | auto connection::query( | ||
| 142 | std::string const& sql, std::function<void(binder&)> const& bf) | ||
| 143 | -> row_reader | ||
| 144 | { | ||
| 145 | ::sqlite3_stmt* pstmt = nullptr; | ||
| 146 | char const* sql_tail = nullptr; | ||
| 147 | auto sql_size = util::int_from_size(sql.size()); | ||
| 148 | if (!sql_size.has_value() || *sql_size >= std::numeric_limits<int>::max() - 1) | ||
| 149 | throw std::invalid_argument{"provided input text too large"}; | ||
| 150 | do_guarded( | ||
| 151 | mut_, | ||
| 152 | [&](auto const& guard) -> void | ||
| 153 | { | ||
| 154 | if (auto s = ::sqlite3_prepare_v2( | ||
| 155 | dbc_, sql.data(), *sql_size + 1, &pstmt, &sql_tail); | ||
| 156 | s != SQLITE_OK) | ||
| 157 | { | ||
| 158 | if (pstmt != nullptr) | ||
| 159 | { | ||
| 160 | // Use contract_assert when having a compiler with | ||
| 161 | // contracts available | ||
| 162 | ::sqlite3_finalize(pstmt); | ||
| 163 | throw std::logic_error{ | ||
| 164 | "expected stmt to be null after failed preparation" | ||
| 165 | }; | ||
| 166 | } | ||
| 167 | throw error{guard, s, dbc_}; | ||
| 168 | } | ||
| 169 | }); | ||
| 170 | if (!pstmt) | ||
| 171 | throw std::invalid_argument{"provided input text contains no SQL"}; | ||
| 172 | auto stmt = statement{pstmt}; | ||
| 173 | if (sql_tail && std::strlen(sql_tail) > 0) | ||
| 174 | throw std::invalid_argument{ | ||
| 175 | "provided input text contains more than one SQL statement" | ||
| 176 | }; | ||
| 177 | auto b = binder{stmt}; | ||
| 178 | bf(b); | ||
| 179 | return row_reader{std::move(stmt)}; | ||
| 180 | } | ||
| 181 | |||
| 182 | auto connection::exec( | ||
| 183 | std::string const& sql, std::function<void(binder&)> const& bf) -> void | ||
| 184 | { | ||
| 185 | auto reader = query(sql, bf); | ||
| 186 | while (reader.next()) | ||
| 187 | ; | ||
| 188 | } | ||
| 189 | |||
| 190 | connection::~connection() | ||
| 191 | { | ||
| 192 | std::ignore = ::sqlite3_close(std::exchange(dbc_, nullptr)); | ||
| 193 | } | ||
| 194 | |||
| 195 | auto open(std::string const& filename) -> connection | ||
| 196 | { | ||
| 197 | ::sqlite3* dbc = nullptr; | ||
| 198 | auto s = ::sqlite3_open_v2( | ||
| 199 | filename.c_str(), &dbc, | ||
| 200 | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX | ||
| 201 | | SQLITE_OPEN_EXRESCODE, | ||
| 202 | nullptr); | ||
| 203 | if (s != SQLITE_OK) | ||
| 204 | { | ||
| 205 | if (dbc) | ||
| 206 | { | ||
| 207 | do_guarded( | ||
| 208 | dbc, [&](auto const& guard) -> void { throw error{guard, s, dbc}; }); | ||
| 209 | } | ||
| 210 | else | ||
| 211 | { | ||
| 212 | throw error{s}; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | return connection{dbc}; | ||
| 216 | } | ||
| 217 | |||
| 218 | } // namespace routemon::sqlite3 | ||