winget-cli

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

PackageUpdateTrackingTable.cpp (10589B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "PackageUpdateTrackingTable.h"
      5 #include <winget/PackageVersionDataManifest.h>
      6 #include <winget/SQLiteStatementBuilder.h>
      7 
      8 using namespace AppInstaller::SQLite;
      9 
     10 namespace AppInstaller::Repository::Microsoft::Schema::V2_0
     11 {
     12     using namespace std::string_view_literals;
     13     static constexpr std::string_view s_PUTT_Table_Name = "update_tracking"sv;
     14     static constexpr std::string_view s_PUTT_WriteTimeIndex_Name = "update_tracking_write_idx"sv;
     15     static constexpr std::string_view s_PUTT_Package = "package"sv;
     16     static constexpr std::string_view s_PUTT_WriteTime = "write_time"sv;
     17     static constexpr std::string_view s_PUTT_Manifest = "manifest"sv;
     18     static constexpr std::string_view s_PUTT_Hash = "hash"sv;
     19 
     20     std::string_view PackageUpdateTrackingTable::TableName()
     21     {
     22         return s_PUTT_Table_Name;
     23     }
     24 
     25     void PackageUpdateTrackingTable::Create(SQLite::Connection& connection)
     26     {
     27         using namespace Builder;
     28 
     29         StatementBuilder builder;
     30         builder.CreateTable(s_PUTT_Table_Name).BeginColumns();
     31 
     32         builder.Column(IntegerPrimaryKey());
     33         builder.Column(ColumnBuilder(s_PUTT_Package, Type::Text).NotNull());
     34         builder.Column(ColumnBuilder(s_PUTT_WriteTime, Type::Int64).NotNull());
     35         builder.Column(ColumnBuilder(s_PUTT_Manifest, Type::Blob).NotNull());
     36         builder.Column(ColumnBuilder(s_PUTT_Hash, Type::Blob).NotNull());
     37 
     38         builder.EndColumns();
     39 
     40         builder.Execute(connection);
     41 
     42         StatementBuilder indexBuilder;
     43         indexBuilder.CreateIndex(s_PUTT_WriteTimeIndex_Name).On(s_PUTT_Table_Name).Columns(s_PUTT_WriteTime);
     44         indexBuilder.Execute(connection);
     45     }
     46 
     47     void PackageUpdateTrackingTable::EnsureExists(SQLite::Connection& connection)
     48     {
     49         if (!Exists(connection))
     50         {
     51             Create(connection);
     52         }
     53     }
     54 
     55     void PackageUpdateTrackingTable::Drop(SQLite::Connection& connection)
     56     {
     57         Builder::StatementBuilder dropTableBuilder;
     58         dropTableBuilder.DropTable(s_PUTT_Table_Name);
     59 
     60         dropTableBuilder.Execute(connection);
     61     }
     62 
     63     bool PackageUpdateTrackingTable::Exists(const SQLite::Connection& connection)
     64     {
     65         Builder::StatementBuilder builder;
     66         builder.Select(Builder::RowCount).From(Builder::Schema::MainTable).
     67             Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_PUTT_Table_Name);
     68 
     69         Statement statement = builder.Prepare(connection);
     70         THROW_HR_IF(E_UNEXPECTED, !statement.Step());
     71         return statement.GetColumn<int64_t>(0) != 0;
     72     }
     73 
     74     void PackageUpdateTrackingTable::Update(SQLite::Connection& connection, const ISQLiteIndex* internalIndex, const std::string& packageIdentifier, bool ensureTable)
     75     {
     76         if (ensureTable)
     77         {
     78             EnsureExists(connection);
     79         }
     80 
     81         SearchRequest request;
     82         request.Inclusions.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageIdentifier);
     83         auto result = internalIndex->Search(connection, request);
     84 
     85         if (result.Matches.empty())
     86         {
     87             // Remove any existing package update row
     88             Builder::StatementBuilder deleteBuilder;
     89             deleteBuilder.DeleteFrom(s_PUTT_Table_Name).Where(s_PUTT_Package).LikeWithEscape(packageIdentifier);
     90 
     91             deleteBuilder.Execute(connection);
     92         }
     93         else
     94         {
     95             THROW_HR_IF(E_UNEXPECTED, result.Matches.size() != 1);
     96 
     97             // Insert or update the package row
     98             std::vector<ISQLiteIndex::VersionKey> versionKeys = internalIndex->GetVersionKeysById(connection, result.Matches[0].first);
     99 
    100             Manifest::PackageVersionDataManifest manifest;
    101 
    102             for (const auto& key : versionKeys)
    103             {
    104                 Manifest::PackageVersionDataManifest::VersionData versionData{
    105                     key.VersionAndChannel,
    106                     internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ArpMinVersion),
    107                     internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ArpMaxVersion),
    108                     internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::RelativePath),
    109                     internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ManifestSHA256Hash)
    110                 };
    111 
    112                 manifest.AddVersion(std::move(versionData));
    113             }
    114 
    115             std::string manifestString = manifest.Serialize();
    116 
    117             auto compressor = Manifest::PackageVersionDataManifest::CreateCompressor();
    118             std::vector<uint8_t> compressedManifest = compressor.Compress(manifestString);
    119 
    120             Utility::SHA256::HashBuffer manifestHash = Utility::SHA256::ComputeHash(compressedManifest);
    121             int64_t currentTime = Utility::GetCurrentUnixEpoch();
    122 
    123             // First attempt to update the row and then insert it if no modification occurred.
    124             Builder::StatementBuilder updateBuilder;
    125             updateBuilder.Update(s_PUTT_Table_Name).Set().
    126                 Column(s_PUTT_WriteTime).Equals(currentTime).
    127                 Column(s_PUTT_Manifest).Equals(compressedManifest).
    128                 Column(s_PUTT_Hash).Equals(manifestHash).
    129                 Where(s_PUTT_Package).LikeWithEscape(packageIdentifier);
    130 
    131             updateBuilder.Execute(connection);
    132 
    133             if (connection.GetChanges() == 0)
    134             {
    135                 Builder::StatementBuilder insertBuilder;
    136                 insertBuilder.InsertInto(s_PUTT_Table_Name).
    137                     Columns({ s_PUTT_Package, s_PUTT_WriteTime, s_PUTT_Manifest, s_PUTT_Hash }).
    138                     Values(packageIdentifier, currentTime, compressedManifest, manifestHash);
    139 
    140                 insertBuilder.Execute(connection);
    141             }
    142         }
    143     }
    144 
    145     bool PackageUpdateTrackingTable::CheckConsistency(const SQLite::Connection& connection, ISQLiteIndex* internalIndex, bool log)
    146     {
    147         bool result = true;
    148 
    149         // Ensure that all data in the update table matches the internal index
    150         for (const PackageData& packageData : GetUpdatesSince(connection, 0))
    151         {
    152             auto manifestHash = Utility::SHA256::ComputeHash(packageData.Manifest);
    153             if (!Utility::SHA256::AreEqual(packageData.Hash, manifestHash))
    154             {
    155                 if (!log)
    156                 {
    157                     return false;
    158                 }
    159 
    160                 result = false;
    161                 AICLI_LOG(Repo, Info, << "  [INVALID] value [" << s_PUTT_Hash << "] in table [" << s_PUTT_Table_Name <<
    162                     "] at row [" << packageData.RowID << "]; the hash of the manifest value does not match the hash in the row");
    163             }
    164 
    165             SearchRequest request;
    166             request.Inclusions.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageData.PackageIdentifier);
    167 
    168             if (internalIndex->Search(connection, request).Matches.empty())
    169             {
    170                 if (!log)
    171                 {
    172                     return false;
    173                 }
    174 
    175                 result = false;
    176                 AICLI_LOG(Repo, Info, << "  [INVALID] value [" << s_PUTT_Package << "] in table [" << s_PUTT_Table_Name <<
    177                     "] at row [" << packageData.RowID << "]; the package [" << packageData.PackageIdentifier << "] was not found in the internal index");
    178             }
    179         }
    180 
    181         // Ensure that all packages in the internal index are present in the update table
    182         Builder::StatementBuilder builder;
    183         builder.Select(Builder::RowCount).From(s_PUTT_Table_Name).Where(s_PUTT_Package).Like(Builder::Unbound).Escape(EscapeCharForLike);
    184 
    185         Statement select = builder.Prepare(connection);
    186 
    187         for (const auto& packageMatch : internalIndex->Search(connection, {}).Matches)
    188         {
    189             std::vector<ISQLiteIndex::VersionKey> versionKeys = internalIndex->GetVersionKeysById(connection, packageMatch.first);
    190             ISQLiteIndex::VersionKey& latestVersionKey = versionKeys[0];
    191 
    192             std::string packageIdentifier = internalIndex->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Id).value();
    193 
    194             select.Reset();
    195             select.Bind(1, packageIdentifier);
    196             select.Step();
    197 
    198             if (select.GetColumn<int64_t>(0) != 1)
    199             {
    200                 if (!log)
    201                 {
    202                     return false;
    203                 }
    204 
    205                 result = false;
    206                 AICLI_LOG(Repo, Info, << "  [INVALID] value [" << packageIdentifier << "] in the internal index was not found in [" << s_PUTT_Table_Name << "]");
    207             }
    208         }
    209 
    210         return result;
    211     }
    212 
    213     std::vector<PackageUpdateTrackingTable::PackageData> PackageUpdateTrackingTable::GetUpdatesSince(const SQLite::Connection& connection, int64_t updateBaseTime)
    214     {
    215         Builder::StatementBuilder builder;
    216         builder.Select({ RowIDName, s_PUTT_Package, s_PUTT_WriteTime, s_PUTT_Manifest, s_PUTT_Hash }).
    217             From(s_PUTT_Table_Name).Where(s_PUTT_WriteTime).IsGreaterThanOrEqualTo(updateBaseTime);
    218 
    219         Statement select = builder.Prepare(connection);
    220 
    221         std::vector<PackageData> result;
    222 
    223         while (select.Step())
    224         {
    225             PackageData item;
    226             item.RowID = select.GetColumn<rowid_t>(0);
    227             item.PackageIdentifier = select.GetColumn<std::string>(1);
    228             item.WriteTime = select.GetColumn<int64_t>(2);
    229             item.Manifest = select.GetColumn<blob_t>(3);
    230             item.Hash = select.GetColumn<blob_t>(4);
    231 
    232             result.emplace_back(std::move(item));
    233         }
    234 
    235         return result;
    236     }
    237 
    238     SQLite::blob_t PackageUpdateTrackingTable::GetDataHash(const SQLite::Connection& connection, const std::string& packageIdentifier)
    239     {
    240         Builder::StatementBuilder builder;
    241         builder.Select(s_PUTT_Hash).From(s_PUTT_Table_Name).Where(s_PUTT_Package).LikeWithEscape(packageIdentifier);
    242 
    243         Statement select = builder.Prepare(connection);
    244 
    245         THROW_HR_IF(E_NOT_SET, !select.Step());
    246 
    247         return select.GetColumn<SQLite::blob_t>(0);
    248     }
    249 }