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 | ||
