summaryrefslogtreecommitdiffstats
path: root/server/src/sqlite3.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/sqlite3.cpp')
-rw-r--r--server/src/sqlite3.cpp218
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 @@
1module;
2
3#include <sqlite3.h>
4
5module routemon:sqlite3$impl;
6
7import :sqlite3;
8
9namespace routemon::sqlite3 {
10
11mutex_guard::mutex_guard(::sqlite3_mutex* mut) noexcept : mut_{mut} {}
12
13mutex_guard::~mutex_guard() { ::sqlite3_mutex_leave(mut_); }
14
15auto 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
21auto 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
27error::error(mutex_guard const&, int code, ::sqlite3* dbc)
28 : code_{code}, message_{::sqlite3_errmsg(dbc)}
29{
30}
31
32error::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
41statement::statement(::sqlite3_stmt* stmt) : stmt_{stmt} {}
42statement::statement(statement&& s) noexcept
43{
44 stmt_ = s.stmt_;
45 s.stmt_ = nullptr;
46}
47statement::~statement() { ::sqlite3_finalize(stmt_); }
48auto statement::get() -> ::sqlite3_stmt* { return stmt_; }
49
50row_reader::row_reader(statement stmt) : stmt_{std::move(stmt)} {}
51
52auto row_reader::is_null(int col) -> bool
53{
54 return ::sqlite3_column_type(stmt_.get(), col) == SQLITE_NULL;
55}
56
57auto 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
65auto 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
76auto 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
83auto 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
90auto 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
106binder::binder(statement& stmt) : stmt_{stmt} {}
107
108auto 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
125auto binder::noop(binder&) -> void {}
126
127connection::connection(::sqlite3* dbc)
128 : dbc_{dbc}, mut_{::sqlite3_db_mutex(dbc)}
129{
130}
131
132connection::connection(connection const&) = delete;
133connection::connection(connection&& c) noexcept
134{
135 dbc_ = c.dbc_;
136 mut_ = c.mut_;
137 c.dbc_ = nullptr;
138 c.mut_ = nullptr;
139}
140
141auto 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
182auto 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
190connection::~connection()
191{
192 std::ignore = ::sqlite3_close(std::exchange(dbc_, nullptr));
193}
194
195auto 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