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 }