winget-cli

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

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 }