diff options
-rw-r--r-- | hkutil/database.h | 229 |
1 files changed, 203 insertions, 26 deletions
diff --git a/hkutil/database.h b/hkutil/database.h index c15fa52..370799d 100644 --- a/hkutil/database.h +++ b/hkutil/database.h | |||
@@ -42,16 +42,24 @@ namespace hatkirby { | |||
42 | create | 42 | create |
43 | }; | 43 | }; |
44 | 44 | ||
45 | using blob_type = std::vector<unsigned char>; | ||
46 | |||
45 | using binding = | 47 | using binding = |
46 | mpark::variant< | 48 | mpark::variant< |
47 | std::string, | 49 | std::string, |
48 | int>; | 50 | int, |
51 | double, | ||
52 | std::nullptr_t, | ||
53 | blob_type>; | ||
49 | 54 | ||
50 | using column = | 55 | using column = |
51 | std::tuple< | 56 | std::tuple< |
52 | std::string, | 57 | std::string, |
53 | binding>; | 58 | binding>; |
54 | 59 | ||
60 | using row = | ||
61 | std::vector<binding>; | ||
62 | |||
55 | class database { | 63 | class database { |
56 | public: | 64 | public: |
57 | 65 | ||
@@ -102,22 +110,21 @@ namespace hatkirby { | |||
102 | 110 | ||
103 | void execute(std::string query) | 111 | void execute(std::string query) |
104 | { | 112 | { |
105 | sqlite3_stmt* ppstmt; | 113 | sqlite3_stmt* tempStmt; |
106 | 114 | ||
107 | if (sqlite3_prepare_v2( | 115 | if (sqlite3_prepare_v2( |
108 | ppdb_.get(), | 116 | ppdb_.get(), |
109 | query.c_str(), | 117 | query.c_str(), |
110 | query.length(), | 118 | query.length(), |
111 | &ppstmt, | 119 | &tempStmt, |
112 | NULL) != SQLITE_OK) | 120 | NULL) != SQLITE_OK) |
113 | { | 121 | { |
114 | throw sqlite3_error("Error writing to database", ppdb_.get()); | 122 | throw sqlite3_error("Error writing to database", ppdb_.get()); |
115 | } | 123 | } |
116 | 124 | ||
117 | int result = sqlite3_step(ppstmt); | 125 | ppstmt_type ppstmt(tempStmt); |
118 | sqlite3_finalize(ppstmt); | ||
119 | 126 | ||
120 | if (result != SQLITE_DONE) | 127 | if (sqlite3_step(ppstmt.get()) != SQLITE_DONE) |
121 | { | 128 | { |
122 | throw sqlite3_error("Error writing to database", ppdb_.get()); | 129 | throw sqlite3_error("Error writing to database", ppdb_.get()); |
123 | } | 130 | } |
@@ -147,48 +154,150 @@ namespace hatkirby { | |||
147 | 154 | ||
148 | std::string query_str = query.str(); | 155 | std::string query_str = query.str(); |
149 | 156 | ||
150 | sqlite3_stmt* ppstmt; | 157 | sqlite3_stmt* tempStmt; |
151 | 158 | ||
152 | if (sqlite3_prepare_v2( | 159 | if (sqlite3_prepare_v2( |
153 | ppdb_.get(), | 160 | ppdb_.get(), |
154 | query_str.c_str(), | 161 | query_str.c_str(), |
155 | query_str.length(), | 162 | query_str.length(), |
156 | &ppstmt, | 163 | &tempStmt, |
157 | NULL) != SQLITE_OK) | 164 | NULL) != SQLITE_OK) |
158 | { | 165 | { |
159 | throw sqlite3_error("Error writing to database", ppdb_.get()); | 166 | throw sqlite3_error("Error writing to database", ppdb_.get()); |
160 | } | 167 | } |
161 | 168 | ||
169 | ppstmt_type ppstmt(tempStmt); | ||
170 | |||
162 | int i = 1; | 171 | int i = 1; |
163 | for (const column& c : columns) | 172 | for (const column& c : columns) |
164 | { | 173 | { |
165 | const binding& b = std::get<1>(c); | 174 | bindToStmt(ppstmt, i, std::get<1>(c)); |
166 | 175 | ||
167 | if (mpark::holds_alternative<int>(b)) | 176 | i++; |
168 | { | 177 | } |
169 | sqlite3_bind_int(ppstmt, i, mpark::get<int>(b)); | 178 | |
170 | } else if (mpark::holds_alternative<std::string>(b)) | 179 | if (sqlite3_step(ppstmt.get()) != SQLITE_DONE) |
180 | { | ||
181 | throw sqlite3_error("Error writing to database", ppdb_.get()); | ||
182 | } | ||
183 | } | ||
184 | |||
185 | std::vector<row> queryAll( | ||
186 | std::string queryString, | ||
187 | std::list<binding> bindings) | ||
188 | { | ||
189 | sqlite3_stmt* tempStmt; | ||
190 | |||
191 | if (sqlite3_prepare_v2( | ||
192 | ppdb_.get(), | ||
193 | queryString.c_str(), | ||
194 | queryString.length(), | ||
195 | &tempStmt, | ||
196 | NULL) != SQLITE_OK) | ||
197 | { | ||
198 | throw sqlite3_error("Error preparing query", ppdb_.get()); | ||
199 | } | ||
200 | |||
201 | ppstmt_type ppstmt(tempStmt); | ||
202 | |||
203 | int i = 1; | ||
204 | for (const binding& value : bindings) | ||
205 | { | ||
206 | bindToStmt(ppstmt, i, value); | ||
207 | |||
208 | i++; | ||
209 | } | ||
210 | |||
211 | std::vector<row> result; | ||
212 | |||
213 | while (sqlite3_step(ppstmt.get()) == SQLITE_ROW) | ||
214 | { | ||
215 | row curRow; | ||
216 | |||
217 | int cols = sqlite3_column_count(ppstmt.get()); | ||
218 | |||
219 | for (int i = 0; i < cols; i++) | ||
171 | { | 220 | { |
172 | const std::string& arg = mpark::get<std::string>(b); | 221 | switch (sqlite3_column_type(ppstmt.get(), i)) |
173 | 222 | { | |
174 | sqlite3_bind_text( | 223 | case SQLITE_INTEGER: |
175 | ppstmt, | 224 | { |
176 | i, | 225 | curRow.emplace_back(sqlite3_column_int(ppstmt.get(), i)); |
177 | arg.c_str(), | 226 | |
178 | arg.length(), | 227 | break; |
179 | SQLITE_TRANSIENT); | 228 | } |
229 | |||
230 | case SQLITE_TEXT: | ||
231 | { | ||
232 | curRow.emplace_back( | ||
233 | std::string( | ||
234 | reinterpret_cast<const char*>( | ||
235 | sqlite3_column_text(ppstmt.get(), i)))); | ||
236 | |||
237 | break; | ||
238 | } | ||
239 | |||
240 | case SQLITE_FLOAT: | ||
241 | { | ||
242 | curRow.emplace_back(sqlite3_column_double(ppstmt.get(), i)); | ||
243 | |||
244 | break; | ||
245 | } | ||
246 | |||
247 | case SQLITE_NULL: | ||
248 | { | ||
249 | curRow.emplace_back(nullptr); | ||
250 | |||
251 | break; | ||
252 | } | ||
253 | |||
254 | case SQLITE_BLOB: | ||
255 | { | ||
256 | int len = sqlite3_column_bytes(ppstmt.get(), i); | ||
257 | |||
258 | blob_type value(len); | ||
259 | |||
260 | memcpy( | ||
261 | value.data(), | ||
262 | sqlite3_column_blob(ppstmt.get(), i), | ||
263 | len); | ||
264 | |||
265 | curRow.emplace_back(std::move(value)); | ||
266 | |||
267 | break; | ||
268 | } | ||
269 | |||
270 | default: | ||
271 | { | ||
272 | // Impossible | ||
273 | |||
274 | break; | ||
275 | } | ||
276 | } | ||
180 | } | 277 | } |
181 | 278 | ||
182 | i++; | 279 | result.emplace_back(std::move(curRow)); |
183 | } | 280 | } |
184 | 281 | ||
185 | int result = sqlite3_step(ppstmt); | 282 | return result; |
186 | sqlite3_finalize(ppstmt); | 283 | } |
284 | |||
285 | row queryFirst( | ||
286 | std::string queryString, | ||
287 | std::list<binding> bindings) | ||
288 | { | ||
289 | std::vector<row> dataset = queryAll( | ||
290 | std::move(queryString), | ||
291 | std::move(bindings)); | ||
187 | 292 | ||
188 | if (result != SQLITE_DONE) | 293 | if (dataset.empty()) |
189 | { | 294 | { |
190 | throw sqlite3_error("Error writing to database", ppdb_.get()); | 295 | throw std::logic_error("Query returned empty dataset"); |
191 | } | 296 | } |
297 | |||
298 | row result = std::move(dataset.front()); | ||
299 | |||
300 | return result; | ||
192 | } | 301 | } |
193 | 302 | ||
194 | private: | 303 | private: |
@@ -202,7 +311,75 @@ namespace hatkirby { | |||
202 | } | 311 | } |
203 | }; | 312 | }; |
204 | 313 | ||
314 | class stmt_deleter { | ||
315 | public: | ||
316 | |||
317 | void operator()(sqlite3_stmt* ptr) const | ||
318 | { | ||
319 | sqlite3_finalize(ptr); | ||
320 | } | ||
321 | }; | ||
322 | |||
205 | using ppdb_type = std::unique_ptr<sqlite3, sqlite3_deleter>; | 323 | using ppdb_type = std::unique_ptr<sqlite3, sqlite3_deleter>; |
324 | using ppstmt_type = std::unique_ptr<sqlite3_stmt, stmt_deleter>; | ||
325 | |||
326 | void bindToStmt( | ||
327 | const ppstmt_type& ppstmt, | ||
328 | int i, | ||
329 | const binding& value) | ||
330 | { | ||
331 | if (mpark::holds_alternative<int>(value)) | ||
332 | { | ||
333 | if (sqlite3_bind_int( | ||
334 | ppstmt.get(), | ||
335 | i, | ||
336 | mpark::get<int>(value)) != SQLITE_OK) | ||
337 | { | ||
338 | throw sqlite3_error("Error preparing statement", ppdb_.get()); | ||
339 | } | ||
340 | } else if (mpark::holds_alternative<std::string>(value)) | ||
341 | { | ||
342 | const std::string& arg = mpark::get<std::string>(value); | ||
343 | |||
344 | if (sqlite3_bind_text( | ||
345 | ppstmt.get(), | ||
346 | i, | ||
347 | arg.c_str(), | ||
348 | arg.length(), | ||
349 | SQLITE_TRANSIENT) != SQLITE_OK) | ||
350 | { | ||
351 | throw sqlite3_error("Error preparing statement", ppdb_.get()); | ||
352 | } | ||
353 | } else if (mpark::holds_alternative<double>(value)) | ||
354 | { | ||
355 | if (sqlite3_bind_double( | ||
356 | ppstmt.get(), | ||
357 | i, | ||
358 | mpark::get<double>(value)) != SQLITE_OK) | ||
359 | { | ||
360 | throw sqlite3_error("Error preparing statement", ppdb_.get()); | ||
361 | } | ||
362 | } else if (mpark::holds_alternative<std::nullptr_t>(value)) | ||
363 | { | ||
364 | if (sqlite3_bind_null(ppstmt.get(), i) != SQLITE_OK) | ||
365 | { | ||
366 | throw sqlite3_error("Error preparing statement", ppdb_.get()); | ||
367 | } | ||
368 | } else if (mpark::holds_alternative<blob_type>(value)) | ||
369 | { | ||
370 | const blob_type& arg = mpark::get<blob_type>(value); | ||
371 | |||
372 | if (sqlite3_bind_blob( | ||
373 | ppstmt.get(), | ||
374 | i, | ||
375 | arg.data(), | ||
376 | arg.size(), | ||
377 | SQLITE_TRANSIENT) != SQLITE_OK) | ||
378 | { | ||
379 | throw sqlite3_error("Error preparing statement", ppdb_.get()); | ||
380 | } | ||
381 | } | ||
382 | } | ||
206 | 383 | ||
207 | ppdb_type ppdb_; | 384 | ppdb_type ppdb_; |
208 | 385 | ||