910e62b5创建于 1月15日历史提交
// 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 "content/browser/btm/btm_database_migrator.h"

#include "base/check_deref.h"
#include "base/strings/stringprintf.h"
#include "content/browser/btm/btm_database.h"
#include "sql/meta_table.h"
#include "sql/statement.h"

namespace content {

using internal::BtmDatabaseMigrator;

BtmDatabaseMigrator::BtmDatabaseMigrator(sql::Database* const db,
                                         sql::MetaTable* const meta_table)
    : db_(CHECK_DEREF(db)), meta_table_(CHECK_DEREF(meta_table)) {}

bool MigrateBtmSchemaToLatestVersion(sql::Database& db,
                                     sql::MetaTable& meta_table) {
  BtmDatabaseMigrator migrator(&db, &meta_table);

  for (int next_version = meta_table.GetVersionNumber() + 1;
       next_version <= BtmDatabase::kLatestSchemaVersion; next_version++) {
    switch (next_version) {
      case 2:
        if (!migrator.MigrateSchemaVersionFrom1To2()) {
          return false;
        }
        break;
      case 3:
        if (!migrator.MigrateSchemaVersionFrom2To3()) {
          return false;
        }
        break;
      case 4:
        if (!migrator.MigrateSchemaVersionFrom3To4()) {
          return false;
        }
        break;
      case 5:
        if (!migrator.MigrateSchemaVersionFrom4To5()) {
          return false;
        }
        break;
      case 6:
        if (!migrator.MigrateSchemaVersionFrom5To6()) {
          return false;
        }
        break;
      case 7:
        if (!migrator.MigrateSchemaVersionFrom6To7()) {
          return false;
        }
        break;
      case 8:
        if (!migrator.MigrateSchemaVersionFrom7To8()) {
          return false;
        }
        break;
      case 9:
        if (!migrator.MigrateSchemaVersionFrom8To9()) {
          return false;
        }
        break;
      case 10:
        if (!migrator.MigrateSchemaVersionFrom9To10()) {
          return false;
        }
        break;
      case 11:
        if (!migrator.MigrateSchemaVersionFrom10To11()) {
          return false;
        }
        break;
    }
  }
  return true;
}

bool BtmDatabaseMigrator::MigrateSchemaVersionFrom1To2() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_->HasActiveTransactions());
  // First make a new table that allows for null values in the timestamps
  // columns.
  static constexpr char kNewTableSql[] =  // clang-format off
      "CREATE TABLE new_bounces("
          "site TEXT PRIMARY KEY NOT NULL,"
          "first_site_storage_time INTEGER,"
          "last_site_storage_time INTEGER,"
          "first_user_interaction_time INTEGER,"
          "last_user_interaction_time INTEGER,"
          "first_stateful_bounce_time INTEGER,"
          "last_stateful_bounce_time INTEGER,"
          "first_stateless_bounce_time INTEGER,"
          "last_stateless_bounce_time INTEGER)";
  // clang-format on
  DCHECK(db_->IsSQLValid(kNewTableSql));
  if (!db_->Execute(kNewTableSql)) {
    return false;
  }

  static constexpr char kCopyEverythingSql[] =
      "INSERT INTO new_bounces "
      "SELECT * FROM bounces";
  DCHECK(db_->IsSQLValid(kCopyEverythingSql));
  if (!db_->Execute(kCopyEverythingSql)) {
    return false;
  }

  const std::array<std::string, 8> timestamp_columns{
      "first_site_storage_time",     "last_site_storage_time",
      "first_user_interaction_time", "last_user_interaction_time",
      "first_stateless_bounce_time", "last_stateless_bounce_time",
      "first_stateful_bounce_time",  "last_stateful_bounce_time"};

  for (const std::string& column : timestamp_columns) {
    std::string command = base::StringPrintf(
        "UPDATE new_bounces "
        "SET %s=NULL "
        "WHERE %s=0 ",
        column.c_str(), column.c_str());
    sql::Statement s_nullify(db_->GetUniqueStatement(command));

    if (!s_nullify.Run()) {
      return false;
    }
  }

  // Replace the first_stateless_bounce with the first bounce overall.
  // We have to first case on whether either of the bounce fields are NULL,
  // since MIN will return NULL if either are NULL.
  static constexpr char kReplaceFirstStatelessBounceSql[] =  // clang-format off
    "UPDATE new_bounces "
      "SET first_stateless_bounce_time = "
        "CASE "
          "WHEN first_stateful_bounce_time IS NULL "
            "THEN first_stateless_bounce_time "
          "WHEN first_stateless_bounce_time IS NULL "
            "THEN first_stateful_bounce_time "
          "ELSE MIN(first_stateful_bounce_time,first_stateless_bounce_time) "
        "END";
  // clang-format on
  DCHECK(db_->IsSQLValid(kReplaceFirstStatelessBounceSql));
  if (!db_->Execute(kReplaceFirstStatelessBounceSql)) {
    return false;
  }

  // Replace the last_stateless_bounce with the last bounce overall.
  // We have to first case on whether either of the bounce fields are NULL,
  // since MAX will return NULL if either are NULL.
  static constexpr char kReplaceLastStatelessBounceSql[] =  // clang-format off
      "UPDATE new_bounces "
        "SET last_stateless_bounce_time = "
          "CASE "
            "WHEN last_stateful_bounce_time IS NULL "
              "THEN last_stateless_bounce_time "
            "WHEN last_stateless_bounce_time IS NULL "
              "THEN last_stateful_bounce_time "
            "ELSE MAX(last_stateful_bounce_time,last_stateless_bounce_time) "
          "END";
  // clang-format on
  DCHECK(db_->IsSQLValid(kReplaceLastStatelessBounceSql));
  if (!db_->Execute(kReplaceLastStatelessBounceSql)) {
    return false;
  }
  // Rename this column to be reflect its new purpose.
  static constexpr char kRenameFirstStatelessBounceTimeSql[] =
      "ALTER TABLE new_bounces RENAME COLUMN first_stateless_bounce_time TO "
      "first_bounce_time";
  DCHECK(db_->IsSQLValid(kRenameFirstStatelessBounceTimeSql));
  if (!db_->Execute(kRenameFirstStatelessBounceTimeSql)) {
    return false;
  }

  // Rename this column to be reflect its new purpose.
  static constexpr char kRenameLastStatelessBounceTimeSql[] =
      "ALTER TABLE new_bounces RENAME COLUMN last_stateless_bounce_time TO "
      "last_bounce_time";
  if (!db_->Execute(kRenameLastStatelessBounceTimeSql)) {
    return false;
  }

  // Replace the old `bounces` table with the new one.
  static constexpr char kDropOldTableSql[] = "DROP TABLE bounces";
  if (!db_->Execute(kDropOldTableSql)) {
    return false;
  }

  static constexpr char kReplaceOldTable[] =
      "ALTER TABLE new_bounces RENAME TO bounces";
  if (!db_->Execute(kReplaceOldTable)) {
    return false;
  }

  return meta_table_->SetVersionNumber(2) &&
         meta_table_->SetCompatibleVersionNumber(
             std::min(2, BtmDatabase::kMinCompatibleSchemaVersion));
}

