summaryrefslogtreecommitdiffstats
path: root/server/src/sqlite3.cppm
diff options
context:
space:
mode:
authorRutger Broekhoff2026-08-28 21:12:55 +0200
committerRutger Broekhoff2026-08-28 21:12:55 +0200
commita5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac (patch)
treeb4e0ec57c3799e9f649c6bfe27cb6c3313b97364 /server/src/sqlite3.cppm
parent973aec43ea54bbf95b64fbcb636403401d1ca60e (diff)
downloadroutemon-a5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac.tar.gz
routemon-a5d95afb96eb9a3b65d82f5f84bf8e4a4fb4c8ac.zip
clang-format C++ sources
Diffstat (limited to 'server/src/sqlite3.cppm')
-rw-r--r--server/src/sqlite3.cppm454
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
10namespace routemon::sqlite3 { 10namespace routemon::sqlite3 {
11 11
12 class mutex_guard { 12class 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: 23public:
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: 27private:
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&>())) { 31auto 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&>())) { 37auto 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 { 43class error : public std::exception
38 int code_; 44{
39 std::string message_; 45 int code_;
46 std::string message_;
40 47
41 public: 48public:
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> 64template <class T, template <class U> concept C>
60 concept optional_of = requires { 65concept 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> 71template <class T>
67 concept scannable_prim = 72concept 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> 75template <class T>
73 concept scannable = scannable_prim<T> || optional_of<T, scannable_prim>; 76concept scannable = scannable_prim<T> || optional_of<T, scannable_prim>;
74 77
75 class statement { 78class statement
76 ::sqlite3_stmt* stmt_; 79{
80 ::sqlite3_stmt* stmt_;
77 81
78 public: 82public:
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 { 94class 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 { 141public:
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 { 183class 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: 191public:
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 { 213export 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: 224public:
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 { 288export 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