blob: 4be9dd1b05c1b16098e0c52c91957ed31e8b45a4 [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/files/file_path.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 "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 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 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_; }
base::WeakPtr<DatabaseConnection> GetWeakPtr();
// 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;
// Exposed to `BackingStoreDatabaseImpl`.
std::unique_ptr<BackingStoreTransactionImpl> CreateTransaction(
base::PassKey<BackingStoreDatabaseImpl>,
blink::mojom::IDBTransactionDurability durability,
blink::mojom::IDBTransactionMode mode);
void 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 `RecordIterator`s 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*> CreateLongLivedStatement(
std::string query);
// Called when a statement is no longer needed by a `RecordIterator`.
void ReleaseLongLivedStatement(uint64_t id);
// May return `nullptr` if the statement has been destroyed.
sql::Statement* GetLongLivedStatement(uint64_t id);
// Returns a `Status` for the last operation on `db_`.
Status GetStatusOfLastOperation();
// Also for internal use only; exposed for RecordIterator 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);
private:
DatabaseConnection(base::FilePath path,
std::unique_ptr<sql::Database> db,
std::unique_ptr<sql::MetaTable> meta_table,
blink::IndexedDBDatabaseMetadata metadata,
BackingStoreImpl& backing_store);
bool HasActiveVersionChangeTransaction() const {
return metadata_snapshot_.has_value();
}
// 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);
// These methods add or remove rows to the `blob_references` table. The rows
// correspond to active blobs, 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.
void AddActiveBlobReference(int64_t blob_number);
void RemoveActiveBlobReference(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;
// 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_;
// Long-lived statements (those used for cursor iteration) are owned by `this`
// to ensure that database resources are freed before closing `db_`.
uint64_t next_statement_id_ = 0;
std::map<uint64_t, std::unique_ptr<sql::Statement>> 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;
// TODO(crbug.com/419203257): this should invalidate its weak pointers when
// `db_` is closed.
base::WeakPtrFactory<DatabaseConnection> record_iterator_weak_factory_{this};
// Only used for the callbacks passed to `blob_writers_`.
base::WeakPtrFactory<DatabaseConnection> blob_writers_weak_factory_{this};
base::WeakPtrFactory<DatabaseConnection> weak_factory_{this};
};
} // namespace sqlite
} // namespace content::indexed_db
#endif // CONTENT_BROWSER_INDEXED_DB_INSTANCE_SQLITE_DATABASE_CONNECTION_H_