bool BtmDatabaseMigrator::MigrateSchemaVersionFrom2To3() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_->HasActiveTransactions());

  return db_->Execute(
             "ALTER TABLE bounces ADD COLUMN first_web_authn_assertion_time "
             "INTEGER DEFAULT NULL") &&
         db_->Execute(
             "ALTER TABLE bounces ADD COLUMN last_web_authn_assertion_time "
             "INTEGER DEFAULT NULL") &&
         meta_table_->SetVersionNumber(3) &&
         meta_table_->SetCompatibleVersionNumber(
             std::min(3, BtmDatabase::kMinCompatibleSchemaVersion));
}

bool BtmDatabaseMigrator::MigrateSchemaVersionFrom3To4() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_->HasActiveTransactions());

  static constexpr char kCreatePopupsTableSql[] =  // clang-format off
    "CREATE TABLE popups("
      "opener_site TEXT NOT NULL,"
      "popup_site TEXT NOT NULL,"
      "access_id INT64,"
      "last_popup_time INTEGER,"
      "PRIMARY KEY (`opener_site`,`popup_site`)"
    ")";
  // clang-format on
  DCHECK(db_->IsSQLValid(kCreatePopupsTableSql));

  return db_->Execute(kCreatePopupsTableSql) &&
         meta_table_->SetVersionNumber(4) &&
         meta_table_->SetCompatibleVersionNumber(
             std::min(4, BtmDatabase::kMinCompatibleSchemaVersion));
}

