SQLiteWrapper.cpp (19017B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Public/winget/SQLiteWrapper.h" 5 #include "Public/AppInstallerErrors.h" 6 #include "Public/AppInstallerStrings.h" 7 #include "ICU/SQLiteICU.h" 8 9 #include <wil/result_macros.h> 10 11 using namespace std::chrono_literals; 12 using namespace std::string_view_literals; 13 14 // Enable this to have all Statement constructions output the associated query plan. 15 #define WINGET_SQLITE_EXPLAIN_QUERY_PLAN_ENABLED 0 16 17 #if WINGET_SQLITE_EXPLAIN_QUERY_PLAN_ENABLED 18 #include <stack> 19 #endif 20 21 // Connection is used twice 22 #define SQLITE_ERROR_MSG(_error_,_connection_) (_connection_ ? sqlite3_errmsg(_connection_) : sqlite3_errstr(_error_)) 23 24 #define THROW_SQLITE(_error_,_connection_) \ 25 do { \ 26 int _ts_sqliteReturnValue = (_error_); \ 27 sqlite3* _ts_sqliteConnection = (_connection_); \ 28 THROW_EXCEPTION_MSG(SQLiteException(_ts_sqliteReturnValue), "%hs", SQLITE_ERROR_MSG(_ts_sqliteReturnValue, _ts_sqliteConnection)); \ 29 } while (0,0) 30 31 #define THROW_IF_SQLITE_FAILED(_statement_,_connection_) \ 32 do { \ 33 int _tisf_sqliteReturnValue = (_statement_); \ 34 if (_tisf_sqliteReturnValue != SQLITE_OK) \ 35 { \ 36 THROW_SQLITE(_tisf_sqliteReturnValue,_connection_); \ 37 } \ 38 } while (0,0) 39 40 namespace AppInstaller::SQLite 41 { 42 std::string_view RowIDName = "rowid"sv; 43 44 namespace 45 { 46 size_t GetNextConnectionId() 47 { 48 static std::atomic_size_t connectionId(0); 49 return ++connectionId; 50 } 51 52 size_t GetNextStatementId() 53 { 54 static std::atomic_size_t statementId(0); 55 return ++statementId; 56 } 57 } 58 59 namespace details 60 { 61 void ParameterSpecificsImpl<nullptr_t>::Bind(sqlite3_stmt* stmt, int index, nullptr_t) 62 { 63 THROW_IF_SQLITE_FAILED(sqlite3_bind_null(stmt, index), sqlite3_db_handle(stmt)); 64 } 65 66 void ThrowIfContainsEmbeddedNullCharacter(std::string_view v) 67 { 68 THROW_HR_IF(APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL, v.find('\0') != std::string_view::npos); 69 } 70 71 void ParameterSpecificsImpl<std::string>::Bind(sqlite3_stmt* stmt, int index, const std::string& v) 72 { 73 ThrowIfContainsEmbeddedNullCharacter(v); 74 THROW_IF_SQLITE_FAILED(sqlite3_bind_text64(stmt, index, v.c_str(), v.size(), SQLITE_TRANSIENT, SQLITE_UTF8), sqlite3_db_handle(stmt)); 75 } 76 77 std::string ParameterSpecificsImpl<std::string>::GetColumn(sqlite3_stmt* stmt, int column) 78 { 79 return reinterpret_cast<const char*>(sqlite3_column_text(stmt, column)); 80 } 81 82 void ParameterSpecificsImpl<std::string_view>::Bind(sqlite3_stmt* stmt, int index, std::string_view v) 83 { 84 if (v.empty()) 85 { 86 // An empty string_view can have it's data member return nullptr, which effectively binds a null value. 87 // We don't want that, so instead bind an empty string, which will have a non-null data pointer. 88 ParameterSpecificsImpl<std::string>::Bind(stmt, index, {}); 89 } 90 else 91 { 92 ThrowIfContainsEmbeddedNullCharacter(v); 93 THROW_IF_SQLITE_FAILED(sqlite3_bind_text64(stmt, index, v.data(), v.size(), SQLITE_TRANSIENT, SQLITE_UTF8), sqlite3_db_handle(stmt)); 94 } 95 } 96 97 void ParameterSpecificsImpl<int>::Bind(sqlite3_stmt* stmt, int index, int v) 98 { 99 THROW_IF_SQLITE_FAILED(sqlite3_bind_int(stmt, index, v), sqlite3_db_handle(stmt)); 100 } 101 102 int ParameterSpecificsImpl<int>::GetColumn(sqlite3_stmt* stmt, int column) 103 { 104 return sqlite3_column_int(stmt, column); 105 } 106 107 void ParameterSpecificsImpl<int64_t>::Bind(sqlite3_stmt* stmt, int index, int64_t v) 108 { 109 THROW_IF_SQLITE_FAILED(sqlite3_bind_int64(stmt, index, v), sqlite3_db_handle(stmt)); 110 } 111 112 int64_t ParameterSpecificsImpl<int64_t>::GetColumn(sqlite3_stmt* stmt, int column) 113 { 114 return sqlite3_column_int64(stmt, column); 115 } 116 117 void ParameterSpecificsImpl<bool>::Bind(sqlite3_stmt* stmt, int index, bool v) 118 { 119 THROW_IF_SQLITE_FAILED(sqlite3_bind_int(stmt, index, (v ? 1 : 0)), sqlite3_db_handle(stmt)); 120 } 121 122 bool ParameterSpecificsImpl<bool>::GetColumn(sqlite3_stmt* stmt, int column) 123 { 124 return (sqlite3_column_int(stmt, column) != 0); 125 } 126 127 std::string ParameterSpecificsImpl<blob_t>::ToLog(const blob_t& v) 128 { 129 std::ostringstream strstr; 130 strstr << "blob[" << v.size() << "]"; 131 return strstr.str(); 132 } 133 134 void ParameterSpecificsImpl<blob_t>::Bind(sqlite3_stmt* stmt, int index, const blob_t& v) 135 { 136 THROW_IF_SQLITE_FAILED(sqlite3_bind_blob64(stmt, index, v.data(), v.size(), SQLITE_TRANSIENT), sqlite3_db_handle(stmt)); 137 } 138 139 blob_t ParameterSpecificsImpl<blob_t>::GetColumn(sqlite3_stmt* stmt, int column) 140 { 141 const blob_t::value_type* blobPtr = reinterpret_cast<const blob_t::value_type *>(sqlite3_column_blob(stmt, column)); 142 if (blobPtr) 143 { 144 int blobBytes = sqlite3_column_bytes(stmt, column); 145 return blob_t{ blobPtr, blobPtr + blobBytes }; 146 } 147 else 148 { 149 return {}; 150 } 151 } 152 153 std::string ParameterSpecificsImpl<GUID>::ToLog(const GUID& v) 154 { 155 std::ostringstream strstr; 156 strstr << v; 157 return strstr.str(); 158 } 159 160 void ParameterSpecificsImpl<GUID>::Bind(sqlite3_stmt* stmt, int index, const GUID& v) 161 { 162 static_assert(sizeof(v) == 16); 163 THROW_IF_SQLITE_FAILED(sqlite3_bind_blob64(stmt, index, &v, sizeof(v), SQLITE_TRANSIENT), sqlite3_db_handle(stmt)); 164 } 165 166 GUID ParameterSpecificsImpl<GUID>::GetColumn(sqlite3_stmt* stmt, int column) 167 { 168 GUID result{}; 169 170 const void* blobPtr = sqlite3_column_blob(stmt, column); 171 if (blobPtr) 172 { 173 result = *reinterpret_cast<const GUID*>(blobPtr); 174 } 175 176 return result; 177 } 178 179 void SharedConnection::Disable() 180 { 181 m_active = false; 182 } 183 184 sqlite3* SharedConnection::Get() const 185 { 186 THROW_HR_IF(APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED, !m_active.load()); 187 return m_dbconn.get(); 188 } 189 190 sqlite3** SharedConnection::GetPtr() 191 { 192 return &m_dbconn; 193 } 194 } 195 196 Connection::Connection(const std::string& target, OpenDisposition disposition, OpenFlags flags) 197 { 198 m_dbconn = std::make_shared<details::SharedConnection>(); 199 m_id = GetNextConnectionId(); 200 AICLI_LOG(SQL, Info, << "Opening SQLite connection #" << m_id << ": '" << target << "' [" << std::hex << static_cast<int>(disposition) << ", " << std::hex << static_cast<int>(flags) << "]"); 201 // Always force connection serialization until we determine that there are situations where it is not needed 202 int resultingFlags = static_cast<int>(disposition) | static_cast<int>(flags) | SQLITE_OPEN_FULLMUTEX; 203 THROW_IF_SQLITE_FAILED(sqlite3_open_v2(target.c_str(), m_dbconn->GetPtr(), resultingFlags, nullptr), nullptr); 204 } 205 206 Connection Connection::Create(const std::string& target, OpenDisposition disposition, OpenFlags flags) 207 { 208 Connection result{ target, disposition, flags }; 209 210 THROW_IF_SQLITE_FAILED(sqlite3_extended_result_codes(result.m_dbconn->Get(), 1), result.m_dbconn->Get()); 211 result.SetBusyTimeout(250ms); 212 213 return result; 214 } 215 216 void Connection::EnableICU() 217 { 218 AICLI_LOG(SQL, Verbose, << "Enabling ICU"); 219 THROW_IF_SQLITE_FAILED(sqlite3IcuInit(m_dbconn->Get()), m_dbconn->Get()); 220 } 221 222 rowid_t Connection::GetLastInsertRowID() 223 { 224 return sqlite3_last_insert_rowid(m_dbconn->Get()); 225 } 226 227 int Connection::GetChanges() const 228 { 229 return sqlite3_changes(m_dbconn->Get()); 230 } 231 232 size_t Connection::GetID() const 233 { 234 return m_id; 235 } 236 237 void Connection::SetBusyTimeout(std::chrono::milliseconds timeout) 238 { 239 THROW_IF_SQLITE_FAILED(sqlite3_busy_timeout(m_dbconn->Get(), static_cast<int>(timeout.count())), m_dbconn->Get()); 240 } 241 242 bool Connection::SetJournalMode(std::string_view mode) 243 { 244 using namespace AppInstaller::Utility; 245 246 std::ostringstream stream; 247 stream << "PRAGMA journal_mode=" << mode; 248 249 Statement setJournalMode = Statement::Create(*this, stream.str()); 250 THROW_HR_IF(E_UNEXPECTED, !setJournalMode.Step()); 251 return ToLower(setJournalMode.GetColumn<std::string>(0)) == ToLower(mode); 252 } 253 254 std::shared_ptr<details::SharedConnection> Connection::GetSharedConnection() const 255 { 256 return m_dbconn; 257 } 258 259 Statement::Statement(const Connection& connection, std::string_view sql) 260 { 261 m_dbconn = connection.GetSharedConnection(); 262 m_connectionId = connection.GetID(); 263 m_id = GetNextStatementId(); 264 AICLI_LOG(SQL, Verbose, << "Preparing statement #" << m_connectionId << '-' << m_id << ": " << sql); 265 // SQL string size should include the null terminator (https://www.sqlite.org/c3ref/prepare.html) 266 assert(sql.data()[sql.size()] == '\0'); 267 THROW_IF_SQLITE_FAILED(sqlite3_prepare_v2(connection, sql.data(), static_cast<int>(sql.size() + 1), &m_stmt, nullptr), connection); 268 } 269 270 #if WINGET_SQLITE_EXPLAIN_QUERY_PLAN_ENABLED 271 #define WINGET_SQLITE_EXPLAIN_QUERY_PLAN(_connection_,_sql_) \ 272 std::string _explainStatementSQL_ = "EXPLAIN QUERY PLAN "; \ 273 _explainStatementSQL_.append(_sql_); \ 274 try { \ 275 Statement _explainStatement_(_connection_,_explainStatementSQL_); \ 276 LogExplainQueryPlanResult(_sql_, _explainStatement_); \ 277 } catch(...) {} 278 279 void LogExplainQueryPlanResult(std::string_view sql, Statement& plan) 280 { 281 bool outputHeader = true; 282 std::stack<int> parents; 283 284 while (plan.Step()) 285 { 286 if (outputHeader) 287 { 288 AICLI_LOG(SQL, Info, << "Query plan for: " << sql); 289 outputHeader = false; 290 } 291 292 int id = plan.GetColumn<int>(0); 293 int parent = plan.GetColumn<int>(1); 294 295 while (!parents.empty() && parents.top() != parent) 296 { 297 parents.pop(); 298 } 299 300 AICLI_LOG(SQL, Info, << "|-" << std::string(parents.size() * 2, '-') << ' ' << plan.GetColumn<std::string>(3)); 301 302 parents.push(id); 303 } 304 } 305 #else 306 #define WINGET_SQLITE_EXPLAIN_QUERY_PLAN(_connection_,_sql_) 307 #endif 308 309 Statement Statement::Create(const Connection& connection, const std::string& sql) 310 { 311 WINGET_SQLITE_EXPLAIN_QUERY_PLAN(connection, sql); 312 return { connection, { sql.c_str(), sql.size() } }; 313 } 314 315 Statement Statement::Create(const Connection& connection, std::string_view sql) 316 { 317 WINGET_SQLITE_EXPLAIN_QUERY_PLAN(connection, sql); 318 // We need the statement to be null terminated, and the only way to guarantee that with a string_view is to construct a string copy. 319 return Create(connection, std::string(sql)); 320 } 321 322 Statement Statement::Create(const Connection& connection, char const* const sql) 323 { 324 WINGET_SQLITE_EXPLAIN_QUERY_PLAN(connection, sql); 325 return { connection, sql }; 326 } 327 328 bool Statement::Step(bool closeConnectionOnError) 329 { 330 AICLI_LOG(SQL, Verbose, << "Stepping statement #" << m_connectionId << '-' << m_id); 331 int result = sqlite3_step(m_stmt.get()); 332 333 if (result == SQLITE_ROW) 334 { 335 AICLI_LOG(SQL, Verbose, << "Statement #" << m_connectionId << '-' << m_id << " has data"); 336 m_state = State::HasRow; 337 return true; 338 } 339 else if (result == SQLITE_DONE) 340 { 341 AICLI_LOG(SQL, Verbose, << "Statement #" << m_connectionId << '-' << m_id << " has completed"); 342 m_state = State::Completed; 343 return false; 344 } 345 else 346 { 347 m_state = State::Error; 348 349 if (closeConnectionOnError) 350 { 351 m_dbconn->Disable(); 352 } 353 354 THROW_SQLITE(result, sqlite3_db_handle(m_stmt.get())); 355 } 356 } 357 358 void Statement::Execute(bool closeConnectionOnError) 359 { 360 THROW_HR_IF(E_UNEXPECTED, Step(closeConnectionOnError)); 361 } 362 363 bool Statement::GetColumnIsNull(int column) 364 { 365 int type = sqlite3_column_type(m_stmt.get(), column); 366 return type == SQLITE_NULL; 367 } 368 369 void Statement::Reset() 370 { 371 AICLI_LOG(SQL, Verbose, << "Reset statement #" << m_connectionId << '-' << m_id); 372 // Ignore return value from reset, as if it is an error, it was the error from the last call to step. 373 sqlite3_reset(m_stmt.get()); 374 m_state = State::Prepared; 375 } 376 377 Transaction::Transaction() : m_inProgress(false) 378 {} 379 380 Transaction::Transaction(Connection& connection, std::string&& name, bool immediateWrite) : 381 m_name(std::move(name)) 382 { 383 using namespace std::string_literals; 384 385 Statement begin = Statement::Create(connection, "BEGIN "s + (immediateWrite ? "IMMEDIATE" : "DEFERRED")); 386 m_rollback = Statement::Create(connection, "ROLLBACK"); 387 m_commit = Statement::Create(connection, "COMMIT"); 388 389 AICLI_LOG(SQL, Verbose, << "Begin transaction: " << m_name); 390 begin.Step(); 391 } 392 393 Transaction Transaction::Create(Connection& connection, std::string name, bool immediateWrite) 394 { 395 return { connection, std::move(name), immediateWrite }; 396 } 397 398 Transaction::~Transaction() 399 { 400 // Prevent a termination by not throwing on errors here 401 Rollback(false); 402 } 403 404 void Transaction::Rollback(bool throwOnError) 405 { 406 if (m_inProgress) 407 { 408 // Only try rollback once 409 m_inProgress = false; 410 411 try 412 { 413 AICLI_LOG(SQL, Verbose, << "Roll back transaction: " << m_name); 414 m_rollback.Step(true); 415 } 416 catch (...) 417 { 418 if (throwOnError) 419 { 420 throw; 421 } 422 423 LOG_CAUGHT_EXCEPTION(); 424 } 425 } 426 } 427 428 void Transaction::Commit() 429 { 430 if (m_inProgress) 431 { 432 AICLI_LOG(SQL, Verbose, << "Commit transaction: " << m_name); 433 m_commit.Step(); 434 m_inProgress = false; 435 } 436 } 437 438 Savepoint::Savepoint() : m_inProgress(false) 439 {} 440 441 Savepoint::Savepoint(Connection& connection, std::string&& name) : 442 m_name(std::move(name)) 443 { 444 using namespace std::string_literals; 445 446 Statement begin = Statement::Create(connection, "SAVEPOINT ["s + m_name + "]"); 447 m_rollbackTo = Statement::Create(connection, "ROLLBACK TO ["s + m_name + "]"); 448 m_release = Statement::Create(connection, "RELEASE ["s + m_name + "]"); 449 450 AICLI_LOG(SQL, Verbose, << "Begin savepoint: " << m_name); 451 begin.Step(); 452 } 453 454 Savepoint Savepoint::Create(Connection& connection, std::string name) 455 { 456 return { connection, std::move(name) }; 457 } 458 459 Savepoint::~Savepoint() 460 { 461 // Prevent a termination by not throwing on errors here 462 Rollback(false); 463 } 464 465 void Savepoint::Rollback(bool throwOnError) 466 { 467 if (m_inProgress) 468 { 469 // Only try rollback once 470 m_inProgress = false; 471 472 try 473 { 474 AICLI_LOG(SQL, Verbose, << "Roll back savepoint: " << m_name); 475 m_rollbackTo.Step(true); 476 // 'ROLLBACK TO' *DOES NOT* remove the savepoint from the transaction stack. 477 // In order to remove it, we must RELEASE. Since we just invoked a ROLLBACK TO 478 // this should have the effect of 'committing' nothing. 479 m_release.Step(true); 480 } 481 catch (...) 482 { 483 if (throwOnError) 484 { 485 throw; 486 } 487 488 LOG_CAUGHT_EXCEPTION(); 489 } 490 } 491 } 492 493 void Savepoint::Commit() 494 { 495 if (m_inProgress) 496 { 497 AICLI_LOG(SQL, Verbose, << "Commit savepoint: " << m_name); 498 m_release.Step(); 499 m_inProgress = false; 500 } 501 } 502 503 Backup::Backup(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName) 504 { 505 m_backup.reset(sqlite3_backup_init(destination, destinationName.c_str(), source, sourceName.c_str())); 506 507 if (!m_backup) 508 { 509 THROW_SQLITE(sqlite3_errcode(destination), destination); 510 } 511 } 512 513 Backup Backup::Create(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName) 514 { 515 return { destination, destinationName, source, sourceName }; 516 } 517 518 bool Backup::Step(int pages) 519 { 520 int stepResult = sqlite3_backup_step(m_backup.get(), pages); 521 522 if (stepResult == SQLITE_OK) 523 { 524 // A negative number of pages should finish the operation 525 if (pages < 0) 526 { 527 THROW_HR(E_UNEXPECTED); 528 } 529 530 // Success but not done 531 return false; 532 } 533 else if (stepResult == SQLITE_DONE) 534 { 535 return true; 536 } 537 else 538 { 539 THROW_SQLITE(stepResult, nullptr); 540 } 541 } 542 543 std::string_view EscapeCharForLike = "'"sv; 544 545 std::string EscapeStringForLike(std::string_view value) 546 { 547 constexpr char singleChar = '_'; 548 constexpr char multiChar = '%'; 549 char escapeChar = EscapeCharForLike[0]; 550 551 std::string result; 552 result.reserve(value.length()); 553 554 for (char c : value) 555 { 556 if (c == singleChar || c == multiChar || c == escapeChar) 557 { 558 result.append(1, escapeChar); 559 } 560 result.append(1, c); 561 } 562 563 return result; 564 } 565 }