sqlite/Iterator.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // <Iterator.hpp> -*- C++ -*- | ||
| 2 | |||
| 3 | #pragma once | ||
| 4 | |||
| 5 | #include "simdb/Assert.hpp" | ||
| 6 | #include "simdb/sqlite/Transaction.hpp" | ||
| 7 | #include "simdb/utils/utf16.hpp" | ||
| 8 | |||
| 9 | #include <memory> | ||
| 10 | #include <optional> | ||
| 11 | #include <sqlite3.h> | ||
| 12 | #include <string.h> | ||
| 13 | #include <string> | ||
| 14 | #include <type_traits> | ||
| 15 | #include <vector> | ||
| 16 | |||
| 17 | namespace simdb { | ||
| 18 | |||
| 19 | namespace detail { | ||
| 20 | template <typename T> struct unrolled_type | ||
| 21 | { | ||
| 22 | using type = T; | ||
| 23 | }; | ||
| 24 | |||
| 25 | template <typename T> struct unrolled_type<std::optional<T>> | ||
| 26 | { | ||
| 27 | using type = T; | ||
| 28 | }; | ||
| 29 | |||
| 30 | template <typename T> using unrolled_type_t = typename unrolled_type<T>::type; | ||
| 31 | |||
| 32 | template <typename T1, typename T2> | ||
| 33 | struct unrolled_is_same | ||
| 34 | : std::conditional_t<std::is_same_v<unrolled_type_t<T1>, unrolled_type_t<T2>>, std::true_type, std::false_type> | ||
| 35 | { | ||
| 36 | }; | ||
| 37 | |||
| 38 | template <typename T1, typename T2> inline constexpr bool unrolled_is_same_v = unrolled_is_same<T1, T2>::value; | ||
| 39 | |||
| 40 | template <typename T> struct is_optional : std::false_type | ||
| 41 | { | ||
| 42 | }; | ||
| 43 | |||
| 44 | template <typename T> struct is_optional<std::optional<T>> : std::true_type | ||
| 45 | { | ||
| 46 | }; | ||
| 47 | |||
| 48 | template <typename T> inline constexpr bool is_optional_v = is_optional<T>::value; | ||
| 49 | |||
| 50 | 197369 | inline bool columnIsNull(sqlite3_stmt* stmt, const int idx) | |
| 51 | { | ||
| 52 | 197369 | return sqlite3_column_type(stmt, idx) == SQLITE_NULL; | |
| 53 | } | ||
| 54 | |||
| 55 | 107214 | inline void requireNonNullColumn(sqlite3_stmt* stmt, const int idx, const std::string& col_name) | |
| 56 | { | ||
| 57 | 107218 | simdb_assert(!columnIsNull(stmt, idx), "Column '" << col_name << "' is NULL; use std::optional overload"); | |
| 58 | 107212 | } | |
| 59 | } // namespace detail | ||
| 60 | |||
| 61 | /*! | ||
| 62 | * \class ResultWriterBase | ||
| 63 | * | ||
| 64 | * \brief Base class for all SELECT objects that are responsible for | ||
| 65 | * writing record values to the user's local variables whenever | ||
| 66 | * a query's result set iterator is advanced. | ||
| 67 | */ | ||
| 68 | class ResultWriterBase | ||
| 69 | { | ||
| 70 | public: | ||
| 71 | /// Destroy | ||
| 72 | 39188 | virtual ~ResultWriterBase() = default; | |
| 73 | |||
| 74 | /// Read the value for the prepared statement at the given column index | ||
| 75 | /// and copy it to the user's local variable. | ||
| 76 | virtual void writeToUserVar(sqlite3_stmt* stmt, const int idx) const = 0; | ||
| 77 | |||
| 78 | /// Return a new copy of this writer. | ||
| 79 | virtual ResultWriterBase* clone() const = 0; | ||
| 80 | |||
| 81 | /// \brief Return the column name for this writer. | ||
| 82 | /// | ||
| 83 | /// Used when creating the query's prepared statement: | ||
| 84 | /// SELECT ColA,ColB FROM Table WHERE Id=44 AND Name='foo' | ||
| 85 | 146433 | const std::string& getColName() const { return col_name_; } | |
| 86 | |||
| 87 | protected: | ||
| 88 | 39188 | ResultWriterBase(const char* col_name) : | |
| 89 | 78376 | col_name_(col_name) | |
| 90 | { | ||
| 91 | 39188 | } | |
| 92 | |||
| 93 | private: | ||
| 94 | std::string col_name_; | ||
| 95 | }; | ||
| 96 | |||
| 97 | /*! | ||
| 98 | * \class ResultWriterInt32 | ||
| 99 | * | ||
| 100 | * \brief Responsible for writing int32 record values to the user's local | ||
| 101 | * variables whenever a query's result set iterator is advanced. | ||
| 102 | */ | ||
| 103 | template <typename IntT = int32_t> class ResultWriterInt32 : public ResultWriterBase | ||
| 104 | { | ||
| 105 | static_assert(detail::unrolled_is_same_v<IntT, int32_t>); | ||
| 106 | |||
| 107 | public: | ||
| 108 | /// \brief Construction | ||
| 109 | /// \param col_name Name of the selected column | ||
| 110 | /// \param user_var Pointer to the local variable where result values are | ||
| 111 | /// written to | ||
| 112 | 22431 | ResultWriterInt32(const char* col_name, IntT* user_var) : | |
| 113 | ResultWriterBase(col_name), | ||
| 114 | 22431 | user_var_(user_var) | |
| 115 | { | ||
| 116 | 22431 | } | |
| 117 | |||
| 118 | /// Read the value for the prepared statement at the given column index | ||
| 119 | /// and copy it to the user's local variable. | ||
| 120 | 8332 | void writeToUserVar(sqlite3_stmt* stmt, const int idx) const override | |
| 121 | { | ||
| 122 | if constexpr (detail::is_optional_v<IntT>) | ||
| 123 | { | ||
| 124 | 13 | if (detail::columnIsNull(stmt, idx)) | |
| 125 | { | ||
| 126 | 1 | *user_var_ = std::nullopt; | |
| 127 | 1 | return; | |
| 128 | } | ||
| 129 | } else | ||
| 130 | { | ||
| 131 | 8319 | detail::requireNonNullColumn(stmt, idx, getColName()); | |
| 132 | } | ||
| 133 | |||
| 134 | 8331 | *user_var_ = sqlite3_column_int(stmt, idx); | |
| 135 | 8319 | } | |
| 136 | |||
| 137 | /// Return a new copy of this writer. | ||
| 138 | 11219 | ResultWriterBase* clone() const override { return new ResultWriterInt32<IntT>(getColName().c_str(), user_var_); } | |
| 139 | |||
| 140 | private: | ||
| 141 | IntT* user_var_; | ||
| 142 | }; | ||
| 143 | |||
| 144 | /*! | ||
| 145 | * \class ResultWriterUInt32 | ||
| 146 | * | ||
| 147 | * \brief Responsible for writing uint32 record values to the user's local | ||
| 148 | * variables whenever a query's result set iterator is advanced. | ||
| 149 | */ | ||
| 150 | template <typename UIntT = uint32_t> class ResultWriterUInt32 : public ResultWriterBase | ||
| 151 | { | ||
| 152 | static_assert(detail::unrolled_is_same_v<UIntT, uint32_t>); | ||
| 153 | |||
| 154 | public: | ||
| 155 | /// \brief Construction | ||
| 156 | /// \param col_name Name of the selected column | ||
| 157 | /// \param user_var Pointer to the local variable where result values are | ||
| 158 | /// written to | ||
| 159 | 45 | ResultWriterUInt32(const char* col_name, UIntT* user_var) : | |
| 160 | ResultWriterBase(col_name), | ||
| 161 | 45 | user_var_(user_var) | |
| 162 | { | ||
| 163 | 45 | } | |
| 164 | |||
| 165 | /// Read the value for the prepared statement at the given column index | ||
| 166 | /// and copy it to the user's local variable. | ||
| 167 | 89805 | void writeToUserVar(sqlite3_stmt* stmt, const int idx) const override | |
| 168 | { | ||
| 169 | if constexpr (detail::is_optional_v<UIntT>) | ||
| 170 | { | ||
| 171 | 4 | if (detail::columnIsNull(stmt, idx)) | |
| 172 | { | ||
| 173 | 3 | *user_var_ = std::nullopt; | |
| 174 | 3 | return; | |
| 175 | } | ||
| 176 | } else | ||
| 177 | { | ||
| 178 | 89801 | detail::requireNonNullColumn(stmt, idx, getColName()); | |
| 179 | } | ||
| 180 | |||
| 181 | 89801 | sqlite3_int64 tmp = sqlite3_column_int64(stmt, idx); | |
| 182 | |||
| 183 | 89801 | simdb_assert(tmp >= 0 && tmp <= UINT32_MAX, "Value out of range for uint32_t"); | |
| 184 | |||
| 185 | 89801 | *user_var_ = static_cast<uint32_t>(tmp); | |
| 186 | 89800 | } | |
| 187 | |||
| 188 | /// Return a new copy of this writer. | ||
| 189 | 26 | ResultWriterBase* clone() const override { return new ResultWriterUInt32<UIntT>(getColName().c_str(), user_var_); } | |
| 190 | |||
| 191 | private: | ||
| 192 | UIntT* user_var_; | ||
| 193 | }; | ||
| 194 | |||
| 195 | /*! | ||
| 196 | * \class ResultWriterInt64 | ||
| 197 | * | ||
| 198 | * \brief Responsible for writing int64 record values to the user's local | ||
| 199 | * variables whenever a query's result set iterator is advanced. | ||
| 200 | */ | ||
| 201 | template <typename IntT = int64_t> class ResultWriterInt64 : public ResultWriterBase | ||
| 202 | { | ||
| 203 | static_assert(detail::unrolled_is_same_v<IntT, int64_t>); | ||
| 204 | |||
| 205 | public: | ||
| 206 | /// \brief Construction | ||
| 207 | /// \param col_name Name of the selected column | ||
| 208 | /// \param user_var Pointer to the local variable where result values are | ||
| 209 | /// written to | ||
| 210 | 43 | ResultWriterInt64(const char* col_name, IntT* user_var) : | |
| 211 | ResultWriterBase(col_name), | ||
| 212 | 43 | user_var_(user_var) | |
| 213 | { | ||
| 214 | 43 | } | |
| 215 | |||
| 216 | /// Read the value for the prepared statement at the given column index | ||
| 217 | /// and copy it to the user's local variable. | ||
| 218 | 6058 | void writeToUserVar(sqlite3_stmt* stmt, const int idx) const override | |
| 219 | { | ||
| 220 | if constexpr (detail::is_optional_v<IntT>) | ||
| 221 | { | ||
| 222 | 4 | if (detail::columnIsNull(stmt, idx)) | |
| 223 | { | ||
| 224 | 3 | *user_var_ = std::nullopt; | |
| 225 | 3 | return; | |
| 226 | } | ||
| 227 | } else | ||
| 228 | { | ||
| 229 | 6054 | detail::requireNonNullColumn(stmt, idx, getColName()); | |
| 230 | } | ||
| 231 | |||
| 232 | 6055 | *user_var_ = sqlite3_column_int64(stmt, idx); | |
| 233 | 6054 | } | |
| 234 | |||
| 235 | /// Return a new copy of this writer. | ||
| 236 | 25 | ResultWriterBase* clone() const override { return new ResultWriterInt64<IntT>(getColName().c_str(), user_var_); } | |
| 237 | |||
| 238 | private: | ||
| 239 | IntT* user_var_; | ||
| 240 | }; | ||
| 241 | |||
| 242 | /*! | ||
| 243 | * \class ResultWriterUInt64 | ||
| 244 | * | ||
| 245 | * \brief Responsible for writing uint64 record values to the user's local | ||
| 246 | * variables whenever a query's result set iterator is advanced. | ||
| 247 | */ | ||
| 248 | template <typename UIntT = uint64_t> class ResultWriterUInt64 : public ResultWriterBase | ||
| 249 | { | ||
| 250 | static_assert(detail::unrolled_is_same_v<UIntT, uint64_t>); | ||
| 251 | |||
| 252 | public: | ||
| 253 | /// \brief Construction | ||
| 254 | /// \param col_name Name of the selected column | ||
| 255 | /// \param user_var Pointer to the local variable where result values are | ||
| 256 | /// written to | ||
| 257 | 30 | ResultWriterUInt64(const char* col_name, UIntT* user_var) : | |
| 258 | ResultWriterBase(col_name), | ||
| 259 | 30 | user_var_(user_var) | |
| 260 | { | ||
| 261 | 30 | } | |
| 262 | |||
| 263 | /// Read the value for the prepared statement at the given column index | ||
| 264 | /// and copy it to the user's local variable. | ||
| 265 | 3026 | void writeToUserVar(sqlite3_stmt* stmt, const int idx) const override | |
| 266 | { | ||
| 267 | if constexpr (detail::is_optional_v<UIntT>) | ||
| 268 | { | ||
| 269 | 4 | if (detail::columnIsNull(stmt, idx)) | |
| 270 | { | ||
| 271 | 2 | *user_var_ = std::nullopt; | |
| 272 | 2 | return; | |
| 273 | } | ||
| 274 | } else | ||
| 275 | { | ||
| 276 | 3022 | detail::requireNonNullColumn(stmt, idx, getColName()); | |
| 277 | } | ||
| 278 | |||
| 279 | 3023 | const void* blob = sqlite3_column_text16(stmt, idx); | |
| 280 | 3023 | if (sqlite3_column_type(stmt, idx) == SQLITE_TEXT && blob != nullptr) | |
| 281 | { | ||
| 282 | 3023 | const char16_t* utf16str = static_cast<const char16_t*>(blob); | |
| 283 | 3023 | *user_var_ = utils::utf16_to_uint64(utf16str, 20); | |
| 284 | } else | ||
| 285 | { | ||
| 286 | ✗ | throw DBException("Invalid data / data type"); | |
| 287 | } | ||
| 288 | 3021 | } | |
| 289 | |||
| 290 | /// Return a new copy of this writer. | ||
| 291 | 15 | ResultWriterBase* clone() const override { return new ResultWriterUInt64<UIntT>(getColName().c_str(), user_var_); } | |
| 292 | |||
| 293 | private: | ||
| 294 | UIntT* user_var_; | ||
| 295 | }; | ||
| 296 | |||
| 297 | /*! | ||
| 298 | * \class ResultWriterDouble | ||
| 299 | * | ||
| 300 | * \brief Responsible for writing double record values to the user's local | ||
| 301 | * variables whenever a query's result set iterator is advanced. | ||
| 302 | */ | ||
| 303 | template <typename DoubleT = double> class ResultWriterDouble : public ResultWriterBase | ||
| 304 | { | ||
| 305 | static_assert(detail::unrolled_is_same_v<DoubleT, double>); | ||
| 306 | |||
| 307 | public: | ||
| 308 | /// \brief Construction | ||
| 309 | /// \param col_name Name of the selected column | ||
| 310 | /// \param user_var Pointer to the local variable where result values are | ||
| 311 | /// written to | ||
| 312 | 16 | ResultWriterDouble(const char* col_name, DoubleT* user_var) : | |
| 313 | ResultWriterBase(col_name), | ||
| 314 | 16 | user_var_(user_var) | |
| 315 | { | ||
| 316 | 16 | } | |
| 317 | |||
| 318 | /// Read the value for the prepared statement at the given column index | ||
| 319 | /// and copy it to the user's local variable. | ||
| 320 | 22 | void writeToUserVar(sqlite3_stmt* stmt, const int idx) const override | |
| 321 | { | ||
| 322 | if constexpr (detail::is_optional_v<DoubleT>) | ||
| 323 | { | ||
| 324 | 4 | if (detail::columnIsNull(stmt, idx)) | |
| 325 | { | ||
| 326 | 3 | *user_var_ = std::nullopt; | |
| 327 | 3 | return; | |
| 328 | } | ||
| 329 | } else | ||
| 330 | { | ||
| 331 | 18 | detail::requireNonNullColumn(stmt, idx, getColName()); | |
| 332 | } | ||
| 333 | |||
| 334 | 19 | *user_var_ = sqlite3_column_double(stmt, idx); | |
| 335 | 18 | } | |
| 336 | |||
| 337 | /// Return a new copy of this writer. | ||
| 338 | 8 | ResultWriterBase* clone() const override | |
| 339 | { | ||
| 340 | 8 | return new ResultWriterDouble<DoubleT>(getColName().c_str(), user_var_); | |
| 341 | } | ||
| 342 | |||
| 343 | private: | ||
| 344 | DoubleT* user_var_; | ||
| 345 | }; | ||
| 346 | |||
| 347 | /*! | ||
| 348 | * \class ResultWriterString | ||
| 349 | * | ||
| 350 | * \brief Responsible for writing text record values to the user's local | ||
| 351 | * variables whenever a query's result set iterator is advanced. | ||
| 352 | */ | ||
| 353 | template <typename StringT = std::string> class ResultWriterString : public ResultWriterBase | ||
| 354 | { | ||
| 355 | static_assert(detail::unrolled_is_same_v<StringT, std::string>); | ||
| 356 | |||
| 357 | public: | ||
| 358 | /// \brief Construction | ||
| 359 | /// \param col_name Name of the selected column | ||
| 360 | /// \param user_var Pointer to the local variable where result values are | ||
| 361 | /// written to | ||
| 362 | 235 | ResultWriterString(const char* col_name, StringT* user_var) : | |
| 363 | ResultWriterBase(col_name), | ||
| 364 | 235 | user_var_(user_var) | |
| 365 | { | ||
| 366 | 235 | } | |
| 367 | |||
| 368 | /// Read the value for the prepared statement at the given column index | ||
| 369 | /// and copy it to the user's local variable. | ||
| 370 | 90126 | void writeToUserVar(sqlite3_stmt* stmt, const int idx) const override | |
| 371 | { | ||
| 372 | if constexpr (detail::is_optional_v<StringT>) | ||
| 373 | { | ||
| 374 | 11 | if (detail::columnIsNull(stmt, idx)) | |
| 375 | { | ||
| 376 | 1 | *user_var_ = std::nullopt; | |
| 377 | 1 | return; | |
| 378 | } | ||
| 379 | 90115 | } else if (detail::columnIsNull(stmt, idx)) | |
| 380 | { | ||
| 381 | 1 | *user_var_ = ""; | |
| 382 | 1 | return; | |
| 383 | } | ||
| 384 | |||
| 385 | 90124 | const char* text = reinterpret_cast<const char*>(sqlite3_column_text(stmt, idx)); | |
| 386 | 90124 | *user_var_ = text ? text : ""; | |
| 387 | } | ||
| 388 | |||
| 389 | /// Return a new copy of this writer. | ||
| 390 | 117 | ResultWriterBase* clone() const override | |
| 391 | { | ||
| 392 | 117 | return new ResultWriterString<StringT>(getColName().c_str(), user_var_); | |
| 393 | } | ||
| 394 | |||
| 395 | private: | ||
| 396 | StringT* user_var_; | ||
| 397 | }; | ||
| 398 | |||
| 399 | /*! | ||
| 400 | * \class ResultWriterBlob<T> | ||
| 401 | * | ||
| 402 | * \brief Responsible for writing blob record values to the user's local | ||
| 403 | * variables whenever a query's result set iterator is advanced. | ||
| 404 | */ | ||
| 405 | template <typename T> class ResultWriterBlob : public ResultWriterBase | ||
| 406 | { | ||
| 407 | public: | ||
| 408 | /// \brief Construction | ||
| 409 | /// \param col_name Name of the selected column | ||
| 410 | /// \param user_var Pointer to the local variable where result values are | ||
| 411 | /// written to | ||
| 412 | 16388 | ResultWriterBlob(const char* col_name, std::vector<T>* user_var) : | |
| 413 | ResultWriterBase(col_name), | ||
| 414 | 16388 | user_var_(user_var) | |
| 415 | { | ||
| 416 | 16388 | } | |
| 417 | |||
| 418 | /// Read the value for the prepared statement at the given column index | ||
| 419 | /// and copy it to the user's local variable. | ||
| 420 | 19716 | void writeToUserVar(sqlite3_stmt* stmt, const int idx) const override | |
| 421 | { | ||
| 422 | 19716 | const void* data = sqlite3_column_blob(stmt, idx); | |
| 423 | 19716 | const int bytes = sqlite3_column_bytes(stmt, idx); | |
| 424 | 19716 | user_var_->resize(bytes / sizeof(T)); | |
| 425 | 19716 | memcpy(user_var_->data(), data, bytes); | |
| 426 | 19716 | } | |
| 427 | |||
| 428 | /// Return a new copy of this writer. | ||
| 429 | 8198 | ResultWriterBase* clone() const override { return new ResultWriterBlob(getColName().c_str(), user_var_); } | |
| 430 | |||
| 431 | private: | ||
| 432 | std::vector<T>* user_var_; | ||
| 433 | }; | ||
| 434 | |||
| 435 | /*! | ||
| 436 | * \class SqlResultIterator | ||
| 437 | * | ||
| 438 | * \brief This class is returned by SqlQuery::getResultSet() and | ||
| 439 | * is used to iterate over a query result set. | ||
| 440 | */ | ||
| 441 | class SqlResultIterator | ||
| 442 | { | ||
| 443 | public: | ||
| 444 | /// Construct with a prepared statement and the result writers that read | ||
| 445 | /// column values and write them into the user's local variables. | ||
| 446 | 19486 | SqlResultIterator(sqlite3_stmt* stmt, std::vector<std::shared_ptr<ResultWriterBase>>&& result_writers) : | |
| 447 | 19486 | stmt_(stmt), | |
| 448 | 19486 | result_writers_(std::move(result_writers)) | |
| 449 | { | ||
| 450 | 19486 | } | |
| 451 | |||
| 452 | /// Finalize the prepared statement on destruction. | ||
| 453 | 19486 | ~SqlResultIterator() | |
| 454 | { | ||
| 455 | 19486 | if (stmt_) | |
| 456 | { | ||
| 457 | 19486 | sqlite3_finalize(stmt_); | |
| 458 | } | ||
| 459 | 19486 | } | |
| 460 | |||
| 461 | /// Get the next record, populate the user's local variables, | ||
| 462 | /// and return TRUE if the record was found. FALSE is returned | ||
| 463 | /// when the entire result set has been iterated over. | ||
| 464 | 124037 | bool getNextRecord() | |
| 465 | { | ||
| 466 | 124037 | auto rc = SQLiteReturnCode(sqlite3_step(stmt_)); | |
| 467 | 124037 | if (rc == SQLITE_ROW) | |
| 468 | { | ||
| 469 | 338025 | for (size_t idx = 0; idx < result_writers_.size(); ++idx) | |
| 470 | { | ||
| 471 | 217085 | result_writers_[idx]->writeToUserVar(stmt_, (int)idx); | |
| 472 | } | ||
| 473 | 120940 | return true; | |
| 474 | } else | ||
| 475 | { | ||
| 476 | 3095 | simdb_assert(rc == SQLITE_DONE, sqlite3_errmsg(sqlite3_db_handle(stmt_))); | |
| 477 | } | ||
| 478 | |||
| 479 | 3095 | return false; | |
| 480 | } | ||
| 481 | |||
| 482 | /// Go back to the beginning of the result set if you need | ||
| 483 | /// to iterate over it again. | ||
| 484 | 2 | void reset() { simdb_assert(!SQLiteReturnCode(sqlite3_reset(stmt_)), sqlite3_errmsg(sqlite3_db_handle(stmt_))); } | |
| 485 | |||
| 486 | private: | ||
| 487 | /// Prepared statement | ||
| 488 | sqlite3_stmt* const stmt_; | ||
| 489 | |||
| 490 | /// Writers that read column values and write them into the user's local | ||
| 491 | /// variables | ||
| 492 | std::vector<std::shared_ptr<ResultWriterBase>> result_writers_; | ||
| 493 | }; | ||
| 494 | |||
| 495 | } // namespace simdb | ||
| 496 |