winget-cli

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

PathPartTable.cpp (17562B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "PathPartTable.h"
      5 #include <winget/SQLiteStatementBuilder.h>
      6 
      7 
      8 namespace AppInstaller::Repository::Microsoft::Schema::V1_0
      9 {
     10     using namespace std::string_view_literals;
     11     static constexpr std::string_view s_PathPartTable_Table_Name = "pathparts"sv;
     12     static constexpr std::string_view s_PathPartTable_PrimaryKeyIndex_Name = "pathparts_pkindex"sv;
     13     static constexpr std::string_view s_PathPartTable_ParentIndex_Name = "pathparts_parentidx"sv;
     14     static constexpr std::string_view s_PathPartTable_ParentValue_Name = "parent"sv;
     15     static constexpr std::string_view s_PathPartTable_PartValue_Name = "pathpart"sv;
     16 
     17     namespace
     18     {
     19         // Attempts to select a path part given the input.
     20         // Returns an no value if none exists, or the rowid of the part if it is found.
     21         std::optional<SQLite::rowid_t> SelectPathPart(SQLite::Connection& connection, std::optional<SQLite::rowid_t> parent, std::string_view part)
     22         {
     23             SQLite::Builder::StatementBuilder builder;
     24             builder.Select(SQLite::RowIDName).From(s_PathPartTable_Table_Name).
     25                 Where(s_PathPartTable_ParentValue_Name).Equals(parent).And(s_PathPartTable_PartValue_Name).Equals(part);
     26 
     27             SQLite::Statement select = builder.Prepare(connection);
     28 
     29             if (select.Step())
     30             {
     31                 return select.GetColumn<SQLite::rowid_t>(0);
     32             }
     33             else
     34             {
     35                 return {};
     36             }
     37         }
     38 
     39         // Inserts the given path part into the table, returning the rowid of the inserted row.
     40         SQLite::rowid_t InsertPathPart(SQLite::Connection& connection, std::optional<SQLite::rowid_t> parent, std::string_view part)
     41         {
     42             THROW_HR_IF(E_INVALIDARG, part.empty());
     43 
     44             SQLite::Builder::StatementBuilder builder;
     45             builder.InsertInto(s_PathPartTable_Table_Name).Columns({ s_PathPartTable_ParentValue_Name, s_PathPartTable_PartValue_Name }).Values(parent, part);
     46 
     47             builder.Execute(connection);
     48 
     49             return connection.GetLastInsertRowID();
     50         }
     51 
     52         // Inserts the no path part into the table.
     53         SQLite::rowid_t InsertNoPathPart(SQLite::Connection& connection)
     54         {
     55             SQLite::Builder::StatementBuilder builder;
     56             builder.
     57                 InsertInto(s_PathPartTable_Table_Name).
     58                 Columns({ SQLite::RowIDName, s_PathPartTable_ParentValue_Name, s_PathPartTable_PartValue_Name }).
     59                 Values(PathPartTable::NoPathId, std::optional<SQLite::rowid_t>{}, std::string_view{});
     60 
     61             builder.Execute(connection);
     62 
     63             return connection.GetLastInsertRowID();
     64         }
     65 
     66         // Gets the parent of a given part by id.
     67         // This should only be called when the part must exist, as it will throw if not found.
     68         std::optional<SQLite::rowid_t> GetParentById(SQLite::Connection& connection, SQLite::rowid_t id)
     69         {
     70             SQLite::Builder::StatementBuilder builder;
     71             builder.Select(s_PathPartTable_ParentValue_Name).From(s_PathPartTable_Table_Name).Where(SQLite::RowIDName).Equals(id);
     72 
     73             SQLite::Statement select = builder.Prepare(connection);
     74 
     75             THROW_HR_IF(APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED, !select.Step());
     76 
     77             if (!select.GetColumnIsNull(0))
     78             {
     79                 return select.GetColumn<SQLite::rowid_t>(0);
     80             }
     81             else
     82             {
     83                 return {};
     84             }
     85         }
     86 
     87         // Determines if any part references this one as their parent.
     88         bool IsLeafPart(SQLite::Connection& connection, SQLite::rowid_t id)
     89         {
     90             SQLite::Builder::StatementBuilder builder;
     91             builder.Select(SQLite::Builder::RowCount).From(s_PathPartTable_Table_Name).Where(s_PathPartTable_ParentValue_Name).Equals(id);
     92 
     93             SQLite::Statement select = builder.Prepare(connection);
     94 
     95             THROW_HR_IF(E_UNEXPECTED, !select.Step());
     96 
     97             // No rows with this as a parent means it is a leaf.
     98             return (select.GetColumn<int>(0) == 0);
     99         }
    100 
    101         // Removes the given part by id.
    102         void RemovePartById(SQLite::Connection& connection, SQLite::rowid_t id)
    103         {
    104             SQLite::Builder::StatementBuilder builder;
    105             builder.DeleteFrom(s_PathPartTable_Table_Name).Where(SQLite::RowIDName).Equals(id);
    106 
    107             builder.Execute(connection);
    108         }
    109     }
    110 
    111     // Starting in V1.1, all code should be going this route of creating named indices rather than using primary or unique keys on columns.
    112     // The resulting database will function the same, but give us control to drop the indices to reduce space.
    113     void PathPartTable::Create(SQLite::Connection& connection)
    114     {
    115         using namespace SQLite::Builder;
    116 
    117         SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPathParts_v1_1");
    118 
    119         StatementBuilder createTableBuilder;
    120         createTableBuilder.CreateTable(s_PathPartTable_Table_Name).Columns({
    121             IntegerPrimaryKey(),
    122             ColumnBuilder(s_PathPartTable_ParentValue_Name, Type::Int64),
    123             ColumnBuilder(s_PathPartTable_PartValue_Name, Type::Text).NotNull()
    124             });
    125 
    126         createTableBuilder.Execute(connection);
    127 
    128         StatementBuilder createPKIndexBuilder;
    129         createPKIndexBuilder.CreateUniqueIndex(s_PathPartTable_PrimaryKeyIndex_Name).On(s_PathPartTable_Table_Name).Columns({ s_PathPartTable_PartValue_Name, s_PathPartTable_ParentValue_Name });
    130         createPKIndexBuilder.Execute(connection);
    131 
    132         StatementBuilder createIndexBuilder;
    133         createIndexBuilder.CreateIndex(s_PathPartTable_ParentIndex_Name).On(s_PathPartTable_Table_Name).Columns(s_PathPartTable_ParentValue_Name);
    134         createIndexBuilder.Execute(connection);
    135 
    136         savepoint.Commit();
    137     }
    138 
    139     void PathPartTable::Create_deprecated(SQLite::Connection& connection)
    140     {
    141         using namespace SQLite::Builder;
    142 
    143         SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPathParts_v1_0");
    144 
    145         StatementBuilder createTableBuilder;
    146         createTableBuilder.CreateTable(s_PathPartTable_Table_Name).Columns({
    147             ColumnBuilder(s_PathPartTable_ParentValue_Name, Type::Int64),
    148             ColumnBuilder(s_PathPartTable_PartValue_Name, Type::Text).NotNull(),
    149             PrimaryKeyBuilder({ s_PathPartTable_PartValue_Name, s_PathPartTable_ParentValue_Name })
    150             });
    151 
    152         createTableBuilder.Execute(connection);
    153 
    154         StatementBuilder createIndexBuilder;
    155         createIndexBuilder.CreateIndex(s_PathPartTable_ParentIndex_Name).On(s_PathPartTable_Table_Name).Columns(s_PathPartTable_ParentValue_Name);
    156 
    157         createIndexBuilder.Execute(connection);
    158 
    159         savepoint.Commit();
    160     }
    161 
    162     void PathPartTable::Drop(SQLite::Connection& connection)
    163     {
    164         SQLite::Builder::StatementBuilder dropTableBuilder;
    165         dropTableBuilder.DropTable(s_PathPartTable_Table_Name);
    166 
    167         dropTableBuilder.Execute(connection);
    168     }
    169 
    170     std::string_view PathPartTable::TableName()
    171     {
    172         return s_PathPartTable_Table_Name;
    173     }
    174 
    175     std::string_view PathPartTable::ValueName()
    176     {
    177         return s_PathPartTable_PartValue_Name;
    178     }
    179 
    180     std::tuple<bool, SQLite::rowid_t> EnsurePathExistsInternal(SQLite::Connection& connection, const std::filesystem::path& relativePath, bool createIfNotFound)
    181     {
    182         THROW_HR_IF(E_INVALIDARG, !relativePath.has_relative_path());
    183         THROW_HR_IF(E_INVALIDARG, relativePath.has_root_path());
    184         THROW_HR_IF(E_INVALIDARG, !relativePath.has_filename());
    185 
    186         std::unique_ptr<SQLite::Savepoint> savepoint;
    187         if (createIfNotFound)
    188         {
    189             savepoint = std::make_unique<SQLite::Savepoint>(SQLite::Savepoint::Create(connection, "ensurepathexists_v1_0"));
    190         }
    191 
    192         bool partsAdded = false;
    193 
    194         std::optional<SQLite::rowid_t> parent;
    195         for (const auto& part : relativePath)
    196         {
    197             std::string utf8part = part.u8string();
    198             std::optional<SQLite::rowid_t> current = SelectPathPart(connection, parent, utf8part);
    199 
    200             if (!current)
    201             {
    202                 if (createIfNotFound)
    203                 {
    204                     partsAdded = true;
    205                     current = InsertPathPart(connection, parent, utf8part);
    206                 }
    207                 else
    208                 {
    209                     // Current part was not found, and we were told not to create.
    210                     // Return false to indicate that the path does not exist.
    211                     return {};
    212                 }
    213             }
    214 
    215             parent = current;
    216         }
    217 
    218         if (savepoint)
    219         {
    220             savepoint->Commit();
    221         }
    222 
    223         // If we get this far, the path exists.
    224         // If we were asked to create it, return whether we needed to or it was already present.
    225         // If not, then true indicates that it exists.
    226         return { (createIfNotFound ? partsAdded : true), parent.value() };
    227     }
    228 
    229     std::tuple<bool, SQLite::rowid_t> PathPartTable::EnsurePathExists(SQLite::Connection& connection, const std::optional<std::filesystem::path>& relativePath, bool createIfNotFound)
    230     {
    231         if (relativePath)
    232         {
    233             return EnsurePathExistsInternal(connection, relativePath.value(), createIfNotFound);
    234         }
    235 
    236         std::unique_ptr<SQLite::Savepoint> savepoint;
    237         if (createIfNotFound)
    238         {
    239             savepoint = std::make_unique<SQLite::Savepoint>(SQLite::Savepoint::Create(connection, "ensurepathexists_v1_0"));
    240         }
    241 
    242         bool partsAdded = false;
    243 
    244         std::optional<SQLite::rowid_t> noPathPart = SelectPathPart(connection, {}, {});
    245 
    246         if (!noPathPart)
    247         {
    248             if (createIfNotFound)
    249             {
    250                 partsAdded = true;
    251                 noPathPart = InsertNoPathPart(connection);
    252             }
    253             else
    254             {
    255                 // Not found, and we were told not to create.
    256                 // Return false to indicate that the path does not exist.
    257                 return {};
    258             }
    259         }
    260 
    261         if (savepoint)
    262         {
    263             savepoint->Commit();
    264         }
    265 
    266         // If we get this far, the path exists.
    267         // If we were asked to create it, return whether we needed to or it was already present.
    268         // If not, then true indicates that it exists.
    269         return { (createIfNotFound ? partsAdded : true), noPathPart.value() };
    270     }
    271 
    272     std::optional<std::string> PathPartTable::GetPathById(const SQLite::Connection& connection, SQLite::rowid_t id)
    273     {
    274         SQLite::Builder::StatementBuilder builder;
    275         builder.Select({ s_PathPartTable_ParentValue_Name, s_PathPartTable_PartValue_Name }).
    276             From(s_PathPartTable_Table_Name).Where(SQLite::RowIDName).Equals(SQLite::Builder::Unbound);
    277 
    278         SQLite::Statement select = builder.Prepare(connection);
    279 
    280         SQLite::rowid_t currentPart = id;
    281         std::string result;
    282 
    283         while (true)
    284         {
    285             select.Reset();
    286             select.Bind(1, currentPart);
    287 
    288             if (select.Step())
    289             {
    290                 std::string partValue = select.GetColumn<std::string>(1);
    291                 if (result.empty())
    292                 {
    293                     result = partValue;
    294                 }
    295                 else
    296                 {
    297                     result = partValue + '/' + result;
    298                 }
    299 
    300                 if (select.GetColumnIsNull(0))
    301                 {
    302                     // If the parent of this column is null, then we have reached the relative root
    303                     break;
    304                 }
    305                 else
    306                 {
    307                     currentPart = select.GetColumn<SQLite::rowid_t>(0);
    308                 }
    309             }
    310             else
    311             {
    312                 if (currentPart == id)
    313                 {
    314                     // The given id did not reference an actual path
    315                     return {};
    316                 }
    317                 else
    318                 {
    319                     // We found a broken path
    320                     AICLI_LOG(Repo, Error, << "Path part references an invalid parent: " << currentPart);
    321                     THROW_HR(APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED);
    322                 }
    323             }
    324         }
    325 
    326         return result;
    327     }
    328 
    329     void PathPartTable::RemovePathById(SQLite::Connection& connection, SQLite::rowid_t id)
    330     {
    331         // Don't bother removing the pathless id
    332         if (id == NoPathId)
    333         {
    334             return;
    335         }
    336 
    337         SQLite::rowid_t currentPartToRemove = id;
    338         while (IsLeafPart(connection, currentPartToRemove))
    339         {
    340             std::optional<SQLite::rowid_t> parent = GetParentById(connection, currentPartToRemove);
    341             RemovePartById(connection, currentPartToRemove);
    342 
    343             // If parent was NULL, this was a root part and we can stop
    344             if (!parent)
    345             {
    346                 break;
    347             }
    348             else
    349             {
    350                 currentPartToRemove = parent.value();
    351             }
    352         }
    353     }
    354 
    355     void PathPartTable::PrepareForPackaging(SQLite::Connection& connection)
    356     {
    357         SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "pfpPathParts_v1_1");
    358 
    359         PrepareForPackaging_deprecated(connection);
    360 
    361         SQLite::Builder::StatementBuilder dropPKIndexBuilder;
    362         dropPKIndexBuilder.DropIndex(s_PathPartTable_PrimaryKeyIndex_Name);
    363         dropPKIndexBuilder.Execute(connection);
    364 
    365         savepoint.Commit();
    366     }
    367 
    368     void PathPartTable::PrepareForPackaging_deprecated(SQLite::Connection& connection)
    369     {
    370         SQLite::Builder::StatementBuilder dropIndexBuilder;
    371         dropIndexBuilder.DropIndex(s_PathPartTable_ParentIndex_Name);
    372         dropIndexBuilder.Execute(connection);
    373     }
    374 
    375     bool PathPartTable::CheckConsistency(const SQLite::Connection& connection, bool log)
    376     {
    377         using QCol = SQLite::Builder::QualifiedColumn;
    378 
    379         // Build a select statement to find pathpart rows containing references to parents with nonexistent rowids
    380         // Such as:
    381         // Select l.rowid, l.parent from pathparts as l left outer join pathparts as r on l.parent = r.rowid where l.parent is not null and r.pathpart is null
    382         constexpr std::string_view s_left = "left"sv;
    383         constexpr std::string_view s_right = "right"sv;
    384         bool result = true;
    385 
    386         {
    387             SQLite::Builder::StatementBuilder builder;
    388             builder.
    389                 Select({ QCol(s_left, SQLite::RowIDName), QCol(s_left, s_PathPartTable_ParentValue_Name) }).
    390                 From(s_PathPartTable_Table_Name).As(s_left).
    391                 LeftOuterJoin(s_PathPartTable_Table_Name).As(s_right).On(QCol(s_left, s_PathPartTable_ParentValue_Name), QCol(s_right, SQLite::RowIDName)).
    392                 Where(QCol(s_left, s_PathPartTable_ParentValue_Name)).IsNotNull().And(QCol(s_right, s_PathPartTable_PartValue_Name)).IsNull();
    393 
    394             SQLite::Statement select = builder.Prepare(connection);
    395 
    396             while (select.Step())
    397             {
    398                 result = false;
    399 
    400                 if (!log)
    401                 {
    402                     break;
    403                 }
    404 
    405                 AICLI_LOG(Repo, Info, << "  [INVALID] pathparts [" << select.GetColumn<SQLite::rowid_t>(0) << "] refers to " << s_PathPartTable_ParentValue_Name << " [" << select.GetColumn<SQLite::rowid_t>(1) << "]");
    406             }
    407         }
    408 
    409         if (!result && !log)
    410         {
    411             return result;
    412         }
    413 
    414         {
    415             // Build a select statement to find values that contain an embedded null character
    416             // Such as:
    417             // Select count(*) from table where instr(value,char(0))>0
    418             SQLite::Builder::StatementBuilder builder;
    419             builder.
    420                 Select({ SQLite::RowIDName, s_PathPartTable_PartValue_Name }).
    421                 From(s_PathPartTable_Table_Name).
    422                 WhereValueContainsEmbeddedNullCharacter(s_PathPartTable_PartValue_Name);
    423 
    424             SQLite::Statement select = builder.Prepare(connection);
    425 
    426             while (select.Step())
    427             {
    428                 result = false;
    429 
    430                 if (!log)
    431                 {
    432                     break;
    433                 }
    434 
    435                 AICLI_LOG(Repo, Info, << "  [INVALID] value in table [" << s_PathPartTable_Table_Name << "] at row [" << select.GetColumn<SQLite::rowid_t>(0) << "] contains an embedded null character and starts with [" << select.GetColumn<std::string>(1) << "]");
    436             }
    437         }
    438 
    439         return result;
    440     }
    441 
    442     bool PathPartTable::IsEmpty(SQLite::Connection& connection)
    443     {
    444         SQLite::Builder::StatementBuilder builder;
    445         builder.Select(SQLite::Builder::RowCount).From(s_PathPartTable_Table_Name);
    446 
    447         SQLite::Statement countStatement = builder.Prepare(connection);
    448 
    449         THROW_HR_IF(E_UNEXPECTED, !countStatement.Step());
    450 
    451         return (countStatement.GetColumn<int>(0) == 0);
    452     }
    453 }