diff options
| author | Rutger Broekhoff | 2026-08-28 21:12:55 +0200 |
|---|---|---|
| committer | Rutger Broekhoff | 2026-08-28 21:12:55 +0200 |
| commit | a5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac (patch) | |
| tree | b4e0ec57c3799e9f649c6bfe27cb6c3313b97364 /server/src/sqlite3.cppm | |
| parent | 973aec43ea54bbf95b64fbcb636403401d1ca60e (diff) | |
| download | routemon-a5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac.tar.gz routemon-a5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac.zip | |
clang-format C++ sources
Diffstat (limited to 'server/src/sqlite3.cppm')
| -rw-r--r-- | server/src/sqlite3.cppm | 454 |
1 files changed, 254 insertions, 200 deletions
diff --git a/server/src/sqlite3.cppm b/server/src/sqlite3.cppm index f226c6b..70f58c0 100644 --- a/server/src/sqlite3.cppm +++ b/server/src/sqlite3.cppm | |||
| @@ -9,249 +9,303 @@ import :util; | |||
| 9 | 9 | ||
| 10 | namespace routemon::sqlite3 { | 10 | namespace routemon::sqlite3 { |
| 11 | 11 | ||
| 12 | class mutex_guard { | 12 | class mutex_guard |
| 13 | explicit mutex_guard(::sqlite3_mutex* mut) noexcept : mut_{mut} { | 13 | { |
| 14 | ::sqlite3_mutex_enter(mut_); | 14 | explicit mutex_guard(::sqlite3_mutex* mut) noexcept : mut_{mut} |
| 15 | } | 15 | { |
| 16 | ::sqlite3_mutex_enter(mut_); | ||
| 17 | } | ||
| 16 | 18 | ||
| 17 | friend auto do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f) -> decltype(f(std::declval<mutex_guard const&>())); | 19 | friend auto |
| 20 | do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f) | ||
| 21 | -> decltype(f(std::declval<mutex_guard const&>())); | ||
| 18 | 22 | ||
| 19 | public: | 23 | public: |
| 20 | mutex_guard(mutex_guard const&) = delete; | 24 | mutex_guard(mutex_guard const&) = delete; |
| 21 | ~mutex_guard() { | 25 | ~mutex_guard() { ::sqlite3_mutex_leave(mut_); } |
| 22 | ::sqlite3_mutex_leave(mut_); | ||
| 23 | } | ||
| 24 | 26 | ||
| 25 | private: | 27 | private: |
| 26 | ::sqlite3_mutex* mut_; | 28 | ::sqlite3_mutex* mut_; |
| 27 | }; | 29 | }; |
| 28 | 30 | ||
| 29 | auto do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f) -> decltype(f(std::declval<mutex_guard const&>())) { | 31 | auto do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f) |
| 30 | return f(mutex_guard{mut}); | 32 | -> decltype(f(std::declval<mutex_guard const&>())) |
| 31 | } | 33 | { |
| 34 | return f(mutex_guard{mut}); | ||
| 35 | } | ||
| 32 | 36 | ||
| 33 | auto do_guarded(::sqlite3* dbc, std::invocable<mutex_guard const&> auto f) -> decltype(f(std::declval<mutex_guard const&>())) { | 37 | auto do_guarded(::sqlite3* dbc, std::invocable<mutex_guard const&> auto f) |
| 34 | return do_guarded(::sqlite3_db_mutex(dbc), f); | 38 | -> decltype(f(std::declval<mutex_guard const&>())) |
| 35 | } | 39 | { |
| 40 | return do_guarded(::sqlite3_db_mutex(dbc), f); | ||
| 41 | } | ||
| 36 | 42 | ||
| 37 | class error : public std::exception { | 43 | class error : public std::exception |
| 38 | int code_; | 44 | { |
| 39 | std::string message_; | 45 | int code_; |
| 46 | std::string message_; | ||
| 40 | 47 | ||
| 41 | public: | 48 | public: |
| 42 | explicit error(mutex_guard const&, int code, ::sqlite3* dbc) | 49 | explicit error(mutex_guard const&, int code, ::sqlite3* dbc) |
| 43 | : code_{code}, message_{::sqlite3_errmsg(dbc)} | 50 | : code_{code}, message_{::sqlite3_errmsg(dbc)} |
| 44 | {} | 51 | { |
| 52 | } | ||
| 45 | 53 | ||
| 46 | explicit error(int code) | 54 | explicit error(int code) : code_{code}, message_{::sqlite3_errstr(code)} {} |
| 47 | : code_{code}, message_{::sqlite3_errstr(code)} | ||
| 48 | {} | ||
| 49 | 55 | ||
| 50 | [[nodiscard]] auto what() const noexcept -> char const* override { | 56 | [[nodiscard]] auto what() const noexcept -> char const* override |
| 51 | return message_.c_str(); | 57 | { |
| 52 | } | 58 | return message_.c_str(); |
| 59 | } | ||
| 53 | 60 | ||
| 54 | [[nodiscard]] auto code() const noexcept -> int { | 61 | [[nodiscard]] auto code() const noexcept -> int { return code_; } |
| 55 | return code_; | 62 | }; |
| 56 | } | ||
| 57 | }; | ||
| 58 | 63 | ||
| 59 | template<class T, template<class U> concept C> | 64 | template <class T, template <class U> concept C> |
| 60 | concept optional_of = requires { | 65 | concept optional_of = requires { |
| 61 | typename T::value_type; | 66 | typename T::value_type; |
| 62 | requires std::same_as<T, std::optional<typename T::value_type>>; | 67 | requires std::same_as<T, std::optional<typename T::value_type>>; |
| 63 | requires C<typename T::value_type>; | 68 | requires C<typename T::value_type>; |
| 64 | }; | 69 | }; |
| 65 | 70 | ||
| 66 | template<class T> | 71 | template <class T> |
| 67 | concept scannable_prim = | 72 | concept scannable_prim = std::same_as<T, std::string> || std::same_as<T, double> |
| 68 | std::same_as<T, std::string> || | 73 | || std::same_as<T, std::int64_t>; |
| 69 | std::same_as<T, double> || | ||
| 70 | std::same_as<T, std::int64_t>; | ||
| 71 | 74 | ||
| 72 | template<class T> | 75 | template <class T> |
| 73 | concept scannable = scannable_prim<T> || optional_of<T, scannable_prim>; | 76 | concept scannable = scannable_prim<T> || optional_of<T, scannable_prim>; |
| 74 | 77 | ||
| 75 | class statement { | 78 | class statement |
| 76 | ::sqlite3_stmt* stmt_; | 79 | { |
| 80 | ::sqlite3_stmt* stmt_; | ||
| 77 | 81 | ||
| 78 | public: | 82 | public: |
| 79 | explicit statement(::sqlite3_stmt* stmt) : stmt_{stmt} {} | 83 | explicit statement(::sqlite3_stmt* stmt) : stmt_{stmt} {} |
| 80 | statement(statement const&) = delete; | 84 | statement(statement const&) = delete; |
| 81 | statement(statement&& s) noexcept { | 85 | statement(statement&& s) noexcept |
| 82 | stmt_ = s.stmt_; | 86 | { |
| 83 | s.stmt_ = nullptr; | 87 | stmt_ = s.stmt_; |
| 84 | } | 88 | s.stmt_ = nullptr; |
| 85 | ~statement() { | 89 | } |
| 86 | ::sqlite3_finalize(stmt_); | 90 | ~statement() { ::sqlite3_finalize(stmt_); } |
| 87 | } | 91 | auto get() -> ::sqlite3_stmt* { return stmt_; } |
| 88 | auto get() -> ::sqlite3_stmt* { | 92 | }; |
| 89 | return stmt_; | ||
| 90 | } | ||
| 91 | }; | ||
| 92 | 93 | ||
| 93 | class row_reader { | 94 | class row_reader |
| 94 | statement stmt_; | 95 | { |
| 96 | statement stmt_; | ||
| 95 | 97 | ||
| 96 | explicit row_reader(statement stmt) : stmt_{std::move(stmt)} {} | 98 | explicit row_reader(statement stmt) : stmt_{std::move(stmt)} {} |
| 97 | 99 | ||
| 98 | friend class connection; | 100 | friend class connection; |
| 99 | 101 | ||
| 100 | void scan(int col, std::string& s) { | 102 | void scan(int col, std::string& s) |
| 101 | if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_TEXT) | 103 | { |
| 102 | throw std::invalid_argument{"invalid type for scan"}; | 104 | if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_TEXT) |
| 103 | unsigned char const* chs = ::sqlite3_column_text(stmt_.get(), col); | 105 | throw std::invalid_argument{"invalid type for scan"}; |
| 104 | auto size = util::size_from_int(::sqlite3_column_bytes(stmt_.get(), col)); | 106 | unsigned char const* chs = ::sqlite3_column_text(stmt_.get(), col); |
| 105 | if (!size.has_value()) | 107 | auto size = util::size_from_int(::sqlite3_column_bytes(stmt_.get(), col)); |
| 106 | throw std::logic_error{"unexpected negative amount of bytes in column"}; | 108 | if (!size.has_value()) |
| 107 | s = std::string{reinterpret_cast<char const*>(chs), *size}; | 109 | throw std::logic_error{"unexpected negative amount of bytes in column"}; |
| 108 | } | 110 | s = std::string{reinterpret_cast<char const*>(chs), *size}; |
| 111 | } | ||
| 109 | 112 | ||
| 110 | void scan(int col, double& v) { | 113 | void scan(int col, double& v) |
| 111 | if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_FLOAT) | 114 | { |
| 112 | throw std::invalid_argument{"invalid type for scan"}; | 115 | if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_FLOAT) |
| 113 | v = ::sqlite3_column_double(stmt_.get(), col); | 116 | throw std::invalid_argument{"invalid type for scan"}; |
| 114 | } | 117 | v = ::sqlite3_column_double(stmt_.get(), col); |
| 118 | } | ||
| 115 | 119 | ||
| 116 | void scan(int col, std::int64_t& v) { | 120 | void scan(int col, std::int64_t& v) |
| 117 | if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_INTEGER) | 121 | { |
| 118 | throw std::invalid_argument{"invalid type for scan"}; | 122 | if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_INTEGER) |
| 119 | v = ::sqlite3_column_int64(stmt_.get(), col); | 123 | throw std::invalid_argument{"invalid type for scan"}; |
| 120 | } | 124 | v = ::sqlite3_column_int64(stmt_.get(), col); |
| 125 | } | ||
| 121 | 126 | ||
| 122 | void scan(int col, optional_of<scannable> auto& v) { | 127 | void scan(int col, optional_of<scannable> auto& v) |
| 123 | if (::sqlite3_column_type(stmt_.get(), col) == SQLITE_NULL) { | 128 | { |
| 124 | v.reset(); | 129 | if (::sqlite3_column_type(stmt_.get(), col) == SQLITE_NULL) |
| 125 | } else { | 130 | { |
| 126 | typename std::remove_cvref_t<decltype(v)>::value_type tmp; | 131 | v.reset(); |
| 127 | scan(col, tmp); | ||
| 128 | v = std::move(tmp); | ||
| 129 | } | ||
| 130 | } | 132 | } |
| 131 | 133 | else | |
| 132 | public: | 134 | { |
| 133 | auto next() -> bool { | 135 | typename std::remove_cvref_t<decltype(v)>::value_type tmp; |
| 134 | ::sqlite3* dbc = ::sqlite3_db_handle(stmt_.get()); | 136 | scan(col, tmp); |
| 135 | return do_guarded(dbc, [&](auto const& guard) -> bool { | 137 | v = std::move(tmp); |
| 136 | auto const s = ::sqlite3_step(stmt_.get()); | ||
| 137 | if (s == SQLITE_ROW) | ||
| 138 | return true; | ||
| 139 | if (s == SQLITE_DONE) | ||
| 140 | return false; | ||
| 141 | throw error{guard, s, dbc}; | ||
| 142 | }); | ||
| 143 | } | 138 | } |
| 139 | } | ||
| 144 | 140 | ||
| 145 | auto scan(scannable auto&... args) -> void { | 141 | public: |
| 146 | auto const ncols = util::size_from_int(::sqlite3_data_count(stmt_.get())); | 142 | auto next() -> bool |
| 147 | if (!ncols.has_value()) | 143 | { |
| 148 | throw std::logic_error{"got unexpected negative amount of columns"}; | 144 | ::sqlite3* dbc = ::sqlite3_db_handle(stmt_.get()); |
| 149 | if (sizeof...(args) > *ncols) | 145 | return do_guarded( |
| 150 | throw std::invalid_argument{"more scanning arguments provided than columns in result set"}; | 146 | dbc, |
| 151 | auto col = 0; (..., scan(col++, args)); | 147 | [&](auto const& guard) -> bool |
| 152 | } | 148 | { |
| 149 | auto const s = ::sqlite3_step(stmt_.get()); | ||
| 150 | if (s == SQLITE_ROW) | ||
| 151 | return true; | ||
| 152 | if (s == SQLITE_DONE) | ||
| 153 | return false; | ||
| 154 | throw error{guard, s, dbc}; | ||
| 155 | }); | ||
| 156 | } | ||
| 157 | |||
| 158 | auto scan(scannable auto&... args) -> void | ||
| 159 | { | ||
| 160 | auto const ncols = util::size_from_int(::sqlite3_data_count(stmt_.get())); | ||
| 161 | if (!ncols.has_value()) | ||
| 162 | throw std::logic_error{"got unexpected negative amount of columns"}; | ||
| 163 | if (sizeof...(args) > *ncols) | ||
| 164 | throw std::invalid_argument{ | ||
| 165 | "more scanning arguments provided than columns in result set" | ||
| 166 | }; | ||
| 167 | auto col = 0; | ||
| 168 | (..., scan(col++, args)); | ||
| 169 | } | ||
| 153 | 170 | ||
| 154 | auto scan_single(scannable auto&... args) -> void { | 171 | auto scan_single(scannable auto&... args) -> void |
| 155 | if (!next()) | 172 | { |
| 156 | throw std::logic_error{"no row in result set"}; | 173 | if (!next()) |
| 157 | scan(args...); | 174 | throw std::logic_error{"no row in result set"}; |
| 158 | if (next()) { | 175 | scan(args...); |
| 159 | throw std::logic_error{"more than one row in result set"}; | 176 | if (next()) |
| 160 | } | 177 | { |
| 178 | throw std::logic_error{"more than one row in result set"}; | ||
| 161 | } | 179 | } |
| 162 | }; | 180 | } |
| 181 | }; | ||
| 163 | 182 | ||
| 164 | class binder { | 183 | class binder |
| 165 | statement& stmt_; | 184 | { |
| 185 | statement& stmt_; | ||
| 166 | 186 | ||
| 167 | explicit binder(statement& stmt) : stmt_{stmt} {} | 187 | explicit binder(statement& stmt) : stmt_{stmt} {} |
| 168 | 188 | ||
| 169 | friend class connection; | 189 | friend class connection; |
| 170 | 190 | ||
| 171 | public: | 191 | public: |
| 172 | auto text(std::string const& param_name, std::string_view str) -> void { | 192 | auto text(std::string const& param_name, std::string_view str) -> void |
| 173 | int const i = ::sqlite3_bind_parameter_index(stmt_.get(), param_name.c_str()); | 193 | { |
| 174 | if (i == 0) | 194 | int const i = |
| 175 | throw std::invalid_argument{std::format("bind: no parameter with name {} found", param_name)}; | 195 | ::sqlite3_bind_parameter_index(stmt_.get(), param_name.c_str()); |
| 176 | auto str_size = util::int_from_size(str.size()); | 196 | if (i == 0) |
| 177 | if (!str_size.has_value()) | 197 | throw std::invalid_argument{std::format( |
| 178 | throw std::invalid_argument{"bind: provided text is too long"}; | 198 | "bind: no parameter with name {} found", param_name)}; |
| 179 | if (auto s = ::sqlite3_bind_text(stmt_.get(), i, str.data(), *str_size, SQLITE_TRANSIENT); s != SQLITE_OK) { | 199 | auto str_size = util::int_from_size(str.size()); |
| 180 | throw error{s}; | 200 | if (!str_size.has_value()) |
| 181 | } | 201 | throw std::invalid_argument{"bind: provided text is too long"}; |
| 202 | if (auto s = ::sqlite3_bind_text( | ||
| 203 | stmt_.get(), i, str.data(), *str_size, SQLITE_TRANSIENT); | ||
| 204 | s != SQLITE_OK) | ||
| 205 | { | ||
| 206 | throw error{s}; | ||
| 182 | } | 207 | } |
| 208 | } | ||
| 183 | 209 | ||
| 184 | static auto noop(binder&) -> void {} | 210 | static auto noop(binder&) -> void {} |
| 185 | }; | 211 | }; |
| 186 | 212 | ||
| 187 | export class connection { | 213 | export class connection |
| 188 | ::sqlite3* dbc_; | 214 | { |
| 189 | ::sqlite3_mutex* mut_; | 215 | ::sqlite3* dbc_; |
| 216 | ::sqlite3_mutex* mut_; | ||
| 190 | 217 | ||
| 191 | explicit connection(::sqlite3* dbc) : dbc_{dbc}, mut_{::sqlite3_db_mutex(dbc)} {} | 218 | explicit connection(::sqlite3* dbc) : dbc_{dbc}, mut_{::sqlite3_db_mutex(dbc)} |
| 219 | { | ||
| 220 | } | ||
| 192 | 221 | ||
| 193 | friend auto open(std::string const& filename) -> connection; | 222 | friend auto open(std::string const& filename) -> connection; |
| 194 | 223 | ||
| 195 | public: | 224 | public: |
| 196 | connection(connection const&) = delete; | 225 | connection(connection const&) = delete; |
| 197 | connection(connection&& c) noexcept { | 226 | connection(connection&& c) noexcept |
| 198 | dbc_ = c.dbc_; | 227 | { |
| 199 | mut_ = c.mut_; | 228 | dbc_ = c.dbc_; |
| 200 | c.dbc_ = nullptr; | 229 | mut_ = c.mut_; |
| 201 | c.mut_ = nullptr; | 230 | c.dbc_ = nullptr; |
| 202 | } | 231 | c.mut_ = nullptr; |
| 232 | } | ||
| 203 | 233 | ||
| 204 | [[nodiscard]] auto query(std::string const& sql, std::function<void(binder&)> const& bf = binder::noop) -> row_reader { | 234 | [[nodiscard]] auto query( |
| 205 | ::sqlite3_stmt* pstmt = nullptr; | 235 | std::string const& sql, |
| 206 | char const* sql_tail = nullptr; | 236 | std::function<void(binder&)> const& bf = binder::noop) -> row_reader |
| 207 | auto sql_size = util::int_from_size(sql.size()); | 237 | { |
| 208 | if (!sql_size.has_value() || *sql_size >= std::numeric_limits<int>::max() - 1) | 238 | ::sqlite3_stmt* pstmt = nullptr; |
| 209 | throw std::invalid_argument{"provided input text too large"}; | 239 | char const* sql_tail = nullptr; |
| 210 | do_guarded(mut_, [&](auto const& guard) -> void { | 240 | auto sql_size = util::int_from_size(sql.size()); |
| 211 | if (auto s = ::sqlite3_prepare_v2(dbc_, sql.data(), *sql_size + 1, &pstmt, &sql_tail); s != SQLITE_OK) { | 241 | if (!sql_size.has_value() |
| 212 | if (pstmt != nullptr) { | 242 | || *sql_size >= std::numeric_limits<int>::max() - 1) |
| 213 | // Use contract_assert when having a compiler with contracts available | 243 | throw std::invalid_argument{"provided input text too large"}; |
| 214 | ::sqlite3_finalize(pstmt); | 244 | do_guarded( |
| 215 | throw std::logic_error{"expected stmt to be null after failed preparation"}; | 245 | mut_, |
| 246 | [&](auto const& guard) -> void | ||
| 247 | { | ||
| 248 | if (auto s = ::sqlite3_prepare_v2( | ||
| 249 | dbc_, sql.data(), *sql_size + 1, &pstmt, &sql_tail); | ||
| 250 | s != SQLITE_OK) | ||
| 251 | { | ||
| 252 | if (pstmt != nullptr) | ||
| 253 | { | ||
| 254 | // Use contract_assert when having a compiler with | ||
| 255 | // contracts available | ||
| 256 | ::sqlite3_finalize(pstmt); | ||
| 257 | throw std::logic_error{ | ||
| 258 | "expected stmt to be null after failed preparation" | ||
| 259 | }; | ||
| 260 | } | ||
| 261 | throw error{guard, s, dbc_}; | ||
| 216 | } | 262 | } |
| 217 | throw error{guard, s, dbc_}; | 263 | }); |
| 218 | } | 264 | if (!pstmt) |
| 219 | }); | 265 | throw std::invalid_argument{"provided input text contains no SQL"}; |
| 220 | if (!pstmt) | 266 | auto stmt = statement{pstmt}; |
| 221 | throw std::invalid_argument{"provided input text contains no SQL"}; | 267 | if (sql_tail && std::strlen(sql_tail) > 0) |
| 222 | auto stmt = statement{pstmt}; | 268 | throw std::invalid_argument{ |
| 223 | if (sql_tail && std::strlen(sql_tail) > 0) | 269 | "provided input text contains more than one SQL statement" |
| 224 | throw std::invalid_argument{"provided input text contains more than one SQL statement"}; | 270 | }; |
| 225 | auto b = binder{stmt}; bf(b); | 271 | auto b = binder{stmt}; |
| 226 | return row_reader{std::move(stmt)}; | 272 | bf(b); |
| 227 | } | 273 | return row_reader{std::move(stmt)}; |
| 274 | } | ||
| 228 | 275 | ||
| 229 | auto exec(std::string const& sql, std::function<void(binder&)> const& bf = binder::noop) -> void { | 276 | auto exec( |
| 230 | auto reader = query(sql, bf); | 277 | std::string const& sql, |
| 231 | while (reader.next()); | 278 | std::function<void(binder&)> const& bf = binder::noop) -> void |
| 232 | } | 279 | { |
| 280 | auto reader = query(sql, bf); | ||
| 281 | while (reader.next()) | ||
| 282 | ; | ||
| 283 | } | ||
| 233 | 284 | ||
| 234 | ~connection() { | 285 | ~connection() { std::ignore = ::sqlite3_close(std::exchange(dbc_, nullptr)); } |
| 235 | std::ignore = ::sqlite3_close(std::exchange(dbc_, nullptr)); | 286 | }; |
| 236 | } | ||
| 237 | }; | ||
| 238 | 287 | ||
| 239 | export auto open(std::string const& filename) -> connection { | 288 | export auto open(std::string const& filename) -> connection |
| 240 | ::sqlite3* dbc = nullptr; | 289 | { |
| 241 | auto s = ::sqlite3_open_v2(filename.c_str(), &dbc, | 290 | ::sqlite3* dbc = nullptr; |
| 242 | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | | 291 | auto s = ::sqlite3_open_v2( |
| 243 | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_EXRESCODE, | 292 | filename.c_str(), &dbc, |
| 244 | nullptr); | 293 | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX |
| 245 | if (s != SQLITE_OK) { | 294 | | SQLITE_OPEN_EXRESCODE, |
| 246 | if (dbc) { | 295 | nullptr); |
| 247 | do_guarded(dbc, [&](auto const& guard) -> void { | 296 | if (s != SQLITE_OK) |
| 248 | throw error{guard, s, dbc}; | 297 | { |
| 249 | }); | 298 | if (dbc) |
| 250 | } else { | 299 | { |
| 251 | throw error{s}; | 300 | do_guarded( |
| 252 | } | 301 | dbc, [&](auto const& guard) -> void { throw error{guard, s, dbc}; }); |
| 302 | } | ||
| 303 | else | ||
| 304 | { | ||
| 305 | throw error{s}; | ||
| 253 | } | 306 | } |
| 254 | return connection{dbc}; | ||
| 255 | } | 307 | } |
| 308 | return connection{dbc}; | ||
| 309 | } | ||
| 256 | 310 | ||
| 257 | } // namespace routemon::sqlite3 | 311 | } // namespace routemon::sqlite3 |