OneToOneTable.cpp (8671B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Microsoft/Schema/1_0/OneToOneTable.h" 5 #include "Microsoft/Schema/1_0/ManifestTable.h" 6 #include <winget/SQLiteStatementBuilder.h> 7 8 9 namespace AppInstaller::Repository::Microsoft::Schema::V1_0 10 { 11 namespace details 12 { 13 using namespace std::string_view_literals; 14 static constexpr std::string_view s_OneToOneTable_IndexSuffix = "_pkindex"sv; 15 16 void CreateOneToOneTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool useNamedIndices) 17 { 18 using namespace SQLite::Builder; 19 20 // Starting in V1.1, all code should be going this route of creating named indices rather than using primary or unique keys on columns. 21 // The resulting database will function the same, but give us control to drop the indices to reduce space. 22 if (useNamedIndices) 23 { 24 SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v1_1"); 25 26 StatementBuilder createTableBuilder; 27 28 createTableBuilder.CreateTable(tableName).Columns({ 29 IntegerPrimaryKey(), 30 ColumnBuilder(valueName, Type::Text).NotNull() 31 }); 32 33 createTableBuilder.Execute(connection); 34 35 StatementBuilder indexBuilder; 36 indexBuilder.CreateUniqueIndex({ tableName, s_OneToOneTable_IndexSuffix }).On(tableName).Columns(valueName); 37 indexBuilder.Execute(connection); 38 39 savepoint.Commit(); 40 } 41 else 42 { 43 StatementBuilder createTableBuilder; 44 45 createTableBuilder.CreateTable(tableName).Columns({ 46 ColumnBuilder(valueName, Type::Text).NotNull().PrimaryKey() 47 }); 48 49 createTableBuilder.Execute(connection); 50 } 51 } 52 53 void DropOneToOneTable(SQLite::Connection& connection, std::string_view tableName) 54 { 55 SQLite::Builder::StatementBuilder dropTableBuilder; 56 dropTableBuilder.DropTable(tableName); 57 58 dropTableBuilder.Execute(connection); 59 } 60 61 std::optional<SQLite::rowid_t> OneToOneTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike) 62 { 63 SQLite::Builder::StatementBuilder selectBuilder; 64 selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName); 65 66 if (useLike) 67 { 68 selectBuilder.LikeWithEscape(value); 69 } 70 else 71 { 72 selectBuilder.Equals(value); 73 } 74 75 SQLite::Statement select = selectBuilder.Prepare(connection); 76 77 if (select.Step()) 78 { 79 return select.GetColumn<SQLite::rowid_t>(0); 80 } 81 else 82 { 83 return {}; 84 } 85 } 86 87 std::optional<std::string> OneToOneTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) 88 { 89 SQLite::Builder::StatementBuilder selectBuilder; 90 selectBuilder.Select(valueName).From(tableName).Where(SQLite::RowIDName).Equals(id); 91 92 SQLite::Statement select = selectBuilder.Prepare(connection); 93 94 if (select.Step()) 95 { 96 return select.GetColumn<std::string>(0); 97 } 98 else 99 { 100 return {}; 101 } 102 } 103 104 std::vector<SQLite::rowid_t> OneToOneTableGetAllRowIds(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, size_t limit) 105 { 106 SQLite::Builder::StatementBuilder selectBuilder; 107 selectBuilder.Select(SQLite::RowIDName).From(tableName).OrderBy(valueName); 108 109 if (limit) 110 { 111 selectBuilder.Limit(limit); 112 } 113 114 SQLite::Statement select = selectBuilder.Prepare(connection); 115 116 std::vector<SQLite::rowid_t> result; 117 while (select.Step()) 118 { 119 result.emplace_back(select.GetColumn<SQLite::rowid_t>(0)); 120 } 121 return result; 122 } 123 124 SQLite::rowid_t OneToOneTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch) 125 { 126 auto selectResult = OneToOneTableSelectIdByValue(connection, tableName, valueName, value, overwriteLikeMatch); 127 if (selectResult) 128 { 129 if (overwriteLikeMatch) 130 { 131 // If the value in the table is not an exact match, overwrite it with the incoming value 132 auto tableValue = OneToOneTableSelectValueById(connection, tableName, valueName, selectResult.value()); 133 if (tableValue.value() != value) 134 { 135 SQLite::Builder::StatementBuilder updateBuilder; 136 updateBuilder.Update(tableName).Set().Column(valueName).Equals(value).Where(SQLite::RowIDName).Equals(selectResult); 137 138 updateBuilder.Execute(connection); 139 } 140 } 141 142 return selectResult.value(); 143 } 144 145 SQLite::Builder::StatementBuilder insertBuilder; 146 insertBuilder.InsertInto(tableName).Columns(valueName).Values(value); 147 148 insertBuilder.Execute(connection); 149 150 return connection.GetLastInsertRowID(); 151 } 152 153 void OneToOneTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, bool useNamedIndices, bool preserveValuesIndex) 154 { 155 if (useNamedIndices && !preserveValuesIndex) 156 { 157 SQLite::Builder::StatementBuilder dropIndexBuilder; 158 dropIndexBuilder.DropIndex({ tableName, s_OneToOneTable_IndexSuffix }); 159 dropIndexBuilder.Execute(connection); 160 } 161 } 162 163 uint64_t OneToOneTableGetCount(const SQLite::Connection& connection, std::string_view tableName) 164 { 165 SQLite::Builder::StatementBuilder builder; 166 builder.Select(SQLite::Builder::RowCount).From(tableName); 167 168 SQLite::Statement countStatement = builder.Prepare(connection); 169 170 THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); 171 172 return static_cast<uint64_t>(countStatement.GetColumn<SQLite::rowid_t>(0)); 173 } 174 175 bool OneToOneTableIsEmpty(SQLite::Connection& connection, std::string_view tableName) 176 { 177 return (OneToOneTableGetCount(connection, tableName) == 0); 178 } 179 180 void OneToOneTableDeleteById(SQLite::Connection& connection, std::string_view tableName, SQLite::rowid_t id) 181 { 182 SQLite::Builder::StatementBuilder builder; 183 builder.DeleteFrom(tableName).Where(SQLite::RowIDName).Equals(id); 184 185 builder.Execute(connection); 186 } 187 188 bool OneToOneTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) 189 { 190 // Build a select statement to find values that contain an embedded null character 191 // Such as: 192 // Select count(*) from table where instr(value,char(0))>0 193 SQLite::Builder::StatementBuilder builder; 194 builder. 195 Select({ SQLite::RowIDName, valueName }). 196 From(tableName). 197 WhereValueContainsEmbeddedNullCharacter(valueName); 198 199 SQLite::Statement select = builder.Prepare(connection); 200 bool result = true; 201 202 while (select.Step()) 203 { 204 result = false; 205 206 if (!log) 207 { 208 break; 209 } 210 211 AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] at row [" << select.GetColumn<SQLite::rowid_t>(0) << "] contains an embedded null character and starts with [" << select.GetColumn<std::string>(1) << "]"); 212 } 213 214 return result; 215 } 216 } 217 }