bool BtmDatabaseMigrator::MigrateSchemaVersionFrom4To5() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_->HasActiveTransactions());

  return db_->Execute(
             "ALTER TABLE popups ADD COLUMN is_current_interaction "
             "BOOLEAN DEFAULT NULL") &&
         meta_table_->SetVersionNumber(5) &&
         meta_table_->SetCompatibleVersionNumber(
             std::min(5, BtmDatabase::kMinCompatibleSchemaVersion));
}

bool BtmDatabaseMigrator::MigrateSchemaVersionFrom5To6() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_->HasActiveTransactions());

  static constexpr char kCreateConfigTableSql[] =  // clang-format off
    "CREATE TABLE config("
      "key TEXT NOT NULL,"
      "int_value INTEGER,"
      "PRIMARY KEY (`key`)"
    ")";
  // clang-format on
  DCHECK(db_->IsSQLValid(kCreateConfigTableSql));

  if (!db_->Execute(kCreateConfigTableSql)) {
    return false;
  }

  if (int result;
      meta_table_->GetValue(BtmDatabase::kPrepopulatedKey, &result)) {
    static constexpr char kInsertValueSql[] =
        "INSERT OR REPLACE INTO config(key,int_value) VALUES(?,?)";
    DCHECK(db_->IsSQLValid(kInsertValueSql));
    sql::Statement statement(db_->GetUniqueStatement(kInsertValueSql));
    statement.BindString(0, BtmDatabase::kPrepopulatedKey);
    statement.BindInt64(1, result);

    if (!statement.Run()) {
      return false;
    }

    if (!meta_table_->DeleteKey(BtmDatabase::kPrepopulatedKey)) {
      return false;
    }
  }

  return meta_table_->SetVersionNumber(6) &&
         meta_table_->SetCompatibleVersionNumber(
             std::min(6, BtmDatabase::kMinCompatibleSchemaVersion));
}

bool BtmDatabaseMigrator::MigrateSchemaVersionFrom6To7() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(db_->HasActiveTransactions());

  static constexpr char kDeleteConfigSql[] = "DELETE FROM config WHERE key = ?";
  CHECK(db_->IsSQLValid(kDeleteConfigSql));
  sql::Statement statement(db_->GetUniqueStatement(kDeleteConfigSql));
  statement.BindString(0, BtmDatabase::kPrepopulatedKey);

  if (!statement.Run()) {
    return false;
  }

  return meta_table_->SetVersionNumber(7) &&
         meta_table_->SetCompatibleVersionNumber(
             std::min(6, BtmDatabase::kMinCompatibleSchemaVersion));
}

bool BtmDatabaseMigrator::MigrateSchemaVersionFrom7To8() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_->HasActiveTransactions());

  return db_->Execute(
             "ALTER TABLE popups ADD COLUMN is_authentication_interaction "
             "BOOLEAN DEFAULT NULL") &&
         meta_table_->SetVersionNumber(8) &&
         meta_table_->SetCompatibleVersionNumber(
             std::min(8, BtmDatabase::kMinCompatibleSchemaVersion));
}

bool BtmDatabaseMigrator::MigrateSchemaVersionFrom8To9() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_->HasActiveTransactions());

  static constexpr char kRenameFirstUserInteractionTimeSql[] =
      "ALTER TABLE bounces RENAME COLUMN first_user_interaction_time TO "
      "first_user_activation_time";
  DCHECK(db_->IsSQLValid(kRenameFirstUserInteractionTimeSql));
  if (!db_->Execute(kRenameFirstUserInteractionTimeSql)) {
    return false;
  }

  static constexpr char kRenameLastUserInteractionTimeSql[] =
      "ALTER TABLE bounces RENAME COLUMN last_user_interaction_time TO "
      "last_user_activation_time";
  DCHECK(db_->IsSQLValid(kRenameLastUserInteractionTimeSql));
  if (!db_->Execute(kRenameLastUserInteractionTimeSql)) {
    return false;
  }

  return meta_table_->SetVersionNumber(9) &&
         meta_table_->SetCompatibleVersionNumber(
             std::min(9, BtmDatabase::kMinCompatibleSchemaVersion));
}

bool BtmDatabaseMigrator::MigrateSchemaVersionFrom9To10() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_->HasActiveTransactions());

  static constexpr char kDropFirstSiteStorageTimeColumnSql[] =
      "ALTER TABLE bounces DROP COLUMN first_site_storage_time";
  DCHECK(db_->IsSQLValid(kDropFirstSiteStorageTimeColumnSql));
  if (!db_->Execute(kDropFirstSiteStorageTimeColumnSql)) {
    return false;
  }

  static constexpr char kDropLastSiteStorageTimeColumnSql[] =
      "ALTER TABLE bounces DROP COLUMN last_site_storage_time";
  DCHECK(db_->IsSQLValid(kDropLastSiteStorageTimeColumnSql));
  if (!db_->Execute(kDropLastSiteStorageTimeColumnSql)) {
    return false;
  }

  static constexpr char kDropFirstStatefulBounceTimeColumnSql[] =
      "ALTER TABLE bounces DROP COLUMN first_stateful_bounce_time";
  DCHECK(db_->IsSQLValid(kDropFirstStatefulBounceTimeColumnSql));
  if (!db_->Execute(kDropFirstStatefulBounceTimeColumnSql)) {
    return false;
  }

  static constexpr char kDropLastStatefulBounceTimeColumnSql[] =
      "ALTER TABLE bounces DROP COLUMN last_stateful_bounce_time";
  DCHECK(db_->IsSQLValid(kDropLastStatefulBounceTimeColumnSql));
  if (!db_->Execute(kDropLastStatefulBounceTimeColumnSql)) {
    return false;
  }

  return meta_table_->SetVersionNumber(10) &&
         meta_table_->SetCompatibleVersionNumber(
             std::min(10, BtmDatabase::kMinCompatibleSchemaVersion));
}

bool BtmDatabaseMigrator::MigrateSchemaVersionFrom10To11() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(db_->HasActiveTransactions());

  static constexpr char kDeleteEmptyRowsSql[] =  //clang-format off
      "DELETE FROM bounces "
      "WHERE "
      "(first_user_activation_time IS NULL "
      "OR last_user_activation_time IS NULL)"
      "AND (first_bounce_time IS NULL OR last_bounce_time IS NULL)"
      "AND (first_web_authn_assertion_time IS NULL "
      "OR last_web_authn_assertion_time IS NULL)";
  //clang-format on
  DCHECK(db_->IsSQLValid(kDeleteEmptyRowsSql));
  if (!db_->Execute(kDeleteEmptyRowsSql)) {
    return false;
  }

  return meta_table_->SetVersionNumber(11) &&
         meta_table_->SetCompatibleVersionNumber(
             std::min(11, BtmDatabase::kMinCompatibleSchemaVersion));
}

}  // namespace content