winget-cli

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

commit b55a6920eb3a0c6e4f02ecadf0b545f66e7dfde4
parent ea0ca3419d344a8c13bfa9c4fe263ffb80e0e03b
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Fri, 15 May 2020 19:17:06 -0700

Make Id,Version,Channel key for manifest operations case insensitive through use of LIKE (#168)


Diffstat:
Msrc/AppInstallerCLITests/SQLiteIndex.cpp | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface.cpp | 34+++++++++++++++++++++++-----------
Msrc/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp | 13+++++++++++--
Msrc/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.h | 6+++---
Msrc/AppInstallerRepositoryCore/SQLiteStatementBuilder.cpp | 6++++++
Msrc/AppInstallerRepositoryCore/SQLiteStatementBuilder.h | 2++
6 files changed, 128 insertions(+), 17 deletions(-)

diff --git a/src/AppInstallerCLITests/SQLiteIndex.cpp b/src/AppInstallerCLITests/SQLiteIndex.cpp @@ -29,7 +29,7 @@ SQLiteIndex SimpleTestSetup(const std::string& filePath, Manifest& manifest, std { SQLiteIndex index = SQLiteIndex::CreateNew(filePath, Schema::Version::Latest()); - manifest.Id = "test.id"; + manifest.Id = "Test.Id"; manifest.Name = "Test Name"; manifest.AppMoniker = "testmoniker"; manifest.Version = "1.0.0"; @@ -157,6 +157,10 @@ TEST_CASE("SQLiteIndexCreateAndAddManifestDuplicate", "[sqliteindex]") // Attempting to add the same manifest at a different path should fail. REQUIRE_THROWS_HR(index.AddManifest(manifest, "differentpath.yaml"), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); + // Attempting to add the same manifest with a differently cased Id at a different path should fail. + manifest.Id = ToLower(manifest.Id); + REQUIRE_THROWS_HR(index.AddManifest(manifest, "differentpath.yaml"), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); + // Attempting to add a different manifest at the same path should fail. manifest.Id += "-new"; REQUIRE_THROWS_HR(index.AddManifest(manifest, relativePath), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); @@ -449,6 +453,84 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangePath", "[sqliteindex][V1_0]") REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); } +TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string manifestPath = "test/id/test.id-1.0.0.yaml"; + Manifest manifest; + manifest.Id = "test.id"; + manifest.Name = "Test Name"; + manifest.AppMoniker = "testmoniker"; + manifest.Version = "1.0.0-test"; + manifest.Channel = "test"; + manifest.Tags = { "t1", "t2" }; + manifest.Commands = { "test1", "test2" }; + + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); + + index.AddManifest(manifest, manifestPath); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + + manifest.Id = "Test.Id"; + + // Update with path update should indicate change + REQUIRE(index.UpdateManifest(manifest, manifestPath)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + + manifest.Version = "1.0.0-Test"; + + // Update with path update should indicate change + REQUIRE(index.UpdateManifest(manifest, manifestPath)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + + manifest.Channel = "Test"; + + // Update with path update should indicate change + REQUIRE(index.UpdateManifest(manifest, manifestPath)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + + manifest.Name = "test name"; + + // Update with path update should indicate change + REQUIRE(index.UpdateManifest(manifest, manifestPath)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); + + // Now remove manifest, with unknown path + index.RemoveManifest(manifest, ""); + } + + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); +} + TEST_CASE("PathPartTable_EnsurePathExists_Negative_Paths", "[sqliteindex][V1_0]") { // Open it directly to directly test pathpart table diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface.cpp @@ -26,21 +26,21 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 // Gets an existing manifest by its rowid., if it exists. std::optional<SQLite::rowid_t> GetExistingManifestId(SQLite::Connection& connection, const Manifest::Manifest& manifest) { - std::optional<SQLite::rowid_t> idId = IdTable::SelectIdByValue(connection, manifest.Id); + std::optional<SQLite::rowid_t> idId = IdTable::SelectIdByValue(connection, manifest.Id, true); if (!idId) { AICLI_LOG(Repo, Info, << "Did not find an Id { " << manifest.Id << " }"); return {}; } - std::optional<SQLite::rowid_t> versionId = VersionTable::SelectIdByValue(connection, manifest.Version); + std::optional<SQLite::rowid_t> versionId = VersionTable::SelectIdByValue(connection, manifest.Version, true); if (!versionId) { AICLI_LOG(Repo, Info, << "Did not find a Version { " << manifest.Version << " }"); return {}; } - std::optional<SQLite::rowid_t> channelId = ChannelTable::SelectIdByValue(connection, manifest.Channel); + std::optional<SQLite::rowid_t> channelId = ChannelTable::SelectIdByValue(connection, manifest.Channel, true); if (!channelId) { AICLI_LOG(Repo, Info, << "Did not find a Channel { " << manifest.Channel << " }"); @@ -239,16 +239,28 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 auto [idInIndex, nameInIndex, monikerInIndex, versionInIndex, channelInIndex] = ManifestTable::GetValuesById<IdTable, NameTable, MonikerTable, VersionTable, ChannelTable>(connection, manifestId); - // We know that the Id, Version, and Channel did not change based on GetExistingManifestId, - // but we still verify that here in the event that the code there changed. - THROW_HR_IF(E_UNEXPECTED, idInIndex != manifest.Id); - THROW_HR_IF(E_UNEXPECTED, versionInIndex != manifest.Version); - THROW_HR_IF(E_UNEXPECTED, channelInIndex != manifest.Channel); - - bool indexModified = false; SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_0"); + bool indexModified = false; + + // Id, Version, and Channel may have changed casing. If so, they too need to be updated. + if (idInIndex != manifest.Id) + { + UpdateManifestValueById<IdTable>(connection, manifest.Id, manifestId); + indexModified = true; + } + + if (versionInIndex != manifest.Version) + { + UpdateManifestValueById<VersionTable>(connection, manifest.Version, manifestId); + indexModified = true; + } + + if (channelInIndex != manifest.Channel) + { + UpdateManifestValueById<ChannelTable>(connection, manifest.Channel, manifestId); + indexModified = true; + } - // If these values changed, we need to update them. if (nameInIndex != manifest.Name) { UpdateManifestValueById<NameTable>(connection, manifest.Name, manifestId); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp @@ -22,10 +22,19 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 createTableBuilder.Execute(connection); } - std::optional<SQLite::rowid_t> OneToOneTableSelectIdByValue(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value) + std::optional<SQLite::rowid_t> OneToOneTableSelectIdByValue(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike) { SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName).Equals(value); + selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName); + + if (useLike) + { + selectBuilder.LikeWithEscape(value); + } + else + { + selectBuilder.Equals(value); + } SQLite::Statement select = selectBuilder.Prepare(connection); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.h @@ -16,7 +16,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 void CreateOneToOneTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName); // Selects the value from the table, returning the rowid if it exists. - std::optional<SQLite::rowid_t> OneToOneTableSelectIdByValue(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value); + std::optional<SQLite::rowid_t> OneToOneTableSelectIdByValue(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike = false); // Selects the value from the table, returning the rowid if it exists. std::optional<std::string> OneToOneTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id); @@ -72,9 +72,9 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 } // Selects the value from the table, returning the rowid if it exists. - static std::optional<SQLite::rowid_t> SelectIdByValue(SQLite::Connection& connection, std::string_view value) + static std::optional<SQLite::rowid_t> SelectIdByValue(SQLite::Connection& connection, std::string_view value, bool useLike = false) { - return details::OneToOneTableSelectIdByValue(connection, TableInfo::TableName(), TableInfo::ValueName(), value); + return details::OneToOneTableSelectIdByValue(connection, TableInfo::TableName(), TableInfo::ValueName(), value, useLike); } // Selects the value from the table, returning it if it exists. diff --git a/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.cpp b/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.cpp @@ -297,6 +297,12 @@ namespace AppInstaller::Repository::SQLite::Builder return *this; } + StatementBuilder& StatementBuilder::LikeWithEscape(std::string_view value) + { + AddBindFunctor(AppendOpAndBinder(Op::Like), EscapeStringForLike(value)); + return Escape(EscapeCharForLike); + } + StatementBuilder& StatementBuilder::Like(details::unbound_t) { AppendOpAndBinder(Op::Like); diff --git a/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.h b/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.h @@ -202,7 +202,9 @@ namespace AppInstaller::Repository::SQLite::Builder StatementBuilder& Equals(details::unbound_t); StatementBuilder& Equals(std::nullptr_t); + StatementBuilder& LikeWithEscape(std::string_view value); StatementBuilder& Like(details::unbound_t); + StatementBuilder& Escape(std::string_view escapeChar); StatementBuilder& Not();