1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
|
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/plus_addresses/webdata/plus_address_table.h"
#include <optional>
#include <string_view>
#include <vector>
#include "base/check_op.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "components/affiliations/core/browser/affiliation_utils.h"
#include "components/plus_addresses/plus_address_types.h"
#include "components/sync/base/data_type.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/protocol/data_type_state.pb.h"
#include "components/sync/protocol/entity_metadata.pb.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace plus_addresses {
namespace {
constexpr char kPlusAddressTable[] = "plus_addresses";
constexpr char kProfileId[] = "profile_id";
constexpr char kFacet[] = "facet";
constexpr char kPlusAddress[] = "plus_address";
// The table name uses the legacy name "model type state" as a historic artifact
// to avoid a data migration.
constexpr char kSyncDataTypeState[] = "plus_address_sync_model_type_state";
constexpr char kModelType[] = "model_type";
constexpr char kValue[] = "value";
constexpr char kSyncEntityMetadata[] = "plus_address_sync_entity_metadata";
// kModelType
constexpr char kStorageKey[] = "storage_key";
// kValue
// Expects that `s` is pointing to a query result containing `kProfileId`,
// `kFacet` and `kPlusAddress`, in that order. Attempts to construct a
// `PlusProfile` from that data, returning nullopt if validation fails.
std::optional<PlusProfile> PlusProfileFromStatement(sql::Statement& s) {
affiliations::FacetURI facet =
affiliations::FacetURI::FromPotentiallyInvalidSpec(s.ColumnString(1));
if (!facet.is_valid()) {
// Unless modified through external means, the facet is valid.
return std::nullopt;
}
return PlusProfile(/*profile_id=*/s.ColumnString(0), std::move(facet),
PlusAddress(s.ColumnString(2)),
/*is_confirmed=*/true);
}
// Populates the `metadata_batch`'s data type state with the state stored for
// `data_type`, or the default, if no state is stored.
// Returns false if the data type state is unparsable.
bool GetDataTypeState(sql::Database& db,
syncer::DataType data_type,
syncer::MetadataBatch& metadata_batch) {
sql::Statement data_type_state_query(db.GetUniqueStatement(base::StringPrintf(
"SELECT %s FROM %s WHERE %s=?", kValue, kSyncDataTypeState, kModelType)));
data_type_state_query.BindInt(0,
syncer::DataTypeToStableIdentifier(data_type));
sync_pb::DataTypeState data_type_state;
// When the user just started syncing `data_type`, no data type state is
// persisted yet and `Step()` will fail. Don't treat this as an error, but
// fallback to the default state instead.
if (data_type_state_query.Step() &&
!data_type_state.ParseFromString(
data_type_state_query.ColumnStringView(0))) {
return false;
}
metadata_batch.SetDataTypeState(data_type_state);
return true;
}
// Adds all entity metadata stored for `data_type` to `metadata_batch`.
// Returns false and aborts if unparsable data is encountered.
bool AddEntityMetadata(sql::Database& db,
syncer::DataType data_type,
syncer::MetadataBatch& metadata_batch) {
sql::Statement entity_query(db.GetUniqueStatement(
base::StringPrintf("SELECT %s, %s FROM %s WHERE %s=?", kStorageKey,
kValue, kSyncEntityMetadata, kModelType)));
entity_query.BindInt(0, syncer::DataTypeToStableIdentifier(data_type));
while (entity_query.Step()) {
auto entity_metadata = std::make_unique<sync_pb::EntityMetadata>();
if (!entity_metadata->ParseFromString(entity_query.ColumnStringView(1))) {
return false;
}
metadata_batch.AddMetadata(entity_query.ColumnString(0),
std::move(entity_metadata));
}
return true;
}
// The `WebDatabase` manages multiple `WebDatabaseTable` in a `TypeKey` -> table
// map. Any unique constant, such as the address of a static suffices as a key.
WebDatabaseTable::TypeKey GetKey() {
static int table_key = 0;
return &table_key;
}
} // namespace
PlusAddressTable::PlusAddressTable() = default;
PlusAddressTable::~PlusAddressTable() = default;
// static
PlusAddressTable* PlusAddressTable::FromWebDatabase(WebDatabase* db) {
return static_cast<PlusAddressTable*>(db->GetTable(GetKey()));
}
WebDatabaseTable::TypeKey PlusAddressTable::GetTypeKey() const {
return GetKey();
}
bool PlusAddressTable::CreateTablesIfNecessary() {
return CreatePlusAddressesTable() && CreateSyncDataTypeStateTable() &&
CreateSyncEntityMetadataTable();
}
bool PlusAddressTable::MigrateToVersion(int version,
bool* update_compatible_version) {
switch (version) {
case 126:
*update_compatible_version = false;
return MigrateToVersion126_InitialSchema();
case 127:
*update_compatible_version = true;
return MigrateToVersion127_SyncSupport();
case 128:
*update_compatible_version = true;
return MigrateToVersion128_ProfileIdString();
}
return true;
}
std::vector<PlusProfile> PlusAddressTable::GetPlusProfiles() const {
sql::Statement query(db()->GetUniqueStatement(
base::StringPrintf("SELECT %s, %s, %s FROM %s", kProfileId, kFacet,
kPlusAddress, kPlusAddressTable)));
std::vector<PlusProfile> result;
while (query.Step()) {
if (std::optional<PlusProfile> profile = PlusProfileFromStatement(query)) {
result.push_back(std::move(*profile));
}
}
return result;
}
std::optional<PlusProfile> PlusAddressTable::GetPlusProfileForId(
std::string_view profile_id) const {
sql::Statement query(db()->GetUniqueStatement(
base::StringPrintf("SELECT %s, %s, %s FROM %s WHERE %s=?", kProfileId,
kFacet, kPlusAddress, kPlusAddressTable, kProfileId)));
query.BindString(0, profile_id);
if (!query.Step()) {
return std::nullopt;
}
return PlusProfileFromStatement(query);
}
bool PlusAddressTable::AddOrUpdatePlusProfile(const PlusProfile& profile) {
CHECK(profile.is_confirmed);
sql::Statement query(db()->GetUniqueStatement(base::StringPrintf(
"INSERT OR REPLACE INTO %s (%s, %s, %s) VALUES (?, ?, ?)",
kPlusAddressTable, kProfileId, kFacet, kPlusAddress)));
query.BindString(0, profile.profile_id.value());
query.BindString(1, profile.facet.canonical_spec());
query.BindString(2, *profile.plus_address);
return query.Run();
}
bool PlusAddressTable::RemovePlusProfile(std::string_view profile_id) {
sql::Statement query(db()->GetUniqueStatement(base::StringPrintf(
"DELETE FROM %s WHERE %s=?", kPlusAddressTable, kProfileId)));
query.BindString(0, profile_id);
return query.Run();
}
bool PlusAddressTable::ClearPlusProfiles() {
return db()->Execute(base::StrCat({"DELETE FROM ", kPlusAddressTable}));
}
bool PlusAddressTable::UpdateEntityMetadata(
syncer::DataType data_type,
const std::string& storage_key,
const sync_pb::EntityMetadata& metadata) {
CHECK_EQ(data_type, syncer::PLUS_ADDRESS);
sql::Statement query(db()->GetUniqueStatement(base::StringPrintf(
"INSERT OR REPLACE INTO %s (%s, %s, %s) VALUES (?, ?, ?)",
kSyncEntityMetadata, kModelType, kStorageKey, kValue)));
query.BindInt(0, syncer::DataTypeToStableIdentifier(data_type));
query.BindString(1, storage_key);
query.BindBlob(2, metadata.SerializeAsString());
return query.Run();
}
bool PlusAddressTable::ClearEntityMetadata(syncer::DataType data_type,
const std::string& storage_key) {
CHECK_EQ(data_type, syncer::PLUS_ADDRESS);
sql::Statement query(db()->GetUniqueStatement(
base::StringPrintf("DELETE FROM %s WHERE %s=? AND %s=?",
kSyncEntityMetadata, kModelType, kStorageKey)));
query.BindInt(0, syncer::DataTypeToStableIdentifier(data_type));
query.BindString(1, storage_key);
return query.Run();
}
bool PlusAddressTable::UpdateDataTypeState(
syncer::DataType data_type,
const sync_pb::DataTypeState& data_type_state) {
CHECK_EQ(data_type, syncer::PLUS_ADDRESS);
sql::Statement query(db()->GetUniqueStatement(
base::StringPrintf("INSERT OR REPLACE INTO %s (%s, %s) VALUES (?, ?)",
kSyncDataTypeState, kModelType, kValue)));
query.BindInt(0, syncer::DataTypeToStableIdentifier(data_type));
query.BindBlob(1, data_type_state.SerializeAsString());
return query.Run();
}
bool PlusAddressTable::ClearDataTypeState(syncer::DataType data_type) {
CHECK_EQ(data_type, syncer::PLUS_ADDRESS);
sql::Statement query(db()->GetUniqueStatement(base::StringPrintf(
"DELETE FROM %s WHERE %s=?", kSyncDataTypeState, kModelType)));
query.BindInt(0, syncer::DataTypeToStableIdentifier(data_type));
return query.Run();
}
bool PlusAddressTable::GetAllSyncMetadata(
syncer::DataType data_type,
syncer::MetadataBatch& metadata_batch) {
CHECK_EQ(data_type, syncer::PLUS_ADDRESS);
return GetDataTypeState(*db(), data_type, metadata_batch) &&
AddEntityMetadata(*db(), data_type, metadata_batch);
}
bool PlusAddressTable::CreatePlusAddressesTable() {
return db()->DoesTableExist(kPlusAddressTable) ||
db()->Execute(base::StringPrintf("CREATE TABLE %s (%s VARCHAR PRIMARY "
"KEY, %s VARCHAR, %s VARCHAR)",
kPlusAddressTable, kProfileId, kFacet,
kPlusAddress));
}
bool PlusAddressTable::CreateSyncDataTypeStateTable() {
return db()->DoesTableExist(kSyncDataTypeState) ||
db()->Execute(base::StringPrintf(
"CREATE TABLE %s (%s INTEGER PRIMARY KEY, %s BLOB)",
kSyncDataTypeState, kModelType, kValue));
}
bool PlusAddressTable::CreateSyncEntityMetadataTable() {
return db()->DoesTableExist(kSyncEntityMetadata) ||
db()->Execute(base::StringPrintf(
"CREATE TABLE %s (%s INTEGER, %s VARCHAR, %s BLOB, "
"PRIMARY KEY (%s, %s))",
kSyncEntityMetadata, kModelType, kStorageKey, kValue, kModelType,
kStorageKey));
}
bool PlusAddressTable::MigrateToVersion126_InitialSchema() {
return db()->Execute(
base::StringPrintf("CREATE TABLE %s (%s VARCHAR PRIMARY KEY, %s VARCHAR)",
kPlusAddressTable, kFacet, kPlusAddress));
}
bool PlusAddressTable::MigrateToVersion127_SyncSupport() {
sql::Transaction transaction(db());
// The migration logic drops the existing `kPlusAddressTable` and recreates it
// with a new schema. No data needs to be migrated between the tables, since
// the table was not used yet.
// Then, new `kSyncDataTypeState` and `kSyncEntityMetadata` tables are
// created.
return transaction.Begin() &&
db()->Execute(base::StrCat({"DROP TABLE ", kPlusAddressTable})) &&
db()->Execute(base::StringPrintf("CREATE TABLE %s (%s INTEGER PRIMARY "
"KEY, %s VARCHAR, %s VARCHAR)",
kPlusAddressTable, kProfileId, kFacet,
kPlusAddress)) &&
db()->Execute(base::StringPrintf(
"CREATE TABLE %s (%s INTEGER PRIMARY KEY, %s BLOB)",
kSyncDataTypeState, kModelType, kValue)) &&
db()->Execute(base::StringPrintf(
"CREATE TABLE %s (%s INTEGER, %s VARCHAR, %s BLOB, "
"PRIMARY KEY (%s, %s))",
kSyncEntityMetadata, kModelType, kStorageKey, kValue, kModelType,
kStorageKey)) &&
transaction.Commit();
}
bool PlusAddressTable::MigrateToVersion128_ProfileIdString() {
sql::Transaction transaction(db());
// Recreates `kPlusAddressTable`, with `kProfileId`'s type changed to VARCHAR.
// No data needs to be migrated, since the table was not used yet.
return transaction.Begin() &&
db()->Execute(base::StrCat({"DROP TABLE ", kPlusAddressTable})) &&
db()->Execute(base::StringPrintf("CREATE TABLE %s (%s VARCHAR PRIMARY "
"KEY, %s VARCHAR, %s VARCHAR)",
kPlusAddressTable, kProfileId, kFacet,
kPlusAddress)) &&
transaction.Commit();
}
} // namespace plus_addresses
|