blob: 642e7cef77451baab8a343490b14b19637ca3cd0 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_INDEXED_DB_INSTANCE_SQLITE_DATABASE_CONNECTION_H_
#define CONTENT_BROWSER_INDEXED_DB_INSTANCE_SQLITE_DATABASE_CONNECTION_H_
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include "base/byte_count.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h"
#include "base/types/expected.h"
#include "base/types/pass_key.h"
#include "content/browser/indexed_db/indexed_db_external_object_storage.h"
#include "content/browser/indexed_db/instance/backing_store.h"
#include "content/browser/indexed_db/instance/sqlite/active_blob_streamer.h"
#include "content/browser/indexed_db/instance/sqlite/backing_store_impl.h"
#include "content/browser/indexed_db/instance/sqlite/blob_writer.h"
#include "content/browser/indexed_db/status.h"
#include "content/common/content_export.h"
#include "sql/streaming_blob_handle.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key_range.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-forward.h"
namespace blink {
class IndexedDBKey;
} // namespace blink
namespace sql {
class Database;
class MetaTable;
class Statement;
class Transaction;
} // namespace sql
namespace content::indexed_db {
struct IndexedDBValue;
namespace sqlite {
class BackingStoreCursorImpl;
class BackingStoreDatabaseImpl;
class BackingStoreTransactionImpl;
// Owns the sole connection to the SQLite database that is backing a given
// IndexedDB database. Also owns the schema, operations and in-memory metadata
// for this database. BackingStore interface methods call into this class to
// perform the actual database operations.
class CONTENT_EXPORT DatabaseConnection {
public:
// Opens a connection to the specified database. When `name` is present, it
// will create a new DB if one does not exist. When `name` is null and a DB
// does not exist or is not already initialized, returns an error. When `path`
// is empty, the database will be opened in-memory.
static StatusOr<std::unique_ptr<DatabaseConnection>> Open(
std::optional<std::u16string_view> name,
base::FilePath path,
BackingStoreImpl& backing_store);
// Destroys the DatabaseConnection pointed to by `db`, if appropriate, i.e. if
// `db` is the last weak pointer.
static void Release(base::WeakPtr<DatabaseConnection> db);
DatabaseConnection(const DatabaseConnection&) = delete;
DatabaseConnection& operator=(const DatabaseConnection&) = delete;
~DatabaseConnection();
const blink::IndexedDBDatabaseMetadata& metadata() const { return metadata_; }
// Gets the version of the database that is actually committed. This can be
// different from the version in `metadata_` during a version change
// transaction.
int64_t GetCommittedVersion() const;
// True when the database is in an early, partially initialized state,
// containing schema but no data. This will be true when the database is first
// created as well as when it's been deleted, but held open due to active blob
// references. Note that in the latter case, the database will contain data
// corresponding to active blobs, but no object stores, records, etc.
bool IsZygotic() const;
// Get the size of the database opened in-memory.
uint64_t GetInMemorySize() const;
std::unique_ptr<BackingStoreDatabaseImpl> CreateDatabaseWrapper();
// Exposed to `BackingStoreDatabaseImpl`.
std::unique_ptr<BackingStoreTransactionImpl> CreateTransactionWrapper(
base::PassKey<BackingStoreDatabaseImpl>,
blink::mojom::IDBTransactionDurability durability,
blink::mojom::IDBTransactionMode mode);
Status BeginTransaction(base::PassKey<BackingStoreTransactionImpl>,
const BackingStoreTransactionImpl& transaction);
// In this phase, blobs, if any, are asynchronously written.
Status CommitTransactionPhaseOne(
base::PassKey<BackingStoreTransactionImpl>,
const BackingStoreTransactionImpl& transaction,
BlobWriteCallback callback,
SerializeFsaCallback serialize_fsa_handle);
Status CommitTransactionPhaseTwo(
base::PassKey<BackingStoreTransactionImpl>,
const BackingStoreTransactionImpl& transaction);
void RollBackTransaction(base::PassKey<BackingStoreTransactionImpl>,
const BackingStoreTransactionImpl& transaction);
// It's possible that a BackingStoreTransactionImpl is created, and Begin() is
// called, but it's never used. In this case, neither Commit nor Rollback will
// be called. This method will be called every time a transaction that was
// begun is being destroyed.
void EndTransaction(base::PassKey<BackingStoreTransactionImpl>,
const BackingStoreTransactionImpl& transaction);
Status SetDatabaseVersion(base::PassKey<BackingStoreTransactionImpl>,
int64_t version);
Status CreateObjectStore(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
std::u16string name,
blink::IndexedDBKeyPath key_path,
bool auto_increment);
Status DeleteObjectStore(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id);
Status RenameObjectStore(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const std::u16string& new_name);
Status CreateIndex(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
blink::IndexedDBIndexMetadata index);
Status DeleteIndex(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id);
Status RenameIndex(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id,
const std::u16string& new_name);
StatusOr<int64_t> GetKeyGeneratorCurrentNumber(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id);
// Updates the key generator current number of `object_store_id` to
// `new_number` if greater than the current number.
Status MaybeUpdateKeyGeneratorCurrentNumber(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t new_number);
StatusOr<std::optional<BackingStore::RecordIdentifier>>
GetRecordIdentifierIfExists(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const blink::IndexedDBKey& key);
// Returns an empty `IndexedDBValue` if the record is not found.
StatusOr<IndexedDBValue> GetValue(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const blink::IndexedDBKey& key);
StatusOr<BackingStore::RecordIdentifier> PutRecord(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const blink::IndexedDBKey& key,
IndexedDBValue value);
Status DeleteRange(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const blink::IndexedDBKeyRange&);
Status ClearObjectStore(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id);
StatusOr<uint32_t> GetObjectStoreKeyCount(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
blink::IndexedDBKeyRange key_range);
Status PutIndexDataForRecord(base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id,
const blink::IndexedDBKey& key,
const BackingStore::RecordIdentifier& record);
StatusOr<blink::IndexedDBKey> GetFirstPrimaryKeyForIndexKey(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id,
const blink::IndexedDBKey& key);
StatusOr<uint32_t> GetIndexKeyCount(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id,
blink::IndexedDBKeyRange key_range);
StatusOr<std::unique_ptr<BackingStore::Cursor>> OpenObjectStoreCursor(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const blink::IndexedDBKeyRange& key_range,
blink::mojom::IDBCursorDirection direction,
bool key_only);
StatusOr<std::unique_ptr<BackingStore::Cursor>> OpenIndexCursor(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id,
const blink::IndexedDBKeyRange& key_range,
blink::mojom::IDBCursorDirection direction,
bool key_only);
// Connects mojo pipes for `objects`. These pipes are backed by
// `ActiveBlobStreamer`.
std::vector<blink::mojom::IDBExternalObjectPtr> CreateAllExternalObjects(
base::PassKey<BackingStoreTransactionImpl>,
const std::vector<IndexedDBExternalObject>& objects,
DeserializeFsaCallback deserialize_fsa_handle);
// Called when the IDB database associated with this connection is deleted.
// This should drop all data with the exception of active blobs, which may
// keep `this` alive.
void DeleteIdbDatabase(base::PassKey<BackingStoreDatabaseImpl>);
// These are exposed for cursors to access `Statement` resources associated
// with `db_`.
//
// Returns a unique ID and a pointer to a `Statement` whose lifetime is
// managed by `this`.
std::tuple<uint64_t, sql::Statement*> CreateCursorStatement(
base::PassKey<BackingStoreCursorImpl>,
std::string query,
int64_t object_store_id);
// Called when a statement is no longer needed by the cursor that created it.
void ReleaseCursorStatement(base::PassKey<BackingStoreCursorImpl>,
uint64_t id);
// May return `nullptr` if the statement has been destroyed.
sql::Statement* GetCursorStatement(base::PassKey<BackingStoreCursorImpl>,
uint64_t id);
// Returns a `Status` for the last operation on `db_`.
// This is exposed for cursor implementations which `Step()` statements
// outside of this class.
Status GetStatusOfLastOperation(base::PassKey<BackingStoreCursorImpl>);
// Also for internal use only; exposed for cursor implementations.
// This adds external objects to `value` which should later be further hooked
// up via `CreateAllExternalObjects()`.
StatusOr<IndexedDBValue> AddExternalObjectMetadataToValue(
IndexedDBValue value,
int64_t record_row_id);
// Changes the size at which blobs are chunked.
static void OverrideMaxBlobSizeForTesting(base::ByteCount size);
private:
FRIEND_TEST_ALL_PREFIXES(DatabaseConnectionTest, TooNew);
DatabaseConnection(base::FilePath path, BackingStoreImpl& backing_store);
// All startup/initialization tasks that can error are performed here. Will
// return Status::OK() on success. `name` must be provided if the database is
// new. If the database is pre-existing, `name` may not be provided, but if it
// is, it must match the database's stored name.
Status Init(std::optional<std::u16string_view> name);
bool HasActiveVersionChangeTransaction() const {
return metadata_snapshot_.has_value();
}
// Gets a handle to a blob in either the `blobs` table (when `chunk_index` is
// 0) or the `overflow_blob_chunks` table, used for writing bytes that
// overflow a single SQLite BLOB.
std::optional<sql::StreamingBlobHandle> OpenBlobChunkForStreaming(
int64_t blob_row_id,
bool readonly,
size_t chunk_index);
// Invoked by an owned `BlobWriter` when it's done writing, or has encountered
// an error.
void OnBlobWriteComplete(int64_t blob_row_id, bool success);
// Invoked when an FSA handle has been serialized. `token` will be empty if
// the serialization was not successful.
void OnFsaHandleSerialized(int64_t blob_row_id,
const std::vector<uint8_t>& token);
// Cancels all outstanding external object processing/writing, including blob
// writes and FSA handle serialization/writing. This is to be called on error.
void CancelBlobWriting();
// Called when a blob that was opened for reading stops being "active", i.e.
// when `ActiveBlobStreamer` in `active_blobs_` no longer has connections.
void OnBlobBecameInactive(int64_t blob_number);
// This method adds a row to the `blob_references` table. The row corresponds
// to an active blob, i.e. the `record_row_id` will be null. These updates are
// made right away when `active_blobs_` is updated (an element is added or
// removed), and also after a transaction is rolled back which may have caused
// the loss of a `blob_references` update.
bool AddActiveBlobReference(int64_t blob_number);
// The connection needs to be held open when there are active blobs or an
// active BackingStore::Database referencing it. This will return false if
// that's the case.
bool CanBeDestroyed() const;
// Attempts to read metadata from the SQLite DB for storing in memory (in
// `metadata_`).
StatusOr<blink::IndexedDBDatabaseMetadata> GenerateIndexedDbMetadata();
// This enum is used to track various events of interest, mostly errors.
//
// LINT.IfChange(SpecificEvent)
enum class SpecificEvent : uint8_t {
// Logged once per database connection, when initializing.
kDatabaseOpenAttempt = 0,
// Logged at most once per database connection, at shutdown time.
kDatabaseHadSqlError = 1,
// These errors correlate to points in the code where a SQLite error may
// occur, but cannot easily be reported to the frontend because they are not
// directly associated with an ongoing request. Most of them correlate with
// blob bookkeeping, and the worst thing that can happen is that reading
// from a blob may throw errors or that blob data may persist on disk until
// the next time the DB is opened.
kSyncActiveBlobsFailed = 2,
kOpenBlobForStreamingFailed = 3,
kAddActiveBlobReferenceFailed = 4,
kRemoveActiveBlobReferenceFailed = 5,
kPragmaPageCountFailed = 6,
kPragmaPageSizeFailed = 7,
// Events associated with various callers of `Fatal()`.
kMissingMetadataTable = 8,
kDatabaseTooNew = 9,
kDatabaseSchemaUnknown = 10,
kDatabaseNameMismatch = 11,
kBlobChunkMissing = 12,
kObjectStoreNotFound = 13,
kBlobTypeUnknown = 14,
kMaxValue = kBlobTypeUnknown,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/storage/enums.xml:IndexedDbSqliteSpecificEvent)
void LogEvent(SpecificEvent event) const;
// Called when a logical inconsistency or other irrecoverable state is
// detected. This could be due to a bug or due to disk corruption. This will
// not/should not be called when SQLite reports an error. If SQLite does not
// report an error, but a logical inconsistency is found in the database, we
// assume that recovering will fail. Therefore this function marks the
// database for deletion.
Status Fatal(Status s, SpecificEvent event);
// Called when the records of an object store have been modified (inserted or
// deleted). This invalidates all cursor statements operating on that store.
void OnRecordsModified(int64_t object_store_id);
// Makes sure the given IDs exist in `metadata_`.
void ValidateInputs(int64_t object_store_id, int64_t index_id);
// The expected path for `db_`, or empty for in-memory DBs.
const base::FilePath path_;
std::unique_ptr<sql::Database> db_;
std::unique_ptr<sql::MetaTable> meta_table_;
blink::IndexedDBDatabaseMetadata metadata_;
raw_ref<BackingStoreImpl> backing_store_;
// A `sql::Transaction` is created only for version change and readwrite
// IndexedDB transactions, only one of which is allowed to run concurrently,
// irrespective of the scope* (this is enforced by `PartitionedLockManager`).
// Readonly IndexedDB transactions that don't overlap with the current
// readwrite transaction run concurrently, executing their statements in the
// context of the active `sql::Transaction` if it exists, else as standalone
// statements with no explicit `sql::Transaction`.
//
// *This is because SQLite allows only one active (readwrite) transaction on a
// database at a time.
std::unique_ptr<sql::Transaction> active_rw_transaction_;
// Cursor statements are owned by `this` to ensure that database resources are
// freed before closing `db_`. See `BackingStoreImpl::GetStatement()` for why
// cursor statements are not ephemeral (unlike other statements).
//
// The object store ID is also stored alongside the `sql::Statement` so that
// the statement can be invalidated when records change.
// TODO(crbug.com/436880910): Consider also storing the `IndexedDBKeyRange` of
// the statement for more precise invalidation.
using CursorStatementHolder =
std::tuple<std::unique_ptr<sql::Statement>, int64_t>;
uint64_t next_statement_id_ = 0;
std::map<uint64_t, CursorStatementHolder> cursor_statements_;
// Only set while a version change transaction is active.
std::optional<blink::IndexedDBDatabaseMetadata> metadata_snapshot_;
// blob_row_id to blob metadata. These are collected over the lifetime of a
// single transaction as records with associated blobs are inserted into the
// database. The contents of the blobs are not written until commit time. The
// objects in this map are also used to vend bytes (via their connected mojo
// remote) if the client reads a value after writing but before committing.
// ("Pending" blobs.)
std::map<int64_t, IndexedDBExternalObject> blobs_to_write_;
// This map will be empty until `CommitTransactionPhaseOne()` is called, at
// which point it will be populated with helper objects that feed the blob
// bytes into the SQLite database. The map will be empty again after all blobs
// are done writing successfully, or at least one has failed.
std::map<int64_t, std::unique_ptr<BlobWriter>> blob_writers_;
// Tracks the number of currently existing operations that will write blobs
// into the database, resulting from a call to WriteNewBlobs(). This will be
// the sum of `blob_writers_.size()` and the number of FSA handle
// serialization operations that have not yet finished. This is eventually the
// same as the number of weak pointers currently vended from
// `blob_writers_weak_factory_`, but will be updated *while* an operation
// bound to such a weak pointer is executed (whereas the weak pointer itself
// will be destroyed only *after* the operation completes).
size_t outstanding_external_object_writes_ = 0U;
// This is non-null whenever `blob_writers_` is non-empty.
BlobWriteCallback blob_write_callback_;
// A blob is active when there's a live reference in some client. Every active
// blob has a corresponding entry in this map. These blobs must keep `this`
// alive since they're backed by the SQLite database.
std::map<int64_t, std::unique_ptr<ActiveBlobStreamer>> active_blobs_;
// Used to track when rolling back a transaction necessitates updating
// `blob_references`. Transaction rollback will affect `blob_references`
// updates that have been made since the transaction started, but we need that
// table to stay in sync with `active_blobs_` regardless of whether the
// transaction is ultimately committed or rolled back.
bool sync_active_blobs_after_transaction_ = false;
// True once `DeleteIdbDatabase` has been called, or if a fatal error occurred
// that we can't recover from.
bool marked_for_permanent_deletion_ = false;
// TODO(crbug.com/419203257): this should invalidate its weak pointers when
// `db_` is closed.
base::WeakPtrFactory<DatabaseConnection> cursor_weak_factory_{this};
// Only used for the callbacks passed to `blob_writers_`.
base::WeakPtrFactory<DatabaseConnection> blob_writers_weak_factory_{this};
// Used to vend pointers to the interfaces within `BackingStore`.
base::WeakPtrFactory<DatabaseConnection> interface_wrapper_weak_factory_{
this};
};
} // namespace sqlite
} // namespace content::indexed_db
#endif // CONTENT_BROWSER_INDEXED_DB_INSTANCE_SQLITE_DATABASE_CONNECTION_H_