blob: ff85a1efc17c429ef144040a7aa3541ffd566af8 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "storage/browser/quota/quota_database_migrations.h"
#include <string>
#include "base/metrics/histogram_functions.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "components/services/storage/public/cpp/buckets/bucket_id.h"
#include "components/services/storage/public/cpp/buckets/constants.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "storage/browser/quota/quota_database.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/buckets/bucket_manager_host.mojom.h"
namespace storage {
namespace {
// The name for the implicit/default bucket before V10.
constexpr char kDefaultNamePreV10[] = "default";
// Overwrites the buckets table with the new_buckets table after data has been
// copied from the former into the latter.
bool OverwriteBucketsTableSetUpIndexes(sql::Database* db) {
// Replace buckets table with new table.
static constexpr char kDeleteBucketTableSql[] = "DROP TABLE buckets";
if (!db->Execute(kDeleteBucketTableSql))
return false;
static constexpr char kRenameBucketTableSql[] =
"ALTER TABLE new_buckets RENAME to buckets";
if (!db->Execute(kRenameBucketTableSql))
return false;
// Create indices on new table.
// clang-format off
static constexpr char kStorageKeyIndexSql[] =
"CREATE UNIQUE INDEX buckets_by_storage_key "
"ON buckets(storage_key, type, name)";
// clang-format on
if (!db->Execute(kStorageKeyIndexSql))
return false;
static constexpr char kHostIndexSql[] =
"CREATE INDEX buckets_by_host ON buckets(host, type)";
if (!db->Execute(kHostIndexSql))
return false;
static constexpr char kLastAccessedIndexSql[] =
"CREATE INDEX buckets_by_last_accessed ON buckets(type, last_accessed)";
if (!db->Execute(kLastAccessedIndexSql))
return false;
static constexpr char kLastModifiedIndexSql[] =
"CREATE INDEX buckets_by_last_modified ON buckets(type, last_modified)";
if (!db->Execute(kLastModifiedIndexSql))
return false;
static constexpr char kExpirationIndexSql[] =
"CREATE INDEX buckets_by_expiration ON buckets(expiration)";
if (!db->Execute(kExpirationIndexSql))
return false;
return true;
}
} // namespace
// static
bool QuotaDatabaseMigrations::UpgradeSchema(QuotaDatabase& quota_database) {
DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database.sequence_checker_);
DCHECK_EQ(0, quota_database.db_->transaction_nesting());
// Reset tables for versions lower than 7 since they are unsupported.
if (quota_database.meta_table_->GetVersionNumber() < 7) {
return false;
}
if (quota_database.meta_table_->GetVersionNumber() == 7) {
bool success = MigrateFromVersion7ToVersion8(quota_database);
RecordMigrationHistogram(/*old_version=*/7, /*new_version=*/8, success);
if (!success)
return false;
}
if (quota_database.meta_table_->GetVersionNumber() == 8) {
bool success = MigrateFromVersion8ToVersion9(quota_database);
RecordMigrationHistogram(/*old_version=*/8, /*new_version=*/9, success);
if (!success)
return false;
}
if (quota_database.meta_table_->GetVersionNumber() == 9) {
bool success = MigrateFromVersion9ToVersion10(quota_database);
RecordMigrationHistogram(/*old_version=*/9, /*new_version=*/10, success);
if (!success) {
return false;
}
}
return quota_database.meta_table_->GetVersionNumber() == 10;
}
void QuotaDatabaseMigrations::RecordMigrationHistogram(int old_version,
int new_version,
bool success) {
base::UmaHistogramBoolean(
base::StrCat({"Quota.DatabaseMigrationFromV",
base::NumberToString(old_version), "ToV",
base::NumberToString(new_version)}),
success);
}
bool QuotaDatabaseMigrations::MigrateFromVersion7ToVersion8(
QuotaDatabase& quota_database) {
DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database.sequence_checker_);
sql::Database* db = quota_database.db_.get();
sql::Transaction transaction(db);
if (!transaction.Begin())
return false;
// Create new buckets table.
// clang-format off
static constexpr char kNewBucketsTableSql[] =
"CREATE TABLE IF NOT EXISTS new_buckets("
"id INTEGER PRIMARY KEY, "
"storage_key TEXT NOT NULL, "
"host TEXT NOT NULL, "
"type INTEGER NOT NULL, "
"name TEXT NOT NULL, "
"use_count INTEGER NOT NULL, "
"last_accessed INTEGER NOT NULL, "
"last_modified INTEGER NOT NULL, "
"expiration INTEGER NOT NULL, "
"quota INTEGER NOT NULL)";
// clang-format on
if (!db->Execute(kNewBucketsTableSql))
return false;
// clang-format off
static constexpr char kSelectBucketSql[] =
"SELECT "
"id,origin,type,name,use_count,last_accessed,last_modified,"
"expiration, quota "
"FROM buckets "
"WHERE id > ? "
"ORDER BY id "
"LIMIT 1";
// clang-format on
sql::Statement select_statement(
db->GetCachedStatement(SQL_FROM_HERE, kSelectBucketSql));
// clang-format off
static constexpr char kInsertBucketSql[] =
"INSERT into new_buckets("
"id,storage_key,host,type,name,use_count,last_accessed,"
"last_modified,expiration,quota) "
"VALUES(?,?,?,?,?,?,?,?,?,?)";
// clang-format on
sql::Statement insert_statement(
db->GetCachedStatement(SQL_FROM_HERE, kInsertBucketSql));
// Transfer bucket data to the new table one at a time with new host data.
BucketId last_bucket_id(0);
while (true) {
select_statement.BindInt64(0, last_bucket_id.value());
if (!select_statement.Step())
break;
// Populate bucket id.
BucketId bucket_id(select_statement.ColumnInt64(0));
insert_statement.BindInt64(0, bucket_id.value());
// Populate storage key and host.
std::string storage_key_string = select_statement.ColumnString(1);
insert_statement.BindString(1, storage_key_string);
std::optional<blink::StorageKey> storage_key =
blink::StorageKey::Deserialize(storage_key_string);
const std::string& host = storage_key.has_value()
? storage_key.value().origin().host()
: std::string();
insert_statement.BindString(2, host);
// Populate type, name, use_count, last_accessed, last_modified,
// expiration and quota.
insert_statement.BindInt(3, select_statement.ColumnInt(2));
insert_statement.BindString(4, select_statement.ColumnString(3));
insert_statement.BindInt(5, select_statement.ColumnInt(4));
insert_statement.BindTime(6, select_statement.ColumnTime(5));
insert_statement.BindTime(7, select_statement.ColumnTime(6));
insert_statement.BindTime(8, select_statement.ColumnTime(7));
insert_statement.BindInt(9, select_statement.ColumnInt(8));
if (!insert_statement.Run())
return false;
select_statement.Reset(/*clear_bound_vars=*/true);
insert_statement.Reset(/*clear_bound_vars=*/true);
last_bucket_id = bucket_id;
}
if (!OverwriteBucketsTableSetUpIndexes(db))
return false;
// Mark database as up to date.
return quota_database.meta_table_->SetVersionNumber(8) &&
quota_database.meta_table_->SetCompatibleVersionNumber(8) &&
transaction.Commit();
}
bool QuotaDatabaseMigrations::MigrateFromVersion8ToVersion9(
QuotaDatabase& quota_database) {
DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database.sequence_checker_);
sql::Database* db = quota_database.db_.get();
sql::Transaction transaction(db);
if (!transaction.Begin())
return false;
// Create new buckets table.
// clang-format off
static constexpr char kNewBucketsTableSql[] =
"CREATE TABLE IF NOT EXISTS new_buckets("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"storage_key TEXT NOT NULL, "
"host TEXT NOT NULL, "
"type INTEGER NOT NULL, "
"name TEXT NOT NULL, "
"use_count INTEGER NOT NULL, "
"last_accessed INTEGER NOT NULL, "
"last_modified INTEGER NOT NULL, "
"expiration INTEGER NOT NULL, "
"quota INTEGER NOT NULL, "
"persistent INTEGER NOT NULL, "
"durability INTEGER NOT NULL) "
"STRICT";
// clang-format on
if (!db->Execute(kNewBucketsTableSql))
return false;
// clang-format off
static constexpr char kSelectBucketSql[] =
"SELECT "
"id,storage_key,host,type,name,use_count,last_accessed,last_modified,"
"expiration,quota "
"FROM buckets "
"WHERE id > ? "
"ORDER BY id "
"LIMIT 1";
// clang-format on
sql::Statement select_statement(
db->GetCachedStatement(SQL_FROM_HERE, kSelectBucketSql));
// clang-format off
static constexpr char kInsertBucketSql[] =
"INSERT into new_buckets("
"id,storage_key,host,type,name,use_count,last_accessed,last_modified,"
"expiration,quota,persistent,durability)"
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?)";
// clang-format on
sql::Statement insert_statement(
db->GetCachedStatement(SQL_FROM_HERE, kInsertBucketSql));
// Transfer bucket data to the new table one at a time with new
// persistence/durability (default) values.
BucketId last_bucket_id(0);
while (true) {
select_statement.BindInt64(0, last_bucket_id.value());
if (!select_statement.Step())
break;
last_bucket_id = BucketId(select_statement.ColumnInt64(0));
insert_statement.BindInt64(0, select_statement.ColumnInt64(0));
insert_statement.BindString(1, select_statement.ColumnString(1));
insert_statement.BindString(2, select_statement.ColumnString(2));
insert_statement.BindInt(3, select_statement.ColumnInt(3));
const std::string bucket_name = select_statement.ColumnString(4);
insert_statement.BindString(4, bucket_name);
insert_statement.BindInt(5, select_statement.ColumnInt(5));
insert_statement.BindTime(6, select_statement.ColumnTime(6));
insert_statement.BindTime(7, select_statement.ColumnTime(7));
// Prior to version 9, the default value for `expiration` was
// base::Time::Max(), and the value was unused. For version 9+, the default
// value is base::Time(). Reset old values to the new default.
insert_statement.BindTime(8, base::Time());
insert_statement.BindInt(9, select_statement.ColumnInt(9));
insert_statement.BindBool(10, false);
// The default durability depends on whether the bucket is default. As of
// the time of this migration, non-default buckets are not supported without
// a flag, but check the name anyway for correctness.
insert_statement.BindInt(
11, static_cast<int>(bucket_name == kDefaultNamePreV10
? blink::mojom::BucketDurability::kStrict
: blink::mojom::BucketDurability::kRelaxed));
if (!insert_statement.Run())
return false;
select_statement.Reset(/*clear_bound_vars=*/true);
insert_statement.Reset(/*clear_bound_vars=*/true);
}
if (!OverwriteBucketsTableSetUpIndexes(db))
return false;
// Mark database as up to date.
return quota_database.meta_table_->SetVersionNumber(9) &&
quota_database.meta_table_->SetCompatibleVersionNumber(9) &&
transaction.Commit();
}
bool QuotaDatabaseMigrations::MigrateFromVersion9ToVersion10(
QuotaDatabase& quota_database) {
DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database.sequence_checker_);
sql::Database* db = quota_database.db_.get();
sql::Transaction transaction(db);
if (!transaction.Begin()) {
return false;
}
// Update the default bucket name to '_default'.
// See https://github.com/WICG/storage-buckets/issues/91
// clang-format off
static constexpr char kUpdateDefaultBucketNameSql[] =
"UPDATE buckets "
"SET name = ? "
"WHERE name = ? ";
// clang-format on
sql::Statement update_statement(
db->GetCachedStatement(SQL_FROM_HERE, kUpdateDefaultBucketNameSql));
update_statement.BindString(0, kDefaultBucketName);
update_statement.BindString(1, kDefaultNamePreV10);
if (!update_statement.Run()) {
return false;
}
// Delete quota table (see crbug.com/1175113).
static constexpr char kDeleteQuotaHostTableSql[] = "DROP TABLE quota";
if (!db->Execute(kDeleteQuotaHostTableSql)) {
return false;
}
// Delete buckets with persistent type (see crbug.com/1175113).
static constexpr char kDeletePersistentTypeBuckets[] =
"DELETE FROM buckets WHERE type = ? ";
sql::Statement delete_statement(
db->GetCachedStatement(SQL_FROM_HERE, kDeletePersistentTypeBuckets));
delete_statement.BindInt(
0, static_cast<int>(blink::mojom::StorageType::kDeprecatedPersistent));
if (!delete_statement.Run()) {
return false;
}
// Mark database as up to date.
return quota_database.meta_table_->SetVersionNumber(10) &&
quota_database.meta_table_->SetCompatibleVersionNumber(10) &&
transaction.Commit();
}
} // namespace storage