// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/disk_cache/sql/sql_shared_cache_isolated_database.h"

#include <algorithm>
#include <limits>
#include <utility>

#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/ref_counted_memory.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/types/expected_macros.h"
#include "components/sqlite_vfs/client.h"
#include "components/sqlite_vfs/sqlite_sandboxed_vfs.h"
#include "components/sqlite_vfs/vfs_utils.h"
#include "net/base/features.h"
#include "net/disk_cache/sql/sql_backend_constants.h"
#include "net/disk_cache/sql/sql_read_cache_memory_monitor.h"
#include "net/disk_cache/sql/sql_shared_cache_isolated_database_queries.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/streaming_blob_handle.h"
#include "sql/transaction.h"

using disk_cache_sql_queries::GetSharedCacheIsolatedDatabaseQuery;
using disk_cache_sql_queries::SharedCacheIsolatedDatabaseQuery;

namespace disk_cache {

namespace {

constexpr int kSharedCacheIsolatedDatabaseCurrentVersion = 1;
constexpr int kSharedCacheIsolatedDatabaseCompatibleVersion = 1;

}  // namespace

// static
std::unique_ptr<SqlSharedCacheIsolatedDatabase::DatabaseAssets>
SqlSharedCacheIsolatedDatabase::DatabaseAssets::MaybeCreate(
    const base::FilePath& directory,
    SqlSharedCacheDbId shared_cache_db_id) {
  ASSIGN_OR_RETURN(
      auto pending_file_set,
      sqlite_vfs::MakePendingFileSet(
          sqlite_vfs::Client::kSharedCacheIsolated, directory,
          base::FilePath::FromASCII(
              base::StrCat({kSqlBackendSharedCacheIsolatedFileNamePrefix,
                            base::NumberToString(*shared_cache_db_id)})),
          /*single_connection=*/false,
          net::features::kRendererAccessibleHttpCacheWalMode.Get()),
      [](auto) { return nullptr; });
  ASSIGN_OR_RETURN(auto vfs_file_set,
                   sqlite_vfs::SqliteVfsFileSet::Bind(
                       sqlite_vfs::Client::kSharedCacheIsolated,
                       std::move(pending_file_set)),
                   [] { return nullptr; });
  return std::make_unique<DatabaseAssets>(directory, shared_cache_db_id,
                                          std::move(vfs_file_set),
                                          base::PassKey<DatabaseAssets>());
}

SqlSharedCacheIsolatedDatabase::DatabaseAssets::DatabaseAssets(
    const base::FilePath& directory,
    SqlSharedCacheDbId shared_cache_db_id,
    sqlite_vfs::SqliteVfsFileSet vfs_file_set,
    base::PassKey<DatabaseAssets>)
    : directory_(directory),
      shared_cache_db_id_(shared_cache_db_id),
      vfs_file_set_(std::move(vfs_file_set)),
      unregister_runner_(sqlite_vfs::SqliteSandboxedVfsDelegate::GetInstance()
                             ->RegisterSandboxedFiles(vfs_file_set_)),
      db_(sql::DatabaseOptions()
              .set_read_only(vfs_file_set_.read_only())
              .set_exclusive_locking(vfs_file_set_.is_single_connection())
              .set_wal_mode(vfs_file_set_.wal_mode())
              .set_vfs_name_discouraged(
                  sqlite_vfs::SqliteSandboxedVfsDelegate::kSqliteVfsName)
              .set_mmap_enabled(false),
          sql::Database::Tag("SharedCacheIsolated")) {}

SqlSharedCacheIsolatedDatabase::DatabaseAssets::~DatabaseAssets() = default;

base::FilePath
SqlSharedCacheIsolatedDatabase::DatabaseAssets::GetDbVirtualFilePath() const {
  return vfs_file_set_.GetDbVirtualFilePath();
}

base::expected<sqlite_vfs::PendingFileSet, sqlite_vfs::FileSetError>
SqlSharedCacheIsolatedDatabase::DatabaseAssets::ShareConnection() {
  return sqlite_vfs::ShareConnection(
      directory_,
      base::FilePath::FromASCII(
          base::StrCat({kSqlBackendSharedCacheIsolatedFileNamePrefix,
                        base::NumberToString(*shared_cache_db_id_)})),
      vfs_file_set_, /*read_write=*/false);
}

void SqlSharedCacheIsolatedDatabase::DatabaseAssets::AbandonAndDeleteFiles() {
  db_.Close();
  vfs_file_set_.Abandon();
  base::FilePath base_name = base::FilePath::FromASCII(
      base::StrCat({kSqlBackendSharedCacheIsolatedFileNamePrefix,
                    base::NumberToString(shared_cache_db_id_.value())}));
  sqlite_vfs::DeleteFiles(sqlite_vfs::Client::kSharedCacheIsolated, directory_,
                          base_name);
}

SqlSharedCacheIsolatedDatabase::SqlSharedCacheIsolatedDatabase(
    std::string nik_string,
    const base::FilePath& directory,
    SqlSharedCacheDbId shared_cache_db_id,
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    scoped_refptr<SqlReadCacheMemoryMonitor> read_cache_memory_monitor)
    : nik_string_(std::move(nik_string)),
      db_assets_(DatabaseAssets::MaybeCreate(directory, shared_cache_db_id)),
      task_runner_(std::move(task_runner)),
      read_cache_memory_monitor_(std::move(read_cache_memory_monitor)) {
  CHECK(task_runner_);
}

SqlSharedCacheIsolatedDatabase::~SqlSharedCacheIsolatedDatabase() = default;

base::expected<std::vector<uint32_t>, SqlSharedCacheIsolatedDatabase::Error>
SqlSharedCacheIsolatedDatabase::GetAllUrlHashes() {
  if (ShouldSimulateFailure(OperationForTesting::kRead)) {
    return base::unexpected(Error::kFailedForTesting);
  }
  if (!db_assets_) {
    return base::unexpected(Error::kFailedToOpenVfsFileSet);
  }
  if (!db_assets_->db().is_open()) {
    return base::unexpected(Error::kDatabaseNotOpen);
  }
  sql::Statement statement(db_assets_->db().GetCachedStatement(
      SQL_FROM_HERE, GetSharedCacheIsolatedDatabaseQuery(
                         SharedCacheIsolatedDatabaseQuery::kSelectHashes)));
  std::vector<uint32_t> hashes;
  while (statement.Step()) {
    hashes.push_back(statement.ColumnInt(0));
  }
  if (!statement.Succeeded()) {
    return base::unexpected(Error::kFailedToExecuteStatement);
  }
  return hashes;
}

base::expected<sqlite_vfs::PendingFileSet,
               SqlSharedCacheIsolatedDatabase::Error>
SqlSharedCacheIsolatedDatabase::GetSharedReadOnlyConnection() {
  if (!db_assets_) {
    return base::unexpected(Error::kFailedToOpenVfsFileSet);
  }
  auto pending_file_set = db_assets_->ShareConnection();
  if (!pending_file_set.has_value()) {
    return base::unexpected(Error::kFailedToShareConnection);
  }
  return std::move(*pending_file_set);
}

void SqlSharedCacheIsolatedDatabase::
    SetSimulateDbFailureCallbackForTesting(  // IN-TEST
        SimFailedCallback callback) {
  simulate_db_failure_callback_ = std::move(callback);
}

bool SqlSharedCacheIsolatedDatabase::ShouldSimulateFailure(
    OperationForTesting op) const {
  return simulate_db_failure_callback_ && simulate_db_failure_callback_.Run(op);
}

base::expected<void, SqlSharedCacheIsolatedDatabase::Error>
SqlSharedCacheIsolatedDatabase::Init() {
  if (ShouldSimulateFailure(OperationForTesting::kInit)) {
    return base::unexpected(Error::kFailedForTesting);
  }
  if (!db_assets_) {
    return base::unexpected(Error::kFailedToOpenVfsFileSet);
  }
  sql::Database& db = db_assets_->db();
  if (db.is_open()) {
    return base::ok();
  }
  if (!db.Open(db_assets_->GetDbVirtualFilePath())) {
    return base::unexpected(Error::kFailedToOpenDatabase);
  }

  if (sql::MetaTable::RazeIfIncompatible(
          &db, kSharedCacheIsolatedDatabaseCompatibleVersion,
          kSharedCacheIsolatedDatabaseCurrentVersion) ==
      sql::RazeIfIncompatibleResult::kFailed) {
    return base::unexpected(Error::kFailedToRazeIncompatibleVersionDatabase);
  }

  sql::MetaTable meta_table;
  if (sql::MetaTable::DoesTableExist(&db)) {
    std::string saved_nik;
    if (!meta_table.Init(&db, kSharedCacheIsolatedDatabaseCurrentVersion,
                         kSharedCacheIsolatedDatabaseCompatibleVersion) ||
        !meta_table.GetValue(kSqlBackendSharedCacheIsolatedMetaTableKeyNik,
                             &saved_nik) ||
        saved_nik != nik_string_) {
      if (!db.Raze()) {
        return base::unexpected(Error::kFailedToRazeIncompatibleNikDatabase);
      }
      meta_table.Reset();
    } else {
      return base::ok();
    }
  }

  sql::Transaction transaction(&db);
  if (!transaction.Begin()) {
    return base::unexpected(Error::kFailedToStartTransaction);
  }

  if (!db.Execute(GetSharedCacheIsolatedDatabaseQuery(
          SharedCacheIsolatedDatabaseQuery::kCreateResourcesTable))) {
    return base::unexpected(Error::kFailedToCreateResourcesTable);
  }

  if (!db.Execute(GetSharedCacheIsolatedDatabaseQuery(
          SharedCacheIsolatedDatabaseQuery::kCreateResourcesTableIndex))) {
    return base::unexpected(Error::kFailedToCreateResourcesTableIndex);
  }

  if (!meta_table.Init(&db, kSharedCacheIsolatedDatabaseCurrentVersion,
                       kSharedCacheIsolatedDatabaseCompatibleVersion)) {
    return base::unexpected(Error::kFailedToInitMetaTable);
  }

  if (!meta_table.SetValue(kSqlBackendSharedCacheIsolatedMetaTableKeyNik,
                           nik_string_)) {
    return base::unexpected(Error::kFailedToSetNikInMetaTable);
  }

  if (!transaction.Commit()) {
    return base::unexpected(Error::kFailedToCommitTransaction);
  }

  return base::ok();
}

base::expected<SqlSharedCacheRowId, SqlSharedCacheIsolatedDatabase::Error>
SqlSharedCacheIsolatedDatabase::Insert(const CacheEntryKey& entry_key,
                                       scoped_refptr<net::IOBuffer> headers,
                                       uint32_t total_body_size,
                                       scoped_refptr<net::IOBuffer> body) {
  if (ShouldSimulateFailure(OperationForTesting::kInsert)) {
    return base::unexpected(Error::kFailedForTesting);
  }
  if (!db_assets_ || !db_assets_->db().is_open()) {
    return base::unexpected(Error::kDatabaseNotOpen);
  }
  if (total_body_size >
      static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) {
    return base::unexpected(Error::kBodyTooLarge);
  }

  sql::Transaction transaction(&db_assets_->db());
  if (!transaction.Begin()) {
    return base::unexpected(Error::kFailedToStartTransaction);
  }

  SqlSharedCacheRowId shared_cache_row_id;
  const bool is_ready =
      (total_body_size == 0) ||
      (body && total_body_size == static_cast<uint32_t>(body->size()));
  constexpr base::span<uint8_t, 0> kEmptySpan;

  {
    sql::Statement statement(db_assets_->db().GetCachedStatement(
        SQL_FROM_HERE, GetSharedCacheIsolatedDatabaseQuery(
                           SharedCacheIsolatedDatabaseQuery::kInsertResource)));
    statement.BindInt64(0, entry_key.resource_url_hash().value());
    statement.BindString(1, entry_key.resource_url());
    // Wrapping the span with a RefCountedStaticMemory to avoid memory copy.
    // SAFETY: The memory referenced by `headers->span()`/`kEmptySpan` must
    // outlive `statement`.
    statement.BindBlob(2, base::MakeRefCounted<base::RefCountedStaticMemory>(
                              headers ? headers->span() : kEmptySpan));
    if (is_ready) {
      // SAFETY: The memory referenced by `body->span()`/`kEmptySpan` must
      // outlive `statement`.
      statement.BindBlob(
          3, base::MakeRefCounted<base::RefCountedStaticMemory>(
                 body ? body->span().first(total_body_size) : kEmptySpan));
    } else {
      statement.BindBlobForStreaming(3, total_body_size);
    }
    statement.BindBool(4, is_ready);

    if (!statement.Run()) {
      return base::unexpected(Error::kFailedToExecuteStatement);
    }
    shared_cache_row_id =
        SqlSharedCacheRowId(db_assets_->db().GetLastInsertRowId());
  }

  if (body && !is_ready) {
    RETURN_IF_ERROR(WriteBodyInternal(entry_key, shared_cache_row_id, 0, body,
                                      /*set_ready=*/false,
                                      /*in_transaction=*/true));
  }

  if (!transaction.Commit()) {
    return base::unexpected(Error::kFailedToCommitTransaction);
  }

  return shared_cache_row_id;
}

base::expected<void, SqlSharedCacheIsolatedDatabase::Error>
SqlSharedCacheIsolatedDatabase::WriteBody(
    const CacheEntryKey& entry_key,
    SqlSharedCacheRowId shared_cache_row_id,
    int offset,
    scoped_refptr<net::IOBuffer> buffer,
    bool set_ready) {
  return WriteBodyInternal(entry_key, shared_cache_row_id, offset, buffer,
                           set_ready, /*in_transaction=*/false);
}

base::expected<void, SqlSharedCacheIsolatedDatabase::Error>
SqlSharedCacheIsolatedDatabase::WriteBodyInternal(
    const CacheEntryKey& entry_key,
    SqlSharedCacheRowId shared_cache_row_id,
    int offset,
    scoped_refptr<net::IOBuffer> buffer,
    bool set_ready,
    bool in_transaction) {
  if (ShouldSimulateFailure(OperationForTesting::kWriteBody)) {
    return base::unexpected(Error::kFailedForTesting);
  }
  if (!db_assets_ || !db_assets_->db().is_open()) {
    return base::unexpected(Error::kDatabaseNotOpen);
  }
  const int size = buffer ? buffer->size() : 0;
  CHECK_GE(size, 0);
  if (offset < 0 || size > std::numeric_limits<int32_t>::max() - offset) {
    return base::unexpected(Error::kInvalidWriteRange);
  }

  std::optional<sql::Transaction> transaction;
  if (!in_transaction) {
    transaction.emplace(&db_assets_->db());
    if (!transaction->Begin()) {
      return base::unexpected(Error::kFailedToStartTransaction);
    }
  }

  if (size > 0) {
    ASSIGN_OR_RETURN(auto blob_handle,
                     db_assets_->db().GetStreamingBlob(
                         "resources", "body", shared_cache_row_id.value(),
                         /*readonly=*/false),
                     [] { return Error::kFailedToGetBlob; });
    if (!blob_handle.Write(offset, buffer->span())) {
      return base::unexpected(Error::kFailedToWriteBlob);
    }
  }

  if (set_ready) {
    sql::Statement statement(db_assets_->db().GetCachedStatement(
        SQL_FROM_HERE,
        GetSharedCacheIsolatedDatabaseQuery(
            SharedCacheIsolatedDatabaseQuery::kSetResourceReady)));
    statement.BindInt64(0, shared_cache_row_id.value());
    if (!statement.Run()) {
      return base::unexpected(Error::kFailedToSetReady);
    }
  }

  if (transaction && !transaction->Commit()) {
    return base::unexpected(Error::kFailedToCommitTransaction);
  }

  return base::ok();
}

SqlSharedCacheIsolatedDatabase::SqlSharedCacheBlobHandleImpl::
    SqlSharedCacheBlobHandleImpl(
        base::OnceClosure decrement_closure,
        scoped_refptr<base::SequencedTaskRunner> task_runner)
    : decrement_closure_(std::move(decrement_closure)),
      task_runner_(std::move(task_runner)) {}

SqlSharedCacheIsolatedDatabase::SqlSharedCacheBlobHandleImpl::
    ~SqlSharedCacheBlobHandleImpl() {
  task_runner_->PostTask(FROM_HERE, std::move(decrement_closure_));
}

SqlSharedCacheIsolatedDatabase::BlobHandleHolder::BlobHandleHolder(
    sql::StreamingBlobHandle blob_handle,
    const CacheEntryKey& entry_key,
    int body_size)
    : blob_handle_(std::move(blob_handle)),
      entry_key_(entry_key),
      body_size_(body_size) {}

SqlSharedCacheIsolatedDatabase::BlobHandleHolder::~BlobHandleHolder() = default;

base::expected<scoped_refptr<SqlSharedCacheBlobHandle>,
               SqlSharedCacheIsolatedDatabase::Error>
SqlSharedCacheIsolatedDatabase::GetBlobHandle(
    const CacheEntryKey& entry_key,
    SqlSharedCacheRowId shared_cache_row_id,
    int body_size) {
  if (ShouldSimulateFailure(OperationForTesting::kRead)) {
    return base::unexpected(Error::kFailedForTesting);
  }
  if (!db_assets_ || !db_assets_->db().is_open()) {
    return base::unexpected(Error::kDatabaseNotOpen);
  }
  ASSIGN_OR_RETURN(
      auto* blob_handle_holder,
      GetCachedBlobHandleHolder(entry_key, shared_cache_row_id, body_size));
  if (blob_handle_holder) {
    blob_handle_holder->IncrementRefCount();
    return base::MakeRefCounted<SqlSharedCacheBlobHandleImpl>(
        base::BindOnce(
            &SqlSharedCacheIsolatedDatabase::DecrementBlobHandleRefCount,
            weak_factory_.GetWeakPtr(), shared_cache_row_id),
        task_runner_);
  }

  ASSIGN_OR_RETURN(
      auto streaming_blob_handle,
      GetStreamingBlobHandle(entry_key, shared_cache_row_id, body_size));
  blob_handle_holders_.try_emplace(
      shared_cache_row_id,
      std::make_unique<BlobHandleHolder>(std::move(streaming_blob_handle),
                                         entry_key, body_size));

  return base::MakeRefCounted<SqlSharedCacheBlobHandleImpl>(
      base::BindOnce(
          &SqlSharedCacheIsolatedDatabase::DecrementBlobHandleRefCount,
          weak_factory_.GetWeakPtr(), shared_cache_row_id),
      task_runner_);
}

base::expected<SqlSharedCacheIsolatedDatabase::BlobHandleHolder*,
               SqlSharedCacheIsolatedDatabase::Error>
SqlSharedCacheIsolatedDatabase::GetCachedBlobHandleHolder(
    const CacheEntryKey& entry_key,
    SqlSharedCacheRowId shared_cache_row_id,
    int body_size) {
  auto it = blob_handle_holders_.find(shared_cache_row_id);
  if (it == blob_handle_holders_.end()) {
    return nullptr;
  }
  if (it->second->resource_url() != entry_key.resource_url()) {
    return base::unexpected(Error::kEntryNotFound);
  }
  if (it->second->body_size() != body_size) {
    return base::unexpected(Error::kBodySizeMismatch);
  }
  return it->second.get();
}

base::expected<sql::StreamingBlobHandle, SqlSharedCacheIsolatedDatabase::Error>
SqlSharedCacheIsolatedDatabase::GetStreamingBlobHandle(
    const CacheEntryKey& entry_key,
    SqlSharedCacheRowId shared_cache_row_id,
    int body_size) {
  if (!db_assets_ || !db_assets_->db().is_open()) {
    return base::unexpected(Error::kDatabaseNotOpen);
  }
  {
    sql::Statement statement(db_assets_->db().GetCachedStatement(
        SQL_FROM_HERE,
        GetSharedCacheIsolatedDatabaseQuery(
            SharedCacheIsolatedDatabaseQuery::kSelectUrlAndReadyByRowId)));
    statement.BindInt64(0, shared_cache_row_id.value());
    if (!statement.Step() || !statement.ColumnBool(1) ||
        statement.ColumnString(0) != entry_key.resource_url()) {
      return base::unexpected(Error::kEntryNotFound);
    }
  }

  ASSIGN_OR_RETURN(auto blob_handle,
                   db_assets_->db().GetStreamingBlob(
                       "resources", "body", shared_cache_row_id.value(),
                       /*readonly=*/true),
                   [] { return Error::kFailedToGetBlob; });
  if (blob_handle.GetSize() != body_size) {
    return base::unexpected(Error::kBodySizeMismatch);
  }
  return blob_handle;
}

void SqlSharedCacheIsolatedDatabase::DecrementBlobHandleRefCount(
    SqlSharedCacheRowId shared_cache_row_id) {
  auto it = blob_handle_holders_.find(shared_cache_row_id);
  if (it == blob_handle_holders_.end()) {
    return;
  }
  CHECK(it->second);
  it->second->DecrementRefCount();
  if (it->second->ref_count() == 0) {
    blob_handle_holders_.erase(it);
  }
}

SqlSharedCacheIsolatedDatabase::ReadResultOrError
SqlSharedCacheIsolatedDatabase::Read(const CacheEntryKey& entry_key,
                                     SqlSharedCacheRowId shared_cache_row_id,
                                     int body_size,
                                     int offset,
                                     scoped_refptr<net::IOBuffer> buffer) {
  if (ShouldSimulateFailure(OperationForTesting::kRead)) {
    return base::unexpected(Error::kFailedForTesting);
  }
  if (!db_assets_ || !db_assets_->db().is_open()) {
    return base::unexpected(Error::kDatabaseNotOpen);
  }
  CHECK(buffer);
  const int buf_len = buffer->size();
  CHECK_GE(buf_len, 0);
  CHECK_GE(body_size, 0);
  if (offset < 0 || buf_len > std::numeric_limits<int32_t>::max() - offset ||
      offset + buf_len > body_size) {
    return base::unexpected(Error::kInvalidReadRange);
  }

  std::optional<sql::StreamingBlobHandle> new_blob_handle;
  sql::StreamingBlobHandle* blob_handle_ptr = nullptr;

  ASSIGN_OR_RETURN(
      auto* blob_handle_holder,
      GetCachedBlobHandleHolder(entry_key, shared_cache_row_id, body_size));
  if (blob_handle_holder) {
    blob_handle_ptr = &blob_handle_holder->blob_handle();
  }

  if (!blob_handle_ptr) {
    ASSIGN_OR_RETURN(
        new_blob_handle,
        GetStreamingBlobHandle(entry_key, shared_cache_row_id, body_size));
    if (new_blob_handle.has_value()) {
      blob_handle_ptr = &new_blob_handle.value();
    }
  }
  if (!blob_handle_ptr) {
    return base::unexpected(Error::kFailedToGetBlob);
  }

  if (!blob_handle_ptr->Read(offset, buffer->span())) {
    return base::unexpected(Error::kFailedToReadBlob);
  }

  ReadResult read_result;
  read_result.read_bytes = buf_len;

  int64_t read_end = static_cast<int64_t>(offset) + buf_len;
  if (read_end < static_cast<int64_t>(body_size) &&
      read_cache_memory_monitor_) {
    const int64_t remaining_bytes = static_cast<int64_t>(body_size) - read_end;
    const int cache_size = base::saturated_cast<int>(remaining_bytes);
    if (read_cache_memory_monitor_->Allocate(cache_size)) {
      auto cache_buffer = base::MakeRefCounted<MonitoredVectorIOBuffer>(
          cache_size, read_cache_memory_monitor_);
      if (blob_handle_ptr->Read(read_end, cache_buffer->span())) {
        read_result.cache_buffer = std::move(cache_buffer);
        read_result.cache_buffer_offset = read_end;
      }
    }
  }

  return read_result;
}

void SqlSharedCacheIsolatedDatabase::DeleteEntry(
    SqlSharedCacheRowId shared_cache_row_id) {
  if (ShouldSimulateFailure(OperationForTesting::kDeleteEntry)) {
    return;
  }
  if (!db_assets_ || !db_assets_->db().is_open()) {
    return;
  }
  sql::Statement statement(db_assets_->db().GetCachedStatement(
      SQL_FROM_HERE,
      GetSharedCacheIsolatedDatabaseQuery(
          SharedCacheIsolatedDatabaseQuery::kDeleteResourceByRowId)));
  statement.BindInt64(0, shared_cache_row_id.value());
  statement.Run();
}

base::expected<void, SqlSharedCacheIsolatedDatabase::Error>
SqlSharedCacheIsolatedDatabase::DeleteEntries(
    const std::vector<SqlSharedCacheRowId>& shared_cache_row_ids) {
  CHECK(!shared_cache_row_ids.empty());
  if (ShouldSimulateFailure(OperationForTesting::kDeleteEntries)) {
    return base::unexpected(Error::kFailedForTesting);
  }
  if (!db_assets_ || !db_assets_->db().is_open()) {
    return base::unexpected(Error::kDatabaseNotOpen);
  }
  sql::Database& db = db_assets_->db();
  sql::Transaction transaction(&db);
  if (!transaction.Begin()) {
    return base::unexpected(Error::kFailedToStartTransaction);
  }

  for (auto row_id : shared_cache_row_ids) {
    sql::Statement statement(db.GetCachedStatement(
        SQL_FROM_HERE,
        GetSharedCacheIsolatedDatabaseQuery(
            SharedCacheIsolatedDatabaseQuery::kDeleteResourceByRowId)));
    statement.BindInt64(0, row_id.value());
    statement.Run();
  }

  if (!transaction.Commit()) {
    return base::unexpected(Error::kFailedToCommitTransaction);
  }

  // TODO(crbug.com/473666511): Autovacuum is disabled on Desktop, so we need
  // to run VACUUM manually at some point. However, running it here might
  // cause performance issues. Figure out a better timing/strategy to run it.

  return base::ok();
}

void SqlSharedCacheIsolatedDatabase::Cleanup() {
  if (db_assets_ && db_assets_->db().is_open()) {
    bool has_entries = false;
    {
      sql::Statement count_statement(db_assets_->db().GetCachedStatement(
          SQL_FROM_HERE,
          GetSharedCacheIsolatedDatabaseQuery(
              SharedCacheIsolatedDatabaseQuery::kSelectRowidLimit1)));
      has_entries = count_statement.Step();
    }
    if (!has_entries) {
      db_assets_->AbandonAndDeleteFiles();
    }
  }
  db_assets_.reset();
}

bool SqlSharedCacheIsolatedDatabase::HasRowForTesting(  // IN-TEST
    SqlSharedCacheRowId shared_cache_row_id) {
  if (!db_assets_ || !db_assets_->db().is_open()) {
    return false;
  }
  sql::Statement statement(db_assets_->db().GetCachedStatement(
      SQL_FROM_HERE,
      GetSharedCacheIsolatedDatabaseQuery(
          SharedCacheIsolatedDatabaseQuery::kSelectUrlAndReadyByRowId)));
  statement.BindInt64(0, shared_cache_row_id.value());
  return statement.Step();
}

}  // namespace disk_cache
