summaryrefslogtreecommitdiffstats
path: root/server/src/sqlite3.cppm
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/sqlite3.cppm')
-rw-r--r--server/src/sqlite3.cppm248
1 files changed, 50 insertions, 198 deletions
diff --git a/server/src/sqlite3.cppm b/server/src/sqlite3.cppm
index 4623cdc..b771c49 100644
--- a/server/src/sqlite3.cppm
+++ b/server/src/sqlite3.cppm
@@ -1,20 +1,34 @@
1module;
2
3#include <sqlite3.h>
4
5export module routemon:sqlite3; 1export module routemon:sqlite3;
6 2
7import std; 3import std;
8import :util; 4import :util;
9 5
6extern "C"
7{
8 using sqlite3 = struct sqlite3;
9 using sqlite3_mutex = struct sqlite3_mutex;
10 using sqlite3_stmt = struct sqlite3_stmt;
11}
12
10namespace routemon::sqlite3 { 13namespace routemon::sqlite3 {
11 14
15template <class T, template <class U> concept C>
16concept optional_of = requires {
17 typename T::value_type;
18 requires std::same_as<T, std::optional<typename T::value_type>>;
19 requires C<typename T::value_type>;
20};
21
22template <class T>
23concept scannable_prim = std::same_as<T, std::string> || std::same_as<T, double>
24 || std::same_as<T, std::int64_t>;
25
26template <class T>
27concept scannable = scannable_prim<T> || optional_of<T, scannable_prim>;
28
12class mutex_guard 29class mutex_guard
13{ 30{
14 explicit mutex_guard(::sqlite3_mutex* mut) noexcept : mut_{mut} 31 explicit mutex_guard(::sqlite3_mutex* mut) noexcept;
15 {
16 ::sqlite3_mutex_enter(mut_);
17 }
18 32
19 friend auto 33 friend auto
20 do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f) 34 do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f)
@@ -22,111 +36,54 @@ class mutex_guard
22 36
23public: 37public:
24 mutex_guard(mutex_guard const&) = delete; 38 mutex_guard(mutex_guard const&) = delete;
25 ~mutex_guard() { ::sqlite3_mutex_leave(mut_); } 39 ~mutex_guard();
26 40
27private: 41private:
28 ::sqlite3_mutex* mut_; 42 ::sqlite3_mutex* mut_;
29}; 43};
30 44
31auto do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f)
32 -> decltype(f(std::declval<mutex_guard const&>()))
33{
34 return f(mutex_guard{mut});
35}
36
37auto do_guarded(::sqlite3* dbc, std::invocable<mutex_guard const&> auto f)
38 -> decltype(f(std::declval<mutex_guard const&>()))
39{
40 return do_guarded(::sqlite3_db_mutex(dbc), f);
41}
42
43class error : public std::exception 45class error : public std::exception
44{ 46{
45 int code_; 47 int code_;
46 std::string message_; 48 std::string message_;
47 49
48public: 50public:
49 explicit error(mutex_guard const&, int code, ::sqlite3* dbc) 51 explicit error(mutex_guard const&, int code, ::sqlite3* dbc);
50 : code_{code}, message_{::sqlite3_errmsg(dbc)} 52 explicit error(int code);
51 {
52 }
53
54 explicit error(int code) : code_{code}, message_{::sqlite3_errstr(code)} {}
55
56 [[nodiscard]] auto what() const noexcept -> char const* override
57 {
58 return message_.c_str();
59 }
60 53
61 [[nodiscard]] auto code() const noexcept -> int { return code_; } 54 [[nodiscard]] auto what() const noexcept -> char const* override;
62}; 55 [[nodiscard]] auto code() const noexcept -> int;
63
64template <class T, template <class U> concept C>
65concept optional_of = requires {
66 typename T::value_type;
67 requires std::same_as<T, std::optional<typename T::value_type>>;
68 requires C<typename T::value_type>;
69}; 56};
70 57
71template <class T>
72concept scannable_prim = std::same_as<T, std::string> || std::same_as<T, double>
73 || std::same_as<T, std::int64_t>;
74
75template <class T>
76concept scannable = scannable_prim<T> || optional_of<T, scannable_prim>;
77
78class statement 58class statement
79{ 59{
80 ::sqlite3_stmt* stmt_; 60 ::sqlite3_stmt* stmt_;
81 61
82public: 62public:
83 explicit statement(::sqlite3_stmt* stmt) : stmt_{stmt} {} 63 explicit statement(::sqlite3_stmt* stmt);
84 statement(statement const&) = delete; 64 statement(statement const&) = delete;
85 statement(statement&& s) noexcept 65 statement(statement&& s) noexcept;
86 { 66 ~statement();
87 stmt_ = s.stmt_; 67 auto get() -> ::sqlite3_stmt*;
88 s.stmt_ = nullptr;
89 }
90 ~statement() { ::sqlite3_finalize(stmt_); }
91 auto get() -> ::sqlite3_stmt* { return stmt_; }
92}; 68};
93 69
94class row_reader 70class row_reader
95{ 71{
96 statement stmt_; 72 statement stmt_;
97 73
98 explicit row_reader(statement stmt) : stmt_{std::move(stmt)} {} 74 explicit row_reader(statement stmt);
99 75
100 friend class connection; 76 friend class connection;
101 77
102 void scan(int col, std::string& s) 78 auto is_null(int col) -> bool;
103 { 79 auto ncols() -> std::size_t;
104 if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_TEXT)
105 throw std::invalid_argument{"invalid type for scan"};
106 unsigned char const* chs = ::sqlite3_column_text(stmt_.get(), col);
107 auto size = util::size_from_int(::sqlite3_column_bytes(stmt_.get(), col));
108 if (!size.has_value())
109 throw std::logic_error{"unexpected negative amount of bytes in column"};
110 s = std::string{reinterpret_cast<char const*>(chs), *size};
111 }
112 80
113 void scan(int col, double& v) 81 auto scan(int col, std::string& s) -> void;
82 auto scan(int col, double& v) -> void;
83 auto scan(int col, std::int64_t& v) -> void;
84 auto scan(int col, optional_of<scannable> auto& v) -> void
114 { 85 {
115 if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_FLOAT) 86 if (is_null(col))
116 throw std::invalid_argument{"invalid type for scan"};
117 v = ::sqlite3_column_double(stmt_.get(), col);
118 }
119
120 void scan(int col, std::int64_t& v)
121 {
122 if (::sqlite3_column_type(stmt_.get(), col) != SQLITE_INTEGER)
123 throw std::invalid_argument{"invalid type for scan"};
124 v = ::sqlite3_column_int64(stmt_.get(), col);
125 }
126
127 void scan(int col, optional_of<scannable> auto& v)
128 {
129 if (::sqlite3_column_type(stmt_.get(), col) == SQLITE_NULL)
130 { 87 {
131 v.reset(); 88 v.reset();
132 } 89 }
@@ -139,28 +96,11 @@ class row_reader
139 } 96 }
140 97
141public: 98public:
142 auto next() -> bool 99 auto next() -> bool;
143 {
144 ::sqlite3* dbc = ::sqlite3_db_handle(stmt_.get());
145 return do_guarded(
146 dbc,
147 [&](auto const& guard) -> bool
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 100
158 auto scan(scannable auto&... args) -> void 101 auto scan(scannable auto&... args) -> void
159 { 102 {
160 auto const ncols = util::size_from_int(::sqlite3_data_count(stmt_.get())); 103 if (sizeof...(args) > ncols())
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{ 104 throw std::invalid_argument{
165 "more scanning arguments provided than columns in result set" 105 "more scanning arguments provided than columns in result set"
166 }; 106 };
@@ -184,30 +124,14 @@ class binder
184{ 124{
185 statement& stmt_; 125 statement& stmt_;
186 126
187 explicit binder(statement& stmt) : stmt_{stmt} {} 127 explicit binder(statement& stmt);
188 128
189 friend class connection; 129 friend class connection;
190 130
191public: 131public:
192 auto text(std::string const& param_name, std::string_view str) -> void 132 auto text(std::string const& param_name, std::string_view str) -> void;
193 {
194 int const i =
195 ::sqlite3_bind_parameter_index(stmt_.get(), param_name.c_str());
196 if (i == 0)
197 throw std::invalid_argument{std::format(
198 "bind: no parameter with name {} found", param_name)};
199 auto str_size = util::int_from_size(str.size());
200 if (!str_size.has_value())
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};
207 }
208 }
209 133
210 static auto noop(binder&) -> void {} 134 static auto noop(binder&) -> void;
211}; 135};
212 136
213export class connection 137export class connection
@@ -215,97 +139,25 @@ export class connection
215 ::sqlite3* dbc_; 139 ::sqlite3* dbc_;
216 ::sqlite3_mutex* mut_; 140 ::sqlite3_mutex* mut_;
217 141
218 explicit connection(::sqlite3* dbc) : dbc_{dbc}, mut_{::sqlite3_db_mutex(dbc)} 142 explicit connection(::sqlite3* dbc);
219 {
220 }
221 143
222 friend auto open(std::string const& filename) -> connection; 144 friend auto open(std::string const& filename) -> connection;
223 145
224public: 146public:
225 connection(connection const&) = delete; 147 connection(connection const&) = delete;
226 connection(connection&& c) noexcept 148 connection(connection&& c) noexcept;
227 {
228 dbc_ = c.dbc_;
229 mut_ = c.mut_;
230 c.dbc_ = nullptr;
231 c.mut_ = nullptr;
232 }
233 149
234 [[nodiscard]] auto query( 150 [[nodiscard]] auto query(
235 std::string const& sql, 151 std::string const& sql,
236 std::function<void(binder&)> const& bf = binder::noop) -> row_reader 152 std::function<void(binder&)> const& bf = binder::noop) -> row_reader;
237 {
238 ::sqlite3_stmt* pstmt = nullptr;
239 char const* sql_tail = nullptr;
240 auto sql_size = util::int_from_size(sql.size());
241 if (!sql_size.has_value()
242 || *sql_size >= std::numeric_limits<int>::max() - 1)
243 throw std::invalid_argument{"provided input text too large"};
244 do_guarded(
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_};
262 }
263 });
264 if (!pstmt)
265 throw std::invalid_argument{"provided input text contains no SQL"};
266 auto stmt = statement{pstmt};
267 if (sql_tail && std::strlen(sql_tail) > 0)
268 throw std::invalid_argument{
269 "provided input text contains more than one SQL statement"
270 };
271 auto b = binder{stmt};
272 bf(b);
273 return row_reader{std::move(stmt)};
274 }
275 153
276 auto exec( 154 auto exec(
277 std::string const& sql, 155 std::string const& sql,
278 std::function<void(binder&)> const& bf = binder::noop) -> void 156 std::function<void(binder&)> const& bf = binder::noop) -> void;
279 {
280 auto reader = query(sql, bf);
281 while (reader.next())
282 ;
283 }
284 157
285 ~connection() { std::ignore = ::sqlite3_close(std::exchange(dbc_, nullptr)); } 158 ~connection();
286}; 159};
287 160
288export auto open(std::string const& filename) -> connection 161export auto open(std::string const& filename) -> connection;
289{
290 ::sqlite3* dbc = nullptr;
291 auto s = ::sqlite3_open_v2(
292 filename.c_str(), &dbc,
293 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX
294 | SQLITE_OPEN_EXRESCODE,
295 nullptr);
296 if (s != SQLITE_OK)
297 {
298 if (dbc)
299 {
300 do_guarded(
301 dbc, [&](auto const& guard) -> void { throw error{guard, s, dbc}; });
302 }
303 else
304 {
305 throw error{s};
306 }
307 }
308 return connection{dbc};
309}
310 162
311} // namespace routemon::sqlite3 163} // namespace routemon::sqlite3