winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }