commit e1ff71f57dfbb5f27bfae82b18cefea203f0d2e6
parent f4f312a6abe801eba5f5d7e95ac6eafca722991e
Author: JohnMcPMS <johnmcp@microsoft.com>
Date: Fri, 9 Oct 2020 11:00:43 -0700
Create integer primary keys in tables that need to be referenced (all but maps) (#604)
Diffstat:
6 files changed, 97 insertions(+), 0 deletions(-)
diff --git a/src/AppInstallerCLITests/SQLiteIndex.cpp b/src/AppInstallerCLITests/SQLiteIndex.cpp
@@ -376,6 +376,68 @@ TEST_CASE("SQLiteIndex_RemoveManifest", "[sqliteindex][V1_0]")
REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection));
}
+TEST_CASE("SQLiteIndex_RemoveManifest_EnsureConsistentRowId", "[sqliteindex]")
+{
+ TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
+ INFO("Using temporary file named: " << tempFile.GetPath());
+
+ std::string manifest1Path = "test/id/test.id-1.0.0.yaml";
+ Manifest manifest1;
+ manifest1.Id = "test.id";
+ manifest1.Name = "Test Name";
+ manifest1.AppMoniker = "testmoniker";
+ manifest1.Version = "1.0.0";
+ manifest1.Channel = "test";
+ manifest1.Tags = { "t1", "t2" };
+ manifest1.Commands = { "test1", "test2" };
+
+ std::string manifest2Path = "test/woah/test.id-1.0.0.yaml";
+ Manifest manifest2;
+ manifest2.Id = "test.woah";
+ manifest2.Name = "Test Name WOAH";
+ manifest2.AppMoniker = "testmoniker";
+ manifest2.Version = "1.0.0";
+ manifest2.Channel = "test";
+ manifest2.Tags = {};
+ manifest2.Commands = { "test1", "test2", "test3" };
+
+ SQLiteIndex index = CreateTestIndex(tempFile);
+
+ index.AddManifest(manifest1, manifest1Path);
+ index.AddManifest(manifest2, manifest2Path);
+
+ // Get the second manifest's id for validating consistency
+ SearchRequest request;
+ request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, manifest2.Id));
+ auto result = index.Search(request);
+
+ REQUIRE(result.Matches.size() == 1);
+ auto manifest2IdRowId = result.Matches[0].first;
+
+ auto rowId = index.GetManifestIdByKey(manifest2IdRowId, {}, {});
+ REQUIRE(rowId);
+ auto manifest2RowId = rowId.value();
+
+ // Now remove manifest1 and prepare
+ index.RemoveManifest(manifest1, manifest1Path);
+ index.PrepareForPackaging();
+
+ // Repeat search to ensure consistent ids
+ result = index.Search(request);
+ REQUIRE(result.Matches.size() == 1);
+ REQUIRE(result.Matches[0].first == manifest2IdRowId);
+
+ rowId = index.GetManifestIdByKey(manifest2IdRowId, {}, {});
+ REQUIRE(rowId);
+ REQUIRE(rowId.value() == manifest2RowId);
+
+ REQUIRE(manifest2.Id == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Id));
+ REQUIRE(manifest2.Name == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Name));
+ REQUIRE(manifest2.Version == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Version));
+ REQUIRE(manifest2.Channel == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Channel));
+ REQUIRE(manifest2Path == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::RelativePath));
+}
+
TEST_CASE("SQLiteIndex_RemoveManifestFile", "[sqliteindex][V1_0]")
{
TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.cpp
@@ -240,6 +240,9 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0
StatementBuilder createTableBuilder;
createTableBuilder.CreateTable(s_ManifestTable_Table_Name).BeginColumns();
+ // Add an integer primary key to keep the manifest rowid consistent
+ createTableBuilder.Column(IntegerPrimaryKey());
+
for (const ManifestColumnInfo& value : values)
{
createTableBuilder.Column(ColumnBuilder(value.Name, Type::Int64).NotNull());
diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp
@@ -26,6 +26,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0
StatementBuilder createTableBuilder;
createTableBuilder.CreateTable(tableName).Columns({
+ IntegerPrimaryKey(),
ColumnBuilder(valueName, Type::Text).NotNull()
});
diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.cpp
@@ -104,6 +104,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0
StatementBuilder createTableBuilder;
createTableBuilder.CreateTable(s_PathPartTable_Table_Name).Columns({
+ IntegerPrimaryKey(),
ColumnBuilder(s_PathPartTable_ParentValue_Name, Type::Int64),
ColumnBuilder(s_PathPartTable_PartValue_Name, Type::Text).NotNull()
});
diff --git a/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.cpp b/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.cpp
@@ -143,6 +143,20 @@ namespace AppInstaller::Repository::SQLite::Builder
}
}
+ IntegerPrimaryKey::IntegerPrimaryKey()
+ {
+ m_stream << SQLite::RowIDName << " INTEGER PRIMARY KEY";
+ }
+
+ IntegerPrimaryKey& IntegerPrimaryKey::AutoIncrement(bool isTrue)
+ {
+ if (isTrue)
+ {
+ m_stream << " AUTOINCREMENT";
+ }
+ return *this;
+ }
+
ColumnBuilder::ColumnBuilder(std::string_view column, Type type)
{
OutputColumns(m_stream, "", column);
diff --git a/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.h b/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.h
@@ -97,6 +97,22 @@ namespace AppInstaller::Repository::SQLite::Builder
Min
};
+ // Helper to mark create an integer primary key for rowid, making it stable across vacuum.
+ struct IntegerPrimaryKey : public details::SubBuilderBase
+ {
+ IntegerPrimaryKey();
+
+ IntegerPrimaryKey(const IntegerPrimaryKey&) = default;
+ IntegerPrimaryKey& operator=(const IntegerPrimaryKey&) = default;
+
+ IntegerPrimaryKey(IntegerPrimaryKey&&) noexcept = default;
+ IntegerPrimaryKey& operator=(IntegerPrimaryKey&&) noexcept = default;
+
+ // Set the column to autoincrement. SQLite recommends against using this value unless
+ // you need to ensure that rowids are not ever reused.
+ IntegerPrimaryKey& AutoIncrement(bool isTrue = true);
+ };
+
// Helper used when creating a table.
struct ColumnBuilder : public details::SubBuilderBase
{