// 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 {

// Quota type for deprecated temporary quota.
constexpr int kDeprecatedTemporaryQuotaType = 0;

}  // namespace

// static
bool QuotaDatabaseMigrations::UpgradeSchema(QuotaDatabase& quota_database) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(quota_database.sequence_checker_);
  CHECK(!quota_database.db_->HasActiveTransactions(),
        base::NotFatalUntil::M148);

  // Reset tables for versions lower than 10 since they are unsupported.
  if (quota_database.meta_table_->GetVersionNumber() < 10) {
    return false;
  }

  if (quota_database.meta_table_->GetVersionNumber() == 10) {
    bool success = MigrateFromVersion10ToVersion11(quota_database);
    RecordMigrationHistogram(/*old_version=*/10, /*new_version=*/11, success);
    if (!success) {
      return false;
    }
  }

  return quota_database.meta_table_->GetVersionNumber() == 11;
}

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::MigrateFromVersion10ToVersion11(
    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;
  }

  // Delete buckets that aren't of the temporary type (see crbug.com/40211051).
  static constexpr char kDeleteTemporaryTypeBuckets[] =
      "DELETE FROM buckets WHERE type != ? ";
  sql::Statement delete_statement(
      db->GetCachedStatement(SQL_FROM_HERE, kDeleteTemporaryTypeBuckets));
  delete_statement.BindInt(0, kDeprecatedTemporaryQuotaType);
  if (!delete_statement.Run()) {
    return false;
  }

  // Drop all indices with type (see crbug.com/40211051).
  static constexpr char kDropStorageKeyIndex[] =
      "DROP INDEX buckets_by_storage_key";
  if (!db->Execute(kDropStorageKeyIndex)) {
    return false;
  }
  static constexpr char kDropHostIndex[] = "DROP INDEX buckets_by_host";
  if (!db->Execute(kDropHostIndex)) {
    return false;
  }
  static constexpr char kDropLastAccessedIndex[] =
      "DROP INDEX buckets_by_last_accessed";
  if (!db->Execute(kDropLastAccessedIndex)) {
    return false;
  }
  static constexpr char kDropLastModifiedIndex[] =
      "DROP INDEX buckets_by_last_modified";
  if (!db->Execute(kDropLastModifiedIndex)) {
    return false;
  }

  // Drop type column from buckets table (see crbug.com/40211051).
  static constexpr char kDropTypeColumn[] =
      "ALTER TABLE buckets DROP COLUMN type";
  sql::Statement drop_statement(
      db->GetCachedStatement(SQL_FROM_HERE, kDropTypeColumn));
  if (!drop_statement.Run()) {
    return false;
  }

  // Create all dropped indices without type (see crbug.com/40211051).
  static constexpr char kCreateStorageKeyIndex[] =
      "CREATE UNIQUE INDEX buckets_by_storage_key ON buckets(storage_key, "
      "name)";
  if (!db->Execute(kCreateStorageKeyIndex)) {
    return false;
  }
  static constexpr char kCreateHostIndex[] =
      "CREATE INDEX buckets_by_host ON buckets(host)";
  if (!db->Execute(kCreateHostIndex)) {
    return false;
  }
  static constexpr char kCreateLastAccessedIndex[] =
      "CREATE INDEX buckets_by_last_accessed ON buckets(last_accessed)";
  if (!db->Execute(kCreateLastAccessedIndex)) {
    return false;
  }
  static constexpr char kCreateLastModifiedIndex[] =
      "CREATE INDEX buckets_by_last_modified ON buckets(last_modified);";
  if (!db->Execute(kCreateLastModifiedIndex)) {
    return false;
  }

  // Mark database as up to date.
  return quota_database.meta_table_->SetVersionNumber(11) &&
         quota_database.meta_table_->SetCompatibleVersionNumber(11) &&
         transaction.Commit();
}

}  // namespace storage
