SQLiteWrapper.h (16582B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #pragma once 4 #include <wil/result_macros.h> 5 #include <wil/resource.h> 6 #include <winsqlite/winsqlite3.h> 7 8 #include <AppInstallerLogging.h> 9 #include <AppInstallerLanguageUtilities.h> 10 11 #include <memory> 12 #include <optional> 13 #include <sstream> 14 #include <string> 15 #include <string_view> 16 #include <tuple> 17 #include <type_traits> 18 #include <utility> 19 #include <vector> 20 21 #define SQLITE_MEMORY_DB_CONNECTION_TARGET ":memory:" 22 23 using namespace std::string_view_literals; 24 25 namespace AppInstaller::SQLite 26 { 27 // The name of the rowid column in SQLite. 28 extern std::string_view RowIDName; 29 30 // The type of a rowid column in code. 31 using rowid_t = int64_t; 32 33 // The type to use for blob data. 34 using blob_t = std::vector<uint8_t>; 35 36 namespace details 37 { 38 template<typename> 39 constexpr bool dependent_false = false; 40 41 template <typename T, typename = void> 42 struct ParameterSpecificsImpl 43 { 44 static T& ToLog(T&&) 45 { 46 static_assert(dependent_false<T>, "No type specific override has been supplied"); 47 } 48 static void Bind(sqlite3_stmt*, int, T&&) 49 { 50 static_assert(dependent_false<T>, "No type specific override has been supplied"); 51 } 52 static T GetColumn(sqlite3_stmt*, int) 53 { 54 static_assert(dependent_false<T>, "No type specific override has been supplied"); 55 } 56 }; 57 58 template <> 59 struct ParameterSpecificsImpl<nullptr_t> 60 { 61 inline static std::string_view ToLog(nullptr_t) { return "null"sv; } 62 static void Bind(sqlite3_stmt* stmt, int index, nullptr_t); 63 }; 64 65 template <> 66 struct ParameterSpecificsImpl<std::string> 67 { 68 inline static const std::string& ToLog(const std::string& v) { return v; } 69 static void Bind(sqlite3_stmt* stmt, int index, const std::string& v); 70 static std::string GetColumn(sqlite3_stmt* stmt, int column); 71 }; 72 73 template <> 74 struct ParameterSpecificsImpl<std::string_view> 75 { 76 inline static const std::string_view& ToLog(const std::string_view& v) { return v; } 77 static void Bind(sqlite3_stmt* stmt, int index, std::string_view v); 78 }; 79 80 template <> 81 struct ParameterSpecificsImpl<int> 82 { 83 inline static int ToLog(int v) { return v; } 84 static void Bind(sqlite3_stmt* stmt, int index, int v); 85 static int GetColumn(sqlite3_stmt* stmt, int column); 86 }; 87 88 template <> 89 struct ParameterSpecificsImpl<int64_t> 90 { 91 inline static int64_t ToLog(int64_t v) { return v; } 92 static void Bind(sqlite3_stmt* stmt, int index, int64_t v); 93 static int64_t GetColumn(sqlite3_stmt* stmt, int column); 94 }; 95 96 template <> 97 struct ParameterSpecificsImpl<bool> 98 { 99 inline static bool ToLog(bool v) { return v; } 100 static void Bind(sqlite3_stmt* stmt, int index, bool v); 101 static bool GetColumn(sqlite3_stmt* stmt, int column); 102 }; 103 104 template <> 105 struct ParameterSpecificsImpl<blob_t> 106 { 107 static std::string ToLog(const blob_t& v); 108 static void Bind(sqlite3_stmt* stmt, int index, const blob_t& v); 109 static blob_t GetColumn(sqlite3_stmt* stmt, int column); 110 }; 111 112 template <> 113 struct ParameterSpecificsImpl<GUID> 114 { 115 static std::string ToLog(const GUID& v); 116 static void Bind(sqlite3_stmt* stmt, int index, const GUID& v); 117 static GUID GetColumn(sqlite3_stmt* stmt, int column); 118 }; 119 120 template <typename E> 121 struct ParameterSpecificsImpl<E, typename std::enable_if_t<std::is_enum_v<E>>> 122 { 123 static auto ToLog(E v) 124 { 125 return ToIntegral(v); 126 } 127 static void Bind(sqlite3_stmt* stmt, int index, E v) 128 { 129 ParameterSpecificsImpl<std::underlying_type_t<E>>::Bind(stmt, index, ToIntegral(v)); 130 } 131 static E GetColumn(sqlite3_stmt* stmt, int column) 132 { 133 return ToEnum<E>(ParameterSpecificsImpl<std::underlying_type_t<E>>::GetColumn(stmt, column)); 134 } 135 }; 136 137 template <typename Opt> 138 struct ParameterSpecificsImpl<std::optional<Opt>> 139 { 140 using Optional = std::optional<Opt>; 141 142 static auto ToLog(const Optional& v) 143 { 144 std::ostringstream result; 145 if (v) 146 { 147 result << ParameterSpecificsImpl<Opt>::ToLog(v.value()); 148 } 149 else 150 { 151 result << "{null}"; 152 } 153 return std::move(result).str(); 154 } 155 156 static void Bind(sqlite3_stmt* stmt, int index, const Optional& v) 157 { 158 if (v) 159 { 160 ParameterSpecificsImpl<Opt>::Bind(stmt, index, v.value()); 161 } 162 else 163 { 164 ParameterSpecificsImpl<nullptr_t>::Bind(stmt, index, nullptr); 165 } 166 } 167 168 static Optional GetColumn(sqlite3_stmt* stmt, int column) 169 { 170 if (sqlite3_column_type(stmt, column) == SQLITE_NULL) 171 { 172 return std::nullopt; 173 } 174 else 175 { 176 return ParameterSpecificsImpl<Opt>::GetColumn(stmt, column); 177 } 178 } 179 }; 180 181 template <typename T> 182 using ParameterSpecifics = ParameterSpecificsImpl<std::decay_t<T>>; 183 184 // Allows the connection to be shared so that it can be closed in some circumstances. 185 struct SharedConnection 186 { 187 // Disables the connection, causing an exception to be thrown by `get`. 188 void Disable(); 189 190 // Gets the connection object if active. 191 sqlite3* Get() const; 192 193 // Gets the connection object for creation. 194 sqlite3** GetPtr(); 195 196 private: 197 std::atomic_bool m_active = true; 198 wil::unique_any<sqlite3*, decltype(sqlite3_close_v2), sqlite3_close_v2> m_dbconn; 199 }; 200 } 201 202 // A SQLite exception. 203 struct SQLiteException : public wil::ResultException 204 { 205 SQLiteException(int error) : wil::ResultException(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, error)) {} 206 }; 207 208 struct Statement; 209 210 // The connection to a database. 211 struct Connection 212 { 213 friend Statement; 214 215 // The disposition for opening a database connection. 216 enum class OpenDisposition : int 217 { 218 // Open existing database for reading. 219 ReadOnly = SQLITE_OPEN_READONLY, 220 // Open existing database for reading and writing. 221 ReadWrite = SQLITE_OPEN_READWRITE, 222 // Create new database for reading and writing. 223 Create = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 224 }; 225 226 // Flags for opening a database connection. 227 enum class OpenFlags : int 228 { 229 // No flags specified. 230 None = 0, 231 // Indicate that the target can be a URI. 232 Uri = SQLITE_OPEN_URI, 233 }; 234 235 static Connection Create(const std::string& target, OpenDisposition disposition, OpenFlags flags = OpenFlags::None); 236 237 Connection() = default; 238 239 Connection(const Connection&) = delete; 240 Connection& operator=(const Connection&) = delete; 241 242 Connection(Connection&& other) = default; 243 Connection& operator=(Connection&& other) = default; 244 245 ~Connection() = default; 246 247 // Enables the ICU integrations on this connection. 248 void EnableICU(); 249 250 // Gets the last inserted rowid to the database. 251 rowid_t GetLastInsertRowID(); 252 253 // Gets the count of changed rows for the last executed statement. 254 int GetChanges() const; 255 256 //. Gets the (fixed but arbitrary) identifier for this connection. 257 size_t GetID() const; 258 259 // Sets the busy timeout for the connection. 260 void SetBusyTimeout(std::chrono::milliseconds timeout); 261 262 // Sets the journal mode. 263 // Returns true if successful, false if not. 264 // Must be performed outside of a transaction. 265 bool SetJournalMode(std::string_view mode); 266 267 operator sqlite3* () const { return m_dbconn->Get(); } 268 269 protected: 270 // Gets the shared connection. 271 std::shared_ptr<details::SharedConnection> GetSharedConnection() const; 272 273 private: 274 Connection(const std::string& target, OpenDisposition disposition, OpenFlags flags); 275 276 size_t m_id = 0; 277 std::shared_ptr<details::SharedConnection> m_dbconn; 278 }; 279 280 // A SQL statement. 281 struct Statement 282 { 283 static Statement Create(const Connection& connection, const std::string& sql); 284 static Statement Create(const Connection& connection, std::string_view sql); 285 static Statement Create(const Connection& connection, char const* const sql); 286 287 Statement() = default; 288 289 Statement(const Statement&) = delete; 290 Statement& operator=(const Statement&) = delete; 291 292 Statement(Statement&& other) = default; 293 Statement& operator=(Statement&& other) = default; 294 295 operator sqlite3_stmt* () const { return m_stmt.get(); } 296 297 // The state of the statement. 298 enum class State 299 { 300 // The statement has been prepared, but not evaluated. 301 Prepared = 0, 302 // The statement has a row available for reading. 303 HasRow = 1, 304 // The statement has been completed. 305 Completed = 2, 306 // The statement has resulted in an error. 307 Error = 3, 308 }; 309 310 // Gets the current state of the statement. 311 State GetState() const { return m_state; } 312 313 // Bind parameters to the statement. 314 // The index is 1 based. 315 template <typename Value> 316 void Bind(int index, Value&& v) 317 { 318 AICLI_LOG(SQL, Verbose, << "Binding statement #" << m_connectionId << '-' << m_id << ": " << index << " => " << details::ParameterSpecifics<Value>::ToLog(std::forward<Value>(v))); 319 details::ParameterSpecifics<Value>::Bind(m_stmt.get(), index, std::forward<Value>(v)); 320 } 321 322 // Evaluate the statement; either retrieving the next row or executing some action. 323 // Returns true if there is a row of data, or false if there is none. 324 // This return value is the equivalent of 'GetState() == State::HasRow' after calling Step. 325 bool Step(bool closeConnectionOnError = false); 326 327 // Equivalent to Step, but does not ever expect a result, throwing if one is retrieved. 328 void Execute(bool closeConnectionOnError = false); 329 330 // Gets a boolean value that indicates whether the specified column value is null in the current row. 331 // The index is 0 based. 332 bool GetColumnIsNull(int column); 333 334 // Gets the value of the specified column from the current row. 335 // The index is 0 based. 336 template <typename Value> 337 Value GetColumn(int column) 338 { 339 THROW_HR_IF(E_BOUNDS, m_state != State::HasRow); 340 return details::ParameterSpecifics<Value>::GetColumn(m_stmt.get(), column); 341 } 342 343 // Gets the entire row of values from the current row. 344 // The values requested *must* be those available starting from the first column, but trailing columns can be omitted. 345 template <typename... Values> 346 std::tuple<Values...> GetRow() 347 { 348 return GetRowImpl<Values...>(std::make_integer_sequence<int, sizeof...(Values)>{}); 349 } 350 351 // Resets the statement state, allowing it to be evaluated again. 352 // Note that this does not clear data bindings. 353 void Reset(); 354 355 // Determines if the statement owns an underlying object. 356 operator bool() const { return static_cast<bool>(m_stmt); } 357 358 private: 359 Statement(const Connection& connection, std::string_view sql); 360 361 // Helper to receive the integer sequence from the public function. 362 // This is equivalent to calling: 363 // for (i = 0 .. count of Values types) 364 // GetColumn<current Value type>(i) 365 // Then putting them all into a tuple. 366 template <typename... Values, int... I> 367 std::tuple<Values...> GetRowImpl(std::integer_sequence<int, I...>) 368 { 369 THROW_HR_IF(E_BOUNDS, m_state != State::HasRow); 370 return std::make_tuple(details::ParameterSpecifics<Values>::GetColumn(m_stmt.get(), I)...); 371 } 372 373 std::shared_ptr<details::SharedConnection> m_dbconn; 374 size_t m_connectionId = 0; 375 size_t m_id = 0; 376 wil::unique_any<sqlite3_stmt*, decltype(sqlite3_finalize), sqlite3_finalize> m_stmt; 377 State m_state = State::Prepared; 378 }; 379 380 // A SQLite transaction. 381 // Use as the beginning of a transaction stack, specifically when the transaction will write 382 // and the database is in WAL mode. 383 struct Transaction 384 { 385 // Creates a transaction, beginning it. 386 static Transaction Create(Connection& connection, std::string name, bool immediateWrite); 387 388 Transaction(); 389 390 Transaction(const Transaction&) = delete; 391 Transaction& operator=(const Transaction&) = delete; 392 393 Transaction(Transaction&&) = default; 394 Transaction& operator=(Transaction&&) = default; 395 396 ~Transaction(); 397 398 // Rolls back the Transaction. 399 void Rollback(bool throwOnError = true); 400 401 // Commits the Transaction. 402 void Commit(); 403 404 private: 405 Transaction(Connection& connection, std::string&& name, bool immediateWrite); 406 407 std::string m_name; 408 DestructionToken m_inProgress = true; 409 Statement m_rollback; 410 Statement m_commit; 411 }; 412 413 // A SQLite savepoint. 414 struct Savepoint 415 { 416 // Creates a savepoint, beginning it. 417 static Savepoint Create(Connection& connection, std::string name); 418 419 Savepoint(); 420 421 Savepoint(const Savepoint&) = delete; 422 Savepoint& operator=(const Savepoint&) = delete; 423 424 Savepoint(Savepoint&&) = default; 425 Savepoint& operator=(Savepoint&&) = default; 426 427 ~Savepoint(); 428 429 // Rolls back the Savepoint. 430 void Rollback(bool throwOnError = true); 431 432 // Commits the Savepoint. 433 void Commit(); 434 435 private: 436 Savepoint(Connection& connection, std::string&& name); 437 438 std::string m_name; 439 DestructionToken m_inProgress = true; 440 Statement m_rollbackTo; 441 Statement m_release; 442 }; 443 444 // A SQLite backup operation. 445 struct Backup 446 { 447 // Creates a backup. 448 static Backup Create(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName); 449 450 Backup(const Backup&) = delete; 451 Backup& operator=(const Backup&) = delete; 452 453 Backup(Backup&&) = default; 454 Backup& operator=(Backup&&) = default; 455 456 // Performs some or all of the backup. 457 // Returns true if the backup is completed, false if not. 458 bool Step(int pages = -1); 459 460 private: 461 Backup(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName); 462 463 wil::unique_any<sqlite3_backup*, decltype(sqlite3_backup_finish), sqlite3_backup_finish> m_backup; 464 }; 465 466 // The escape character used in the EscapeStringForLike function. 467 extern std::string_view EscapeCharForLike; 468 469 // Escapes the given input string for passing to a like operation. 470 std::string EscapeStringForLike(std::string_view value); 471 }