sqlite/PreparedINSERT.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "simdb/Assert.hpp" | ||
| 4 | #include "simdb/schema/Blob.hpp" | ||
| 5 | #include "simdb/schema/SchemaDef.hpp" | ||
| 6 | #include "simdb/sqlite/Connection.hpp" | ||
| 7 | #include "simdb/sqlite/Transaction.hpp" | ||
| 8 | #include "simdb/utils/ValidValue.hpp" | ||
| 9 | #include "simdb/utils/utf16.hpp" | ||
| 10 | |||
| 11 | namespace simdb { | ||
| 12 | |||
| 13 | /*! | ||
| 14 | * \class PreparedINSERT | ||
| 15 | * | ||
| 16 | * \brief Reusable prepared INSERT for high-volume table inserts. Obtained from | ||
| 17 | * DatabaseManager::prepareINSERT(); use setColumnValue() for each | ||
| 18 | * column (by 0-based index) then createRecord() to insert a row. | ||
| 19 | * Column types must match the table schema. | ||
| 20 | */ | ||
| 21 | class PreparedINSERT | ||
| 22 | { | ||
| 23 | public: | ||
| 24 | /// \brief Internal constructor; use DatabaseManager::prepareINSERT() to obtain instances. | ||
| 25 | 218 | PreparedINSERT(SQLitePreparedStatement&& stmt, const std::vector<SqlDataType>& col_dtypes, | |
| 26 | 218 | std::shared_ptr<Connection> db_conn) : | |
| 27 | 218 | prepared_stmt_(std::move(stmt)), | |
| 28 | 218 | stmt_(prepared_stmt_), | |
| 29 | 218 | col_dtypes_(col_dtypes), | |
| 30 | 218 | db_conn_(db_conn) | |
| 31 | { | ||
| 32 | 218 | } | |
| 33 | |||
| 34 | /// \brief Set the value for the given column index (int32_t). | ||
| 35 | /// \param col_idx 0-based column index. | ||
| 36 | /// \param ival Value to set for the column. | ||
| 37 | 321391 | void setColumnValue(uint32_t col_idx, const int32_t ival) | |
| 38 | { | ||
| 39 | 321391 | simdb_assert(col_dtypes_.at(col_idx) == SqlDataType::int32_t, "Column " << col_idx + 1 << " is not an int32_t"); | |
| 40 | |||
| 41 | 321391 | sqlite3_bind_int(stmt_, (int32_t)col_idx + 1, ival); | |
| 42 | 321391 | } | |
| 43 | |||
| 44 | /// \brief Set the value for the given column index (uint32_t). | ||
| 45 | /// \param col_idx 0-based column index. | ||
| 46 | /// \param ival Value to set for the column. | ||
| 47 | 89781 | void setColumnValue(uint32_t col_idx, const uint32_t ival) | |
| 48 | { | ||
| 49 | 89781 | simdb_assert(col_dtypes_.at(col_idx) == SqlDataType::uint32_t, | |
| 50 | "Column " << col_idx + 1 << " is not a uint32_t"); | ||
| 51 | |||
| 52 | 89781 | sqlite3_bind_int64(stmt_, (int32_t)col_idx + 1, static_cast<sqlite3_int64>(ival)); | |
| 53 | 89781 | } | |
| 54 | |||
| 55 | /// \brief Set the value for the given column index (int64_t). | ||
| 56 | /// \param col_idx 0-based column index. | ||
| 57 | /// \param ival Value to set for the column. | ||
| 58 | 6 | void setColumnValue(uint32_t col_idx, const int64_t ival) | |
| 59 | { | ||
| 60 | 6 | simdb_assert(col_dtypes_.at(col_idx) == SqlDataType::int64_t, "Column " << col_idx + 1 << " is not an int64_t"); | |
| 61 | |||
| 62 | 6 | sqlite3_bind_int64(stmt_, (int32_t)col_idx + 1, ival); | |
| 63 | 6 | } | |
| 64 | |||
| 65 | /// \brief Set the value for the given column index (uint64_t). | ||
| 66 | /// \param col_idx 0-based column index. | ||
| 67 | /// \param ival Value to set for the column. | ||
| 68 | 6 | void setColumnValue(uint32_t col_idx, const uint64_t ival) | |
| 69 | { | ||
| 70 | 6 | simdb_assert(col_dtypes_.at(col_idx) == SqlDataType::uint64_t, | |
| 71 | "Column " << col_idx + 1 << " is not a uint64_t"); | ||
| 72 | |||
| 73 | 6 | auto u16 = utils::uint64_to_utf16(ival); | |
| 74 | 6 | sqlite3_bind_text16(stmt_, (int32_t)col_idx + 1, u16.data(), 40, SQLITE_TRANSIENT); | |
| 75 | 6 | } | |
| 76 | |||
| 77 | /// \brief Set the value for the given column index (double/float). | ||
| 78 | /// \tparam ColumnT double or float | ||
| 79 | /// \param col_idx 0-based column index. | ||
| 80 | /// \param dval Value to set for the column. | ||
| 81 | template <typename ColumnT> | ||
| 82 | 5 | typename std::enable_if<std::is_floating_point_v<ColumnT>, void>::type setColumnValue(uint32_t col_idx, | |
| 83 | const ColumnT dval) | ||
| 84 | { | ||
| 85 | 5 | simdb_assert(col_dtypes_.at(col_idx) == SqlDataType::double_t, "Column " << col_idx + 1 << " is not a double"); | |
| 86 | |||
| 87 | 5 | sqlite3_bind_double(stmt_, (int32_t)col_idx + 1, dval); | |
| 88 | 5 | } | |
| 89 | |||
| 90 | /// \brief Set the value for the given column index (blob from std::vector<T>). | ||
| 91 | /// \tparam T Blob element type. | ||
| 92 | /// \param col_idx 0-based column index. | ||
| 93 | /// \param blobval Value to set for the column. | ||
| 94 | 26697 | template <typename T> void setColumnValue(uint32_t col_idx, const std::vector<T>& blobval) | |
| 95 | { | ||
| 96 | 26697 | simdb_assert(col_dtypes_.at(col_idx) == SqlDataType::blob_t, "Column " << col_idx + 1 << " is not a BLOB"); | |
| 97 | |||
| 98 | 26697 | sqlite3_bind_blob(stmt_, (int32_t)col_idx + 1, blobval.data(), blobval.size() * sizeof(T), 0); | |
| 99 | 26697 | } | |
| 100 | |||
| 101 | /// \brief Set the value for the given column index (SqlBlob). | ||
| 102 | /// \param col_idx 0-based column index. | ||
| 103 | /// \param blobval Value to set for the column. | ||
| 104 | 1 | void setColumnValue(uint32_t col_idx, const SqlBlob& blobval) | |
| 105 | { | ||
| 106 | 1 | simdb_assert(col_dtypes_.at(col_idx) == SqlDataType::blob_t, "Column " << col_idx << " is not a BLOB"); | |
| 107 | |||
| 108 | 1 | sqlite3_bind_blob(stmt_, (int32_t)col_idx + 1, blobval.data_ptr, (int32_t)blobval.num_bytes, 0); | |
| 109 | 1 | } | |
| 110 | |||
| 111 | /// \brief Set the value for the given column index (std::string). | ||
| 112 | /// \param col_idx 0-based column index. | ||
| 113 | /// \param strval Value to set for the column. | ||
| 114 | 105577 | void setColumnValue(uint32_t col_idx, const std::string& strval) | |
| 115 | { | ||
| 116 | 105577 | simdb_assert(col_dtypes_.at(col_idx) == SqlDataType::string_t, "Column " << col_idx << " is not a TEXT"); | |
| 117 | |||
| 118 | 105577 | sqlite3_bind_text(stmt_, (int32_t)col_idx + 1, strval.c_str(), -1, SQLITE_TRANSIENT); | |
| 119 | 105577 | } | |
| 120 | |||
| 121 | /// \brief Set the value for the given column index (const char*). | ||
| 122 | /// \param col_idx 0-based column index. | ||
| 123 | /// \param strval Value to set for the column. | ||
| 124 | 57 | void setColumnValue(uint32_t col_idx, const char* strval) | |
| 125 | { | ||
| 126 | 57 | simdb_assert(col_dtypes_.at(col_idx) == SqlDataType::string_t, "Column " << col_idx << " is not a TEXT"); | |
| 127 | |||
| 128 | 57 | sqlite3_bind_text(stmt_, (int32_t)col_idx + 1, strval, -1, SQLITE_STATIC); | |
| 129 | 57 | } | |
| 130 | |||
| 131 | /// \brief Execute the prepared INSERT and return the new row ID. | ||
| 132 | /// \param clear_bindings If true, clears all bound values after the INSERT. | ||
| 133 | /// If false, bound values are reused for the next createRecord(); | ||
| 134 | /// ensure they remain valid (e.g. not out of scope). | ||
| 135 | /// \return The row ID of the newly inserted record. | ||
| 136 | 410500 | int createRecord(bool clear_bindings = true) | |
| 137 | { | ||
| 138 | 410500 | auto rc = SQLiteReturnCode(sqlite3_step(stmt_)); | |
| 139 | 410499 | simdb_assert(rc == SQLITE_DONE, "Could not perform INSERT. Error: " << sqlite3_errmsg(db_conn_->getDatabase())); | |
| 140 | |||
| 141 | 410499 | sqlite3_reset(stmt_); | |
| 142 | 410499 | if (clear_bindings) | |
| 143 | { | ||
| 144 | 410499 | sqlite3_clear_bindings(stmt_); | |
| 145 | } | ||
| 146 | 820998 | return db_conn_->getLastInsertRowId(); | |
| 147 | } | ||
| 148 | |||
| 149 | /// \brief Instead of calling setColumnValue() multiple times followed | ||
| 150 | /// by createRecord(), you can just call this method once. | ||
| 151 | /// \tparam ColumnT The type of the first column. | ||
| 152 | /// \tparam RestOfColumns The types of the remaining columns. | ||
| 153 | /// \param col_val The value for the first column. | ||
| 154 | /// \param rest The values for the remaining columns. | ||
| 155 | /// \note The variadic template arguments must match ALL columns in this | ||
| 156 | /// table, both in number and in data type. | ||
| 157 | /// \return The row ID of the newly inserted record. | ||
| 158 | template <typename ColumnT, typename... RestOfColumns> | ||
| 159 | 96226 | int createRecordWithColValues(const ColumnT& col_val, RestOfColumns&&... rest) | |
| 160 | { | ||
| 161 | 96226 | if (!one_shot_col_idx_.isValid()) | |
| 162 | { | ||
| 163 | 95975 | one_shot_col_idx_ = 0; | |
| 164 | } | ||
| 165 | 96226 | setColumnValue(one_shot_col_idx_, col_val); | |
| 166 | 96226 | ++one_shot_col_idx_.getValue(); | |
| 167 | 96226 | return createRecordWithColValues(std::forward<RestOfColumns>(rest)...); | |
| 168 | } | ||
| 169 | |||
| 170 | /// \brief Base case for variadic createRecordWithColValues(). | ||
| 171 | /// \tparam ColumnT The type of the column. | ||
| 172 | /// \param col_val The value for the column. | ||
| 173 | /// \return The row ID of the newly inserted object. | ||
| 174 | 375322 | template <typename ColumnT> int createRecordWithColValues(const ColumnT& col_val) | |
| 175 | { | ||
| 176 | 375322 | if (!one_shot_col_idx_.isValid()) | |
| 177 | { | ||
| 178 | 279347 | one_shot_col_idx_ = 0; | |
| 179 | } | ||
| 180 | 375322 | setColumnValue(one_shot_col_idx_, col_val); | |
| 181 | 375322 | auto id = createRecord(); | |
| 182 | 375321 | one_shot_col_idx_.clearValid(); | |
| 183 | 375321 | return id; | |
| 184 | } | ||
| 185 | |||
| 186 | private: | ||
| 187 | SQLitePreparedStatement prepared_stmt_; | ||
| 188 | sqlite3_stmt* stmt_ = nullptr; | ||
| 189 | std::vector<SqlDataType> col_dtypes_; | ||
| 190 | std::shared_ptr<Connection> db_conn_; | ||
| 191 | ValidValue<uint32_t> one_shot_col_idx_; | ||
| 192 | }; | ||
| 193 | |||
| 194 | } // namespace simdb | ||
| 195 |