#include "content/browser/indexed_db/instance/sqlite/database_connection.h"
#include <memory>
#include <string>
#include <utility>
#include "base/byte_count.h"
#include "base/byte_size.h"
#include "base/check.h"
#include "base/containers/heap_array.h"
#include "base/containers/to_vector.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "build/build_config.h"
#include "content/browser/indexed_db/indexed_db_data_format_version.h"
#include "content/browser/indexed_db/indexed_db_reporting.h"
#include "content/browser/indexed_db/indexed_db_value.h"
#include "content/browser/indexed_db/instance/backing_store.h"
#include "content/browser/indexed_db/instance/record.h"
#include "content/browser/indexed_db/instance/sqlite/backing_store_cursor_impl.h"
#include "content/browser/indexed_db/instance/sqlite/backing_store_database_impl.h"
#include "content/browser/indexed_db/instance/sqlite/backing_store_transaction_impl.h"
#include "content/browser/indexed_db/status.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "sql/database.h"
#include "sql/error_delegate_util.h"
#include "sql/meta_table.h"
#include "sql/recovery.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
#include "third_party/snappy/src/snappy.h"
#include "third_party/zstd/src/lib/zstd.h"
#include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
#define RETURN_IF_STATEMENT_ERRORED(statement) \
if (!statement.Succeeded()) { \
return base::unexpected(Status(*db_)); \
}
#define RUN_STATEMENT_RETURN_ON_ERROR(statement) \
if (!statement.Run()) { \
return base::unexpected(Status(*db_)); \
}
#define RETURN_STATUS_ON_ERROR(expr) \
if (!expr) { \
return Status(*db_); \
}
#define EXECUTE_AND_RETURN_STATUS_ON_ERROR(db, sql) \
if (!db->Execute(sql)) { \
return Status(*db); \
}
namespace content::indexed_db::sqlite {
namespace {
enum class CompressionType : uint8_t {
kUncompressed = 0,
kZstd = 1,
kSnappy = 2,
};
std::optional<base::ByteCount> g_max_blob_size_override;
base::ByteCount GetMaxBlobSize() {
return g_max_blob_size_override.value_or(base::MiB(5));
}
constexpr char16_t kKeyPathSeparator[] = u" ";
void BindKeyPath(sql::Statement& statement,
int param_index,
const blink::IndexedDBKeyPath& key_path) {
switch (key_path.type()) {
case blink::mojom::IDBKeyPathType::Null:
statement.BindNull(param_index);
break;
case blink::mojom::IDBKeyPathType::String:
statement.BindBlob(param_index, key_path.string());
break;
case blink::mojom::IDBKeyPathType::Array:
statement.BindBlob(param_index,
base::JoinString(key_path.array(), kKeyPathSeparator));
break;
default:
NOTREACHED();
}
}
StatusOr<blink::IndexedDBKeyPath> ColumnKeyPath(sql::Statement& statement,
int column_index) {
if (statement.GetColumnType(column_index) == sql::ColumnType::kNull) {
return blink::IndexedDBKeyPath();
}
ASSIGN_OR_RETURN(
std::u16string encoded, statement.ColumnBlobAsString16(column_index),
[]() { return Status::Corruption("Key path unexpected size"); });
std::vector<std::u16string> parts = base::SplitString(
encoded, kKeyPathSeparator, base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (parts.empty()) {
return blink::IndexedDBKeyPath(std::u16string());
}
if (parts.size() == 1) {
return blink::IndexedDBKeyPath(std::move(parts.front()));
}
return blink::IndexedDBKeyPath(std::move(parts));
}
StatusOr<mojo_base::BigBuffer> DoDecompress(
base::span<const uint8_t> compressed,
int compression_type) {
if (compression_type == static_cast<int>(CompressionType::kUncompressed)) {
return mojo_base::BigBuffer(compressed);
}
if (compression_type == static_cast<int>(CompressionType::kZstd)) {
uint64_t decompressed_size =
ZSTD_getFrameContentSize(compressed.data(), compressed.size());
if (decompressed_size == ZSTD_CONTENTSIZE_UNKNOWN ||
decompressed_size == ZSTD_CONTENTSIZE_ERROR) {
return base::unexpected(Status::Corruption("ZSTD decompression failed"));
}
mojo_base::BigBuffer decompressed(decompressed_size);
if (ZSTD_isError(ZSTD_decompress(decompressed.data(), decompressed.size(),
compressed.data(), compressed.size()))) {
return base::unexpected(Status::Corruption("ZSTD decompression failed"));
}
return std::move(decompressed);
}
if (compression_type == static_cast<int>(CompressionType::kSnappy)) {
size_t decompressed_length;
base::span<const char> src = base::as_chars(compressed);
if (!snappy::GetUncompressedLength(src.data(), src.size(),
&decompressed_length)) {
return base::unexpected(
Status::Corruption("Snappy decompression failed"));
}
mojo_base::BigBuffer decompressed(decompressed_length);
base::span<char> dest = base::as_writable_chars(base::span(decompressed));
if (!snappy::RawUncompress(src.data(), src.size(), dest.data())) {
return base::unexpected(
Status::Corruption("Snappy decompression failed"));
}
return std::move(decompressed);
}
return base::unexpected(Status::Corruption("unknown compression type"));
}
constexpr std::string_view kV8DataVersionKey = "v8_data_version";
constexpr int kCurrentSchemaVersion = 1;
constexpr int kCompatibleSchemaVersion = kCurrentSchemaVersion;
Status CreateSchema(sql::Database* db, std::u16string_view name) {
EXECUTE_AND_RETURN_STATUS_ON_ERROR(db,
"CREATE TABLE indexed_db_metadata "
"(name BLOB NOT NULL,"
" version INTEGER NOT NULL)");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(
db,
"CREATE TABLE object_stores "
"(id INTEGER PRIMARY KEY,"
" name BLOB NOT NULL,"
" key_path BLOB,"
" auto_increment INTEGER NOT NULL,"
" key_generator_current_number INTEGER NOT NULL)");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(db,
"CREATE TABLE indexes "
"(object_store_id INTEGER NOT NULL,"
" id INTEGER NOT NULL,"
" name BLOB NOT NULL,"
" key_path BLOB,"
" is_unique INTEGER NOT NULL,"
" multi_entry INTEGER NOT NULL,"
" PRIMARY KEY (object_store_id, id)"
") WITHOUT ROWID");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(db,
"CREATE TABLE records "
"(row_id INTEGER PRIMARY KEY,"
" object_store_id INTEGER NOT NULL,"
" compression_type INTEGER NOT NULL,"
" key BLOB NOT NULL,"
" value BLOB NOT NULL)");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(
db,
"CREATE UNIQUE INDEX records_by_key ON records(object_store_id, key)");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(
db,
"CREATE TABLE index_references "
"(record_row_id INTEGER NOT NULL,"
" index_id INTEGER NOT NULL,"
" key BLOB NOT NULL,"
" object_store_id INTEGER NOT NULL,"
" record_key BLOB NOT NULL,"
" PRIMARY KEY (record_row_id, index_id, key)"
") WITHOUT ROWID");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(
db,
"CREATE INDEX index_references_by_key "
"ON index_references (object_store_id, index_id, key, record_key)");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(
db,
"CREATE TABLE blobs "
"(row_id INTEGER PRIMARY KEY AUTOINCREMENT,"
" object_type INTEGER NOT NULL,"
" mime_type TEXT,"
" size_bytes INTEGER,"
" file_name BLOB,"
" last_modified INTEGER, "
" bytes BLOB)");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(
db,
"CREATE TABLE overflow_blob_chunks "
"(row_id INTEGER PRIMARY KEY,"
" blob_row_id INTEGER NOT NULL,"
" chunk_index INTEGER NOT NULL,"
" bytes BLOB NOT NULL)");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(
db,
"CREATE TABLE blob_references "
"(row_id INTEGER PRIMARY KEY,"
" blob_row_id INTEGER NOT NULL,"
" record_row_id INTEGER)");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(db,
"CREATE INDEX blob_references_by_blob "
"ON blob_references (blob_row_id)");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(db,
"CREATE INDEX blob_references_by_record "
"ON blob_references (record_row_id)");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(
db,
"CREATE TRIGGER on_record_deleted AFTER DELETE ON records "
"BEGIN"
" DELETE FROM index_references WHERE record_row_id = OLD.row_id;"
" DELETE FROM blob_references WHERE record_row_id = OLD.row_id;"
"END");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(
db,
"CREATE TRIGGER on_blob_reference_deleted"
" AFTER DELETE ON blob_references "
"WHEN NOT EXISTS"
" (SELECT 1 FROM blob_references WHERE blob_row_id = OLD.blob_row_id) "
"BEGIN"
" DELETE FROM blobs WHERE row_id = OLD.blob_row_id;"
"END");
EXECUTE_AND_RETURN_STATUS_ON_ERROR(
db,
"CREATE TRIGGER on_blob_deleted"
" AFTER DELETE ON blobs "
"BEGIN"
" DELETE FROM overflow_blob_chunks WHERE blob_row_id = OLD.row_id;"
"END");
sql::Statement statement(
db->GetUniqueStatement("INSERT INTO indexed_db_metadata "
"(name, version) VALUES (?, ?)"));
statement.BindBlob(0, std::u16string(name));
statement.BindInt64(1, blink::IndexedDBDatabaseMetadata::NO_VERSION);
if (!statement.Run()) {
return Status(*db);
}
return Status::OK();
}
std::vector<std::string_view> StartRecordRangeQuery(
std::string_view command,
const blink::IndexedDBKeyRange& key_range) {
std::vector<std::string_view> query_pieces{command};
query_pieces.push_back(
" FROM records"
" WHERE object_store_id = ?");
if (key_range.lower().IsValid()) {
query_pieces.push_back(key_range.lower_open() ? " AND key > ?"
: " AND key >= ?");
}
if (key_range.upper().IsValid()) {
query_pieces.push_back(key_range.upper_open() ? " AND key < ?"
: " AND key <= ?");
}
return query_pieces;
}
int BindRecordRangeQueryParams(sql::Statement& statement,
int64_t object_store_id,
const blink::IndexedDBKeyRange& key_range) {
int param_index = 0;
statement.BindInt64(param_index++, object_store_id);
if (key_range.lower().IsValid()) {
statement.BindBlob(param_index++, EncodeSortableIDBKey(key_range.lower()));
}
if (key_range.upper().IsValid()) {
statement.BindBlob(param_index++, EncodeSortableIDBKey(key_range.upper()));
}
return param_index;
}
class ObjectStoreCursorImpl : public BackingStoreCursorImpl {
public:
static StatusOr<std::unique_ptr<ObjectStoreCursorImpl>> Create(
base::WeakPtr<DatabaseConnection> db,
int64_t object_store_id,
const blink::IndexedDBKeyRange& key_range,
bool key_only,
bool ascending_order) {
std::vector<std::string_view> query_pieces = StartRecordRangeQuery(
key_only ? "SELECT key" : "SELECT key, value, compression_type, row_id",
key_range);
if (ascending_order) {
query_pieces.push_back(
" AND (@is_first_seek = 1 OR key > @position)"
" AND (@target_key IS NULL OR key >= @target_key)"
" ORDER BY key ASC");
} else {
query_pieces.push_back(
" AND (@is_first_seek = 1 OR key < @position)"
" AND (@target_key IS NULL OR key <= @target_key)"
" ORDER BY key DESC");
}
auto [statement_id, statement] = db->CreateCursorStatement(
PassKey(), base::StrCat(query_pieces), object_store_id);
auto cursor =
base::WrapUnique(new ObjectStoreCursorImpl(db, statement_id, key_only));
int param_index =
BindRecordRangeQueryParams(*statement, object_store_id, key_range);
statement->BindBool(cursor->is_first_seek_index_ = param_index++, true);
statement->BindNull(cursor->position_index_ = param_index++);
statement->BindNull(cursor->target_key_index_ = param_index++);
if (!statement->Step()) {
if (!statement->Succeeded()) {
return base::unexpected(db->GetStatusOfLastOperation(PassKey()));
}
return nullptr;
}
Status s = cursor->UpdateRecord(*statement);
if (!s.ok()) {
return base::unexpected(s);
}
return std::move(cursor);
}
void SavePosition() override { saved_position_ = position_; }
Status TryResetToLastSavedPosition() override {
if (!saved_position_) {
return Status::InvalidArgument("Position not saved");
}
position_ = *std::move(saved_position_);
saved_position_.reset();
return BackingStoreCursorImpl::TryResetToLastSavedPosition();
}
protected:
void BindParameters(sql::Statement& statement,
const blink::IndexedDBKey& target_key,
const blink::IndexedDBKey& target_primary_key) override {
CHECK(!target_primary_key.IsValid());
statement.BindBool(is_first_seek_index_, false);
statement.BindBlob(position_index_, position_);
if (target_key.IsValid()) {
statement.BindBlob(target_key_index_, EncodeSortableIDBKey(target_key));
} else {
statement.BindNull(target_key_index_);
}
}
StatusOr<std::unique_ptr<Record>> ReadRow(
sql::Statement& statement) override {
CHECK(statement.Succeeded());
position_ = statement.ColumnBlobAsString(0);
blink::IndexedDBKey key = DecodeSortableIDBKey(position_);
if (key_only_) {
return std::make_unique<ObjectStoreKeyOnlyRecord>(std::move(key));
}
base::span<const uint8_t> bits = statement.ColumnBlob(1);
int compression_type = statement.ColumnInt(2);
IndexedDBValue value;
ASSIGN_OR_RETURN(value.bits, db()->Decompress(bits, compression_type));
int64_t record_row_id = statement.ColumnInt64(3);
return db()
->AddExternalObjectMetadataToValue(std::move(value), record_row_id)
.transform([&](IndexedDBValue value_with_metadata) {
return std::make_unique<ObjectStoreRecord>(
std::move(key), std::move(value_with_metadata));
});
}
private:
ObjectStoreCursorImpl(base::WeakPtr<DatabaseConnection> db,
uint64_t statement_id,
bool key_only)
: BackingStoreCursorImpl(std::move(db), statement_id),
key_only_(key_only) {}
bool key_only_;
int is_first_seek_index_ = 0;
int position_index_ = 0;
int target_key_index_ = 0;
std::string position_;
std::optional<std::string> saved_position_;
};
class IndexCursorImpl : public BackingStoreCursorImpl {
public:
static StatusOr<std::unique_ptr<IndexCursorImpl>> Create(
base::WeakPtr<DatabaseConnection> db,
int64_t object_store_id,
int64_t index_id,
const blink::IndexedDBKeyRange& key_range,
bool key_only,
bool first_primary_keys_only,
bool ascending_order) {
std::vector<std::string_view> query_pieces{
"SELECT index_references.key AS index_key"};
if (first_primary_keys_only) {
query_pieces.push_back(", MIN(record_key)");
} else {
query_pieces.push_back(", record_key");
}
if (key_only) {
query_pieces.push_back(" FROM index_references");
} else {
query_pieces.push_back(
", records.value"
", records.compression_type"
", records.row_id"
" FROM index_references INNER JOIN records"
" ON index_references.record_row_id = records.row_id");
}
query_pieces.push_back(
" WHERE index_references.object_store_id = @object_store_id"
" AND index_references.index_id = @index_id");
if (key_range.lower().IsValid()) {
query_pieces.push_back(key_range.lower_open()
? " AND index_key > @lower"
: " AND index_key >= @lower");
}
if (key_range.upper().IsValid()) {
query_pieces.push_back(key_range.upper_open()
? " AND index_key < @upper"
: " AND index_key <= @upper");
}
if (ascending_order) {
if (first_primary_keys_only) {
query_pieces.push_back(
" AND (@is_first_seek = 1 OR index_key > @position)"
" AND (@target_key IS NULL OR index_key >= @target_key)"
" GROUP BY index_key"
" ORDER BY index_key ASC");
} else {
query_pieces.push_back(
" AND "
" ("
" @is_first_seek = 1"
" OR (index_key = @position AND record_key >"
" @object_store_position)"
" OR index_key > @position"
" )"
" AND (@target_key IS NULL OR index_key >= @target_key)"
" AND "
" ("
" @target_primary_key IS NULL"
" OR (index_key = @target_key AND record_key >="
" @target_primary_key)"
" OR index_key > @target_key"
" )"
" ORDER BY index_key ASC, record_key ASC");
}
} else {
if (first_primary_keys_only) {
query_pieces.push_back(
" AND (@is_first_seek = 1 OR index_key < @position)"
" AND (@target_key IS NULL OR index_key <= @target_key)"
" GROUP BY index_key"
" ORDER BY index_key DESC");
} else {
query_pieces.push_back(
" AND "
" ("
" @is_first_seek = 1"
" OR (index_key = @position AND record_key < "
" @object_store_position)"
" OR index_key < @position"
" )"
" AND (@target_key IS NULL OR index_key <= @target_key)"
" AND "
" ("
" @target_primary_key IS NULL"
" OR (index_key = @target_key AND record_key <= "
" @target_primary_key)"
" OR index_key < @target_key"
" )"
" ORDER BY index_key DESC, record_key DESC");
}
}
auto [statement_id, statement] = db->CreateCursorStatement(
PassKey(), base::StrCat(query_pieces), object_store_id);
auto cursor = base::WrapUnique(new IndexCursorImpl(
db, statement_id, key_only, first_primary_keys_only));
int param_index = 0;
statement->BindInt64(param_index++, object_store_id);
statement->BindInt64(param_index++, index_id);
if (key_range.lower().IsValid()) {
statement->BindBlob(param_index++,
EncodeSortableIDBKey(key_range.lower()));
}
if (key_range.upper().IsValid()) {
statement->BindBlob(param_index++,
EncodeSortableIDBKey(key_range.upper()));
}
statement->BindBool(cursor->is_first_seek_index_ = param_index++, true);
statement->BindNull(cursor->position_index_ = param_index++);
if (!first_primary_keys_only) {
cursor->object_store_position_index_ = param_index++;
statement->BindNull(cursor->object_store_position_index_.value());
}
statement->BindNull(cursor->target_key_index_ = param_index++);
if (!first_primary_keys_only) {
cursor->target_primary_key_index_ = param_index++;
statement->BindNull(cursor->target_primary_key_index_.value());
}
if (!statement->Step()) {
if (!statement->Succeeded()) {
return base::unexpected(db->GetStatusOfLastOperation(PassKey()));
}
return nullptr;
}
Status s = cursor->UpdateRecord(*statement);
if (!s.ok()) {
return base::unexpected(s);
}
return std::move(cursor);
}
void SavePosition() override {
saved_position_ = {position_, object_store_position_};
}
Status TryResetToLastSavedPosition() override {
if (!saved_position_) {
return Status::InvalidArgument("Position not saved");
}
std::tie(position_, object_store_position_) = *std::move(saved_position_);
saved_position_.reset();
return BackingStoreCursorImpl::TryResetToLastSavedPosition();
}
protected:
void BindParameters(sql::Statement& statement,
const blink::IndexedDBKey& target_key,
const blink::IndexedDBKey& target_primary_key) override {
CHECK(!first_primary_keys_only_ || !target_primary_key.IsValid());
statement.BindBool(is_first_seek_index_, false);
statement.BindBlob(position_index_, position_);
if (target_key.IsValid()) {
statement.BindBlob(target_key_index_, EncodeSortableIDBKey(target_key));
} else {
statement.BindNull(target_key_index_);
}
if (!first_primary_keys_only_) {
statement.BindBlob(object_store_position_index_.value(),
object_store_position_);
if (target_primary_key.IsValid()) {
statement.BindBlob(target_primary_key_index_.value(),
EncodeSortableIDBKey(target_primary_key));
} else {
statement.BindNull(target_primary_key_index_.value());
}
}
}
StatusOr<std::unique_ptr<Record>> ReadRow(
sql::Statement& statement) override {
CHECK(statement.Succeeded());
position_ = statement.ColumnBlobAsString(0);
blink::IndexedDBKey key = DecodeSortableIDBKey(position_);
object_store_position_ = statement.ColumnBlobAsString(1);
blink::IndexedDBKey primary_key =
DecodeSortableIDBKey(object_store_position_);
if (key_only_) {
return std::make_unique<IndexKeyOnlyRecord>(std::move(key),
std::move(primary_key));
}
base::span<const uint8_t> bits = statement.ColumnBlob(2);
int compression_type = statement.ColumnInt(3);
IndexedDBValue value;
ASSIGN_OR_RETURN(value.bits, db()->Decompress(bits, compression_type));
int64_t record_row_id = statement.ColumnInt64(4);
return db()
->AddExternalObjectMetadataToValue(std::move(value), record_row_id)
.transform([&](IndexedDBValue value_with_metadata) {
return std::make_unique<IndexRecord>(std::move(key),
std::move(primary_key),
std::move(value_with_metadata));
});
}
private:
IndexCursorImpl(base::WeakPtr<DatabaseConnection> db,
uint64_t statement_id,
bool key_only,
bool first_primary_keys_only)
: BackingStoreCursorImpl(std::move(db), statement_id),
key_only_(key_only),
first_primary_keys_only_(first_primary_keys_only) {}
bool key_only_;
bool first_primary_keys_only_;
int is_first_seek_index_ = 0;
int position_index_ = 0;
int target_key_index_ = 0;
std::optional<int> object_store_position_index_;
std::optional<int> target_primary_key_index_;
std::string position_;
std::string object_store_position_;
std::optional<std::tuple<std::string, std::string>> saved_position_;
};
}
StatusOr<std::unique_ptr<DatabaseConnection>> DatabaseConnection::Open(
std::optional<std::u16string_view> name,
base::FilePath path,
BackingStoreImpl& backing_store) {
auto connection =
base::WrapUnique(new DatabaseConnection(path, backing_store));
Status s = connection->Init(name);
if (!path.empty() && !s.ok()) {
IndexedDBDataLossInfo loss;
if (connection->marked_for_permanent_deletion_) {
loss.status = blink::mojom::IDBDataLoss::Total;
loss.message = s.ToString();
}
connection = base::WrapUnique(new DatabaseConnection(path, backing_store));
s = connection->Init(name);
connection->data_loss_info_ = std::move(loss);
s.Log("IndexedDB.SQLite.OpenRetryResult");
}
if (!s.ok()) {
return base::unexpected(s);
}
return connection;
}
void DatabaseConnection::Release(base::WeakPtr<DatabaseConnection> db) {
if (!db) {
return;
}
DatabaseConnection* db_ptr = db.get();
db.reset();
if (db_ptr->CanSelfDestruct()) {
db_ptr->backing_store_->DestroyConnection(db_ptr->metadata_.name);
}
}
DatabaseConnection::DatabaseConnection(base::FilePath path,
BackingStoreImpl& backing_store)
: path_(path), backing_store_(backing_store) {}
DatabaseConnection::~DatabaseConnection() {
active_blobs_.clear();
if (!db_ || in_memory()) {
return;
}
bool had_sql_error =
!sql::IsSqliteSuccessCode(sql::ToSqliteResultCode(db_->GetErrorCode()));
if (marked_for_permanent_deletion_ || (IsZygotic() && !had_sql_error)) {
db_.reset();
sql::Database::Delete(path_);
} else if (had_sql_error) {
LogEvent(SpecificEvent::kDatabaseHadSqlError);
#if BUILDFLAG(IS_FUCHSIA)
if (db_->is_open() && sql::IsErrorCatastrophic(db_->GetErrorCode())) {
db_.reset();
sql::Database::Delete(path_);
}
#else
std::ignore = sql::Recovery::RecoverIfPossible(
db_.get(), db_->GetErrorCode(),
sql::Recovery::Strategy::kRecoverWithMetaVersionOrRaze);
#endif
}
}
Status DatabaseConnection::Init(std::optional<std::u16string_view> name) {
LogEvent(SpecificEvent::kDatabaseOpenAttempt);
constexpr sql::Database::Tag kSqlTag = "IndexedDB";
constexpr sql::Database::Tag kSqlTagInMemory = "IndexedDBEphemeral";
db_ =
std::make_unique<sql::Database>(sql::DatabaseOptions()
.set_exclusive_locking(true)
.set_wal_mode(true)
.set_enable_triggers(true),
in_memory() ? kSqlTagInMemory : kSqlTag);
if (in_memory()) {
RETURN_STATUS_ON_ERROR(db_->OpenInMemory());
} else {
RETURN_STATUS_ON_ERROR(db_->Open(path_));
}
RETURN_STATUS_ON_ERROR(db_->Execute("PRAGMA recursive_triggers=ON"));
sql::Transaction transaction(db_.get());
RETURN_STATUS_ON_ERROR(transaction.Begin());
const bool is_new_db = !sql::MetaTable::DoesTableExist(db_.get());
if (is_new_db) {
IDB_RETURN_IF_ERROR(CreateSchema(db_.get(), *name));
}
meta_table_ = std::make_unique<sql::MetaTable>();
RETURN_STATUS_ON_ERROR(meta_table_->Init(db_.get(), kCurrentSchemaVersion,
kCompatibleSchemaVersion));
if (meta_table_->GetCompatibleVersionNumber() > kCurrentSchemaVersion) {
return Fatal(Status::NotFound("Database too new"),
SpecificEvent::kDatabaseTooNew);
}
const auto current_data_format = IndexedDBDataFormatVersion::GetCurrent();
if (!is_new_db) {
int64_t data_format_version;
if (!meta_table_->GetValue(kV8DataVersionKey, &data_format_version)) {
return Fatal(Status::Corruption("Missing data format version"),
SpecificEvent::kV8FormatTooNewOrMissing);
}
if (!current_data_format.IsAtLeast(
IndexedDBDataFormatVersion::Decode(data_format_version))) {
return Fatal(
Status::NotFound("Unintelligible data format version: too new"),
SpecificEvent::kV8FormatTooNewOrMissing);
}
}
meta_table_->SetValue(kV8DataVersionKey, current_data_format.Encode());
switch (meta_table_->GetVersionNumber()) {
case kCurrentSchemaVersion:
break;
default:
return Fatal(Status::NotFound(
"Unknown database schema version (database too new?)"),
SpecificEvent::kDatabaseSchemaUnknown);
}
StatusOr<blink::IndexedDBDatabaseMetadata> metadata =
GenerateIndexedDbMetadata();
if (!metadata.has_value()) {
return metadata.error();
}
metadata_ = *std::move(metadata);
if (name && (metadata_.name != *name)) {
return Fatal(Status::Corruption("Database name mismatch"),
SpecificEvent::kDatabaseNameMismatch);
}
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"DELETE FROM blob_references WHERE record_row_id IS NULL"));
RETURN_STATUS_ON_ERROR(statement.Run());
RETURN_STATUS_ON_ERROR(transaction.Commit());
return Status::OK();
}
bool DatabaseConnection::IsZygotic() const {
return metadata().version == blink::IndexedDBDatabaseMetadata::NO_VERSION;
}
int64_t DatabaseConnection::GetCommittedVersion() const {
return metadata_snapshot_ ? metadata_snapshot_->version : metadata_.version;
}
uint64_t DatabaseConnection::GetInMemorySize() const {
CHECK(in_memory());
uint32_t page_count = 0;
uint16_t page_size = 0;
{
sql::Statement statement(db_->GetReadonlyStatement("PRAGMA page_count"));
if (!statement.Step()) {
LogEvent(SpecificEvent::kPragmaPageCountFailed);
return 0;
}
page_count = static_cast<uint32_t>(statement.ColumnInt(0));
}
{
sql::Statement statement(db_->GetReadonlyStatement("PRAGMA page_size"));
if (!statement.Step()) {
LogEvent(SpecificEvent::kPragmaPageSizeFailed);
return 0;
}
page_size = static_cast<uint16_t>(statement.ColumnInt(0));
}
return static_cast<uint64_t>(page_count) * page_size;
}
std::unique_ptr<BackingStoreDatabaseImpl>
DatabaseConnection::CreateDatabaseWrapper() {
return std::make_unique<BackingStoreDatabaseImpl>(
interface_wrapper_weak_factory_.GetWeakPtr());
}
std::unique_ptr<BackingStoreTransactionImpl>
DatabaseConnection::CreateTransactionWrapper(
base::PassKey<BackingStoreDatabaseImpl>,
blink::mojom::IDBTransactionDurability durability,
blink::mojom::IDBTransactionMode mode) {
return std::make_unique<BackingStoreTransactionImpl>(
interface_wrapper_weak_factory_.GetWeakPtr(), durability, mode);
}
Status DatabaseConnection::BeginTransaction(
base::PassKey<BackingStoreTransactionImpl>,
const BackingStoreTransactionImpl& transaction) {
CHECK(!HasActiveVersionChangeTransaction());
if (transaction.mode() == blink::mojom::IDBTransactionMode::ReadOnly) {
return Status::OK();
}
CHECK(!active_rw_transaction_);
active_rw_transaction_ = std::make_unique<sql::Transaction>(db_.get());
if (transaction.durability() ==
blink::mojom::IDBTransactionDurability::Strict) {
RETURN_STATUS_ON_ERROR(db_->Execute("PRAGMA synchronous=FULL"));
} else {
RETURN_STATUS_ON_ERROR(db_->Execute("PRAGMA synchronous=NORMAL"));
}
RETURN_STATUS_ON_ERROR(active_rw_transaction_->Begin());
if (transaction.mode() == blink::mojom::IDBTransactionMode::VersionChange) {
metadata_snapshot_.emplace(metadata_);
}
return Status::OK();
}
Status DatabaseConnection::CommitTransactionPhaseOne(
base::PassKey<BackingStoreTransactionImpl>,
const BackingStoreTransactionImpl& transaction,
BlobWriteCallback callback,
SerializeFsaCallback serialize_fsa_handle) {
CHECK(blob_write_callback_.is_null());
CHECK(blob_writers_.empty());
CHECK_EQ(outstanding_external_object_writes_, 0U);
std::map<int64_t, IndexedDBExternalObject> blobs_to_commit =
std::move(blobs_staged_for_commit_);
for (auto& [blob_row_id, external_object] : blobs_to_commit) {
{
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE, "SELECT 1 FROM blobs WHERE row_id = ?"));
statement.BindInt64(0, blob_row_id);
if (!statement.Step()) {
continue;
}
}
++outstanding_external_object_writes_;
if (external_object.object_type() ==
IndexedDBExternalObject::ObjectType::kFileSystemAccessHandle) {
serialize_fsa_handle.Run(
*external_object.file_system_access_token_remote(),
base::BindOnce(&DatabaseConnection::OnFsaHandleSerialized,
blob_writers_weak_factory_.GetWeakPtr(), blob_row_id));
continue;
}
std::unique_ptr<BlobWriter> writer = BlobWriter::WriteBlobIntoDatabase(
external_object,
base::BindRepeating(&DatabaseConnection::OpenBlobChunkForStreaming,
base::Unretained(this), blob_row_id,
false),
base::BindOnce(&DatabaseConnection::OnBlobWriteComplete,
blob_writers_weak_factory_.GetWeakPtr(), blob_row_id));
blob_writers_[blob_row_id] = std::move(writer);
}
if (outstanding_external_object_writes_ == 0) {
return std::move(callback).Run(
BlobWriteResult::kRunPhaseTwoAndReturnResult);
}
CHECK_NE(transaction.mode(), blink::mojom::IDBTransactionMode::ReadOnly);
blob_write_callback_ = std::move(callback);
return Status::OK();
}
std::optional<sql::StreamingBlobHandle>
DatabaseConnection::OpenBlobChunkForStreaming(int64_t blob_row_id,
bool readonly,
size_t chunk_index) {
if (chunk_index == 0) {
return db_->GetStreamingBlob("blobs", "bytes", blob_row_id, readonly);
}
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE,
"SELECT row_id "
"FROM overflow_blob_chunks "
"WHERE blob_row_id = ? AND chunk_index = ?"));
statement.BindInt64(0, blob_row_id);
statement.BindInt64(1, chunk_index);
if (!statement.Step()) {
if (statement.Succeeded()) {
Fatal(Status::NotFound("Blob chunk missing"),
SpecificEvent::kBlobChunkMissing);
} else {
LogEvent(SpecificEvent::kOpenBlobForStreamingFailed);
}
return std::nullopt;
}
int64_t chunk_row_id = statement.ColumnInt64(0);
return db_->GetStreamingBlob("overflow_blob_chunks", "bytes", chunk_row_id,
readonly);
}
void DatabaseConnection::OnBlobWriteComplete(int64_t blob_row_id,
bool success) {
blob_writers_.erase(blob_row_id);
if (!success) {
CancelBlobWriting();
return;
}
if (--outstanding_external_object_writes_ == 0) {
std::move(blob_write_callback_).Run(BlobWriteResult::kRunPhaseTwoAsync);
}
}
void DatabaseConnection::OnFsaHandleSerialized(
int64_t blob_row_id,
const std::vector<uint8_t>& data) {
bool success = false;
if (!data.empty()) {
sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
"UPDATE blobs "
"SET bytes = ? "
"WHERE row_id = ?"));
statement.BindBlob(0, data);
statement.BindInt64(1, blob_row_id);
success = statement.Run();
}
OnBlobWriteComplete(blob_row_id, success);
}
void DatabaseConnection::CancelBlobWriting() {
blob_writers_weak_factory_.InvalidateWeakPtrs();
blob_writers_.clear();
outstanding_external_object_writes_ = 0;
if (blob_write_callback_) {
std::move(blob_write_callback_)
.Run(base::unexpected(Status::IOError("Error")));
}
}
Status DatabaseConnection::CommitTransactionPhaseTwo(
base::PassKey<BackingStoreTransactionImpl>,
const BackingStoreTransactionImpl& transaction) {
if (transaction.mode() == blink::mojom::IDBTransactionMode::ReadOnly) {
return Status::OK();
}
sync_active_blobs_after_transaction_ = false;
RETURN_STATUS_ON_ERROR(active_rw_transaction_->Commit());
if (transaction.mode() == blink::mojom::IDBTransactionMode::VersionChange) {
CHECK(metadata_snapshot_.has_value());
metadata_snapshot_.reset();
}
return Status::OK();
}
void DatabaseConnection::RollBackTransaction(
base::PassKey<BackingStoreTransactionImpl>,
const BackingStoreTransactionImpl& transaction) {
if (transaction.mode() == blink::mojom::IDBTransactionMode::ReadOnly) {
return;
}
blobs_staged_for_commit_.clear();
blob_write_callback_.Reset();
CancelBlobWriting();
active_rw_transaction_->Rollback();
if (transaction.mode() == blink::mojom::IDBTransactionMode::VersionChange) {
CHECK(metadata_snapshot_.has_value());
metadata_ = *std::move(metadata_snapshot_);
metadata_snapshot_.reset();
}
}
void DatabaseConnection::EndTransaction(
base::PassKey<BackingStoreTransactionImpl>,
const BackingStoreTransactionImpl& transaction) {
if (transaction.mode() == blink::mojom::IDBTransactionMode::ReadOnly) {
return;
}
CHECK(active_rw_transaction_);
active_rw_transaction_.reset();
if (sync_active_blobs_after_transaction_) {
sql::Transaction sql_transaction(db_.get());
if (!sql_transaction.Begin()) {
LogEvent(SpecificEvent::kSyncActiveBlobsFailed);
return;
}
{
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE,
"UPDATE blob_references SET record_row_id = 0"
" WHERE record_row_id IS NULL"));
if (!statement.Run()) {
LogEvent(SpecificEvent::kSyncActiveBlobsFailed);
return;
}
}
for (auto& [blob_number, _] : active_blobs_) {
if (!AddActiveBlobReference(blob_number)) {
LogEvent(SpecificEvent::kSyncActiveBlobsFailed);
return;
}
}
{
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"DELETE FROM blob_references WHERE record_row_id = 0"));
if (!statement.Run()) {
LogEvent(SpecificEvent::kSyncActiveBlobsFailed);
return;
}
}
if (!sql_transaction.Commit()) {
LogEvent(SpecificEvent::kSyncActiveBlobsFailed);
}
sync_active_blobs_after_transaction_ = false;
}
}
Status DatabaseConnection::SetDatabaseVersion(
base::PassKey<BackingStoreTransactionImpl>,
int64_t version) {
CHECK(HasActiveVersionChangeTransaction());
sql::Statement statement(
db_->GetUniqueStatement("UPDATE indexed_db_metadata SET version = ?"));
statement.BindInt64(0, version);
RETURN_STATUS_ON_ERROR(statement.Run());
metadata_.version = version;
return Status::OK();
}
Status DatabaseConnection::CreateObjectStore(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
std::u16string name,
blink::IndexedDBKeyPath key_path,
bool auto_increment) {
CHECK(HasActiveVersionChangeTransaction());
CHECK(!metadata_.object_stores.contains(object_store_id));
CHECK_GT(object_store_id, metadata_.max_object_store_id);
blink::IndexedDBObjectStoreMetadata metadata(
std::move(name), object_store_id, std::move(key_path), auto_increment);
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"INSERT INTO object_stores "
"(id, name, key_path, auto_increment, key_generator_current_number) "
"VALUES (?, ?, ?, ?, ?)"));
statement.BindInt64(0, metadata.id);
statement.BindBlob(1, metadata.name);
BindKeyPath(statement, 2, metadata.key_path);
statement.BindBool(3, metadata.auto_increment);
statement.BindInt64(4, ObjectStoreMetaDataKey::kKeyGeneratorInitialNumber);
RETURN_STATUS_ON_ERROR(statement.Run());
metadata_.object_stores[object_store_id] = std::move(metadata);
metadata_.max_object_store_id = object_store_id;
return Status::OK();
}
Status DatabaseConnection::DeleteObjectStore(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id) {
CHECK(HasActiveVersionChangeTransaction());
CHECK(metadata_.object_stores.contains(object_store_id));
{
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"DELETE FROM index_references WHERE object_store_id = ?"));
statement.BindInt64(0, object_store_id);
RETURN_STATUS_ON_ERROR(statement.Run());
}
{
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM indexes WHERE object_store_id = ?"));
statement.BindInt64(0, object_store_id);
RETURN_STATUS_ON_ERROR(statement.Run());
}
{
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM records WHERE object_store_id = ?"));
statement.BindInt64(0, object_store_id);
RETURN_STATUS_ON_ERROR(statement.Run());
}
{
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM object_stores WHERE id = ?"));
statement.BindInt64(0, object_store_id);
RETURN_STATUS_ON_ERROR(statement.Run());
}
CHECK(metadata_.object_stores.erase(object_store_id) == 1);
return Status::OK();
}
Status DatabaseConnection::RenameObjectStore(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const std::u16string& new_name) {
CHECK(HasActiveVersionChangeTransaction());
CHECK(metadata_.object_stores.contains(object_store_id));
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE, "UPDATE object_stores SET name = ? WHERE id = ?"));
statement.BindBlob(0, new_name);
statement.BindInt64(1, object_store_id);
RETURN_STATUS_ON_ERROR(statement.Run());
metadata_.object_stores.at(object_store_id).name = new_name;
return Status::OK();
}
Status DatabaseConnection::CreateIndex(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
blink::IndexedDBIndexMetadata index) {
CHECK(HasActiveVersionChangeTransaction());
CHECK(metadata_.object_stores.contains(object_store_id));
blink::IndexedDBObjectStoreMetadata& object_store =
metadata_.object_stores.at(object_store_id);
int64_t index_id = index.id;
CHECK(!object_store.indexes.contains(index_id));
CHECK_GT(index_id, object_store.max_index_id);
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"INSERT INTO indexes "
"(object_store_id, id, name, key_path, is_unique, multi_entry) "
"VALUES (?, ?, ?, ?, ?, ?)"));
statement.BindInt64(0, object_store_id);
statement.BindInt64(1, index_id);
statement.BindBlob(2, index.name);
BindKeyPath(statement, 3, index.key_path);
statement.BindBool(4, index.unique);
statement.BindBool(5, index.multi_entry);
RETURN_STATUS_ON_ERROR(statement.Run());
object_store.indexes[index_id] = std::move(index);
object_store.max_index_id = index_id;
return Status::OK();
}
Status DatabaseConnection::DeleteIndex(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id) {
CHECK(HasActiveVersionChangeTransaction());
ValidateInputs(object_store_id, index_id);
{
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM index_references "
"WHERE object_store_id = ? AND index_id = ?"));
statement.BindInt64(0, object_store_id);
statement.BindInt64(1, index_id);
RETURN_STATUS_ON_ERROR(statement.Run());
}
{
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"DELETE FROM indexes WHERE object_store_id = ? AND id = ?"));
statement.BindInt64(0, object_store_id);
statement.BindInt64(1, index_id);
RETURN_STATUS_ON_ERROR(statement.Run());
}
CHECK(metadata_.object_stores.at(object_store_id).indexes.erase(index_id) ==
1);
return Status::OK();
}
Status DatabaseConnection::RenameIndex(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id,
const std::u16string& new_name) {
CHECK(HasActiveVersionChangeTransaction());
ValidateInputs(object_store_id, index_id);
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"UPDATE indexes SET name = ? WHERE object_store_id = ? AND id = ?"));
statement.BindBlob(0, new_name);
statement.BindInt64(1, object_store_id);
statement.BindInt64(2, index_id);
RETURN_STATUS_ON_ERROR(statement.Run());
metadata_.object_stores.at(object_store_id).indexes.at(index_id).name =
new_name;
return Status::OK();
}
StatusOr<int64_t> DatabaseConnection::GetKeyGeneratorCurrentNumber(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id) {
CHECK(metadata_.object_stores.contains(object_store_id));
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE,
"SELECT key_generator_current_number "
"FROM object_stores WHERE id = ?"));
statement.BindInt64(0, object_store_id);
if (!statement.Step()) {
RETURN_IF_STATEMENT_ERRORED(statement);
return base::unexpected(
Fatal(Status::NotFound("Object store not found in database"),
SpecificEvent::kObjectStoreNotFound));
}
return statement.ColumnInt64(0);
}
Status DatabaseConnection::MaybeUpdateKeyGeneratorCurrentNumber(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t new_number) {
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"UPDATE object_stores SET key_generator_current_number = ? "
"WHERE id = ? AND key_generator_current_number < ?"));
statement.BindInt64(0, new_number);
statement.BindInt64(1, object_store_id);
statement.BindInt64(2, new_number);
RETURN_STATUS_ON_ERROR(statement.Run());
return Status::OK();
}
StatusOr<std::optional<BackingStore::RecordIdentifier>>
DatabaseConnection::GetRecordIdentifierIfExists(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const blink::IndexedDBKey& key) {
std::string encoded_key = EncodeSortableIDBKey(key);
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE,
"SELECT row_id FROM records "
"WHERE object_store_id = ? AND key = ?"));
statement.BindInt64(0, object_store_id);
statement.BindBlob(1, encoded_key);
if (statement.Step()) {
return BackingStore::RecordIdentifier{statement.ColumnInt64(0),
std::move(encoded_key)};
}
RETURN_IF_STATEMENT_ERRORED(statement);
return std::nullopt;
}
StatusOr<IndexedDBValue> DatabaseConnection::GetValue(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const blink::IndexedDBKey& key) {
IndexedDBValue value;
int64_t record_row_id;
{
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"SELECT row_id, value, compression_type FROM records "
"WHERE object_store_id = ? AND key = ?"));
statement.BindInt64(0, object_store_id);
statement.BindBlob(1, EncodeSortableIDBKey(key));
if (!statement.Step()) {
RETURN_IF_STATEMENT_ERRORED(statement);
return IndexedDBValue();
}
record_row_id = statement.ColumnInt64(0);
base::span<const uint8_t> bits = statement.ColumnBlob(1);
int compression_type = statement.ColumnInt(2);
ASSIGN_OR_RETURN(value.bits, Decompress(bits, compression_type));
}
return AddExternalObjectMetadataToValue(std::move(value), record_row_id);
}
StatusOr<IndexedDBValue> DatabaseConnection::AddExternalObjectMetadataToValue(
IndexedDBValue value,
int64_t record_row_id) {
{
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"SELECT "
" blobs.row_id, object_type, mime_type, size_bytes, file_name, "
" last_modified "
"FROM blobs INNER JOIN blob_references"
" ON blob_references.blob_row_id = blobs.row_id "
"WHERE"
" blob_references.record_row_id = ? AND object_type != ? "
"ORDER BY blobs.row_id"));
statement.BindInt64(0, record_row_id);
statement.BindInt64(
1, static_cast<int>(
IndexedDBExternalObject::ObjectType::kFileSystemAccessHandle));
while (statement.Step()) {
const int64_t blob_row_id = statement.ColumnInt64(0);
if (auto it = blobs_staged_for_commit_.find(blob_row_id);
it != blobs_staged_for_commit_.end()) {
value.external_objects.emplace_back(it->second);
} else {
auto object_type = static_cast<IndexedDBExternalObject::ObjectType>(
statement.ColumnInt(1));
if (object_type == IndexedDBExternalObject::ObjectType::kBlob) {
value.external_objects.emplace_back(
statement.ColumnString16(2),
statement.ColumnInt64(3), blob_row_id);
} else if (object_type == IndexedDBExternalObject::ObjectType::kFile) {
value.external_objects.emplace_back(
blob_row_id, statement.ColumnString16(2),
statement.ColumnString16(4),
statement.ColumnTime(5),
statement.ColumnInt64(3));
} else {
return base::unexpected(
Fatal(Status::Corruption("Unknown object type in `blobs`"),
SpecificEvent::kBlobTypeUnknown));
}
}
}
RETURN_IF_STATEMENT_ERRORED(statement);
}
{
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"SELECT "
" blobs.row_id, bytes "
"FROM blobs INNER JOIN blob_references"
" ON blob_references.blob_row_id = blobs.row_id "
"WHERE"
" blob_references.record_row_id = ? AND object_type = ? "
"ORDER BY blobs.row_id"));
statement.BindInt64(0, record_row_id);
statement.BindInt64(
1, static_cast<int>(
IndexedDBExternalObject::ObjectType::kFileSystemAccessHandle));
while (statement.Step()) {
const int64_t blob_row_id = statement.ColumnInt64(0);
if (auto it = blobs_staged_for_commit_.find(blob_row_id);
it != blobs_staged_for_commit_.end()) {
mojo::PendingRemote<blink::mojom::FileSystemAccessTransferToken>
token_clone;
it->second.file_system_access_token_remote()->Clone(
token_clone.InitWithNewPipeAndPassReceiver());
value.external_objects.emplace_back(std::move(token_clone));
} else {
base::span<const uint8_t> serialized_handle = statement.ColumnBlob(1);
value.external_objects.emplace_back(std::vector<uint8_t>(
serialized_handle.begin(), serialized_handle.end()));
}
}
RETURN_IF_STATEMENT_ERRORED(statement);
}
return value;
}
StatusOr<BackingStore::RecordIdentifier> DatabaseConnection::PutRecord(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const blink::IndexedDBKey& key,
IndexedDBValue value) {
const std::string encoded_key = EncodeSortableIDBKey(key);
{
std::vector<uint8_t> bits_copy;
base::span<const uint8_t> bits_span;
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"INSERT OR REPLACE INTO records "
"(object_store_id, key, compression_type, value) VALUES (?, ?, ?, ?)"));
statement.BindInt64(0, object_store_id);
statement.BindBlob(1, encoded_key);
CompressionType compression_type = CompressionType::kUncompressed;
static constexpr base::ByteSize kMinimumCompressionSize(64);
static constexpr float kMinimumCompressionRatio = 0.8f;
if (value.bits.storage_type() ==
mojo_base::BigBuffer::StorageType::kSharedMemory) {
bits_copy = base::ToVector(std::move(value.bits));
bits_span = base::span(bits_copy);
} else {
bits_span = base::span(value.bits);
}
if (bits_span.size() >= kMinimumCompressionSize.InBytes()) {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_FUCHSIA)
size_t max_compressed_size = ZSTD_compressBound(bits_span.size());
std::vector<uint8_t> compressed_bits(max_compressed_size);
size_t compressed_length = ZSTD_compress(
compressed_bits.data(), compressed_bits.size(), bits_span.data(),
bits_span.size(), -4);
compression_type = CompressionType::kZstd;
#else
size_t max_compressed_size =
snappy::MaxCompressedLength(bits_span.size());
std::vector<uint8_t> compressed_bits(max_compressed_size);
size_t compressed_length = 0;
base::span<const char> src = base::as_chars(bits_span);
base::span<char> dest =
base::as_writable_chars(base::span(compressed_bits));
snappy::RawCompress(src.data(), src.size(), dest.data(),
&compressed_length);
compression_type = CompressionType::kSnappy;
#endif
if (compressed_length <= bits_span.size() * kMinimumCompressionRatio) {
compressed_bits.resize(compressed_length);
bits_copy = std::move(compressed_bits);
bits_span = base::span(bits_copy);
} else {
compression_type = CompressionType::kUncompressed;
}
}
statement.BindInt(2, static_cast<int>(compression_type));
statement.BindBlob(
3, base::MakeRefCounted<base::RefCountedStaticMemory>(bits_span));
RUN_STATEMENT_RETURN_ON_ERROR(statement);
}
const int64_t record_row_id = db_->GetLastInsertRowId();
for (auto& external_object : value.external_objects) {
int64_t blob_row_id = -1;
if (external_object.object_type() ==
IndexedDBExternalObject::ObjectType::kFileSystemAccessHandle) {
sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO blobs "
"(object_type) "
"VALUES (?)"));
statement.BindInt(0, static_cast<int>(external_object.object_type()));
RUN_STATEMENT_RETURN_ON_ERROR(statement);
blob_row_id = db_->GetLastInsertRowId();
} else {
int main_chunk_size =
std::min(external_object.size(), GetMaxBlobSize().InBytes());
{
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO blobs "
"(object_type, mime_type, size_bytes, "
"bytes, file_name, last_modified) "
"VALUES (?, ?, ?, ?, ?, ?)"));
statement.BindInt(0, static_cast<int>(external_object.object_type()));
statement.BindString16(1, external_object.type());
statement.BindInt64(2, external_object.size());
statement.BindBlobForStreaming(3, main_chunk_size);
if (external_object.object_type() ==
IndexedDBExternalObject::ObjectType::kBlob) {
statement.BindNull(4);
statement.BindNull(5);
} else {
CHECK_EQ(external_object.object_type(),
IndexedDBExternalObject::ObjectType::kFile);
statement.BindString16(4, external_object.file_name());
statement.BindTime(5, external_object.last_modified());
}
RUN_STATEMENT_RETURN_ON_ERROR(statement);
}
blob_row_id = db_->GetLastInsertRowId();
int chunk_index = 1;
for (int64_t bytes_written = main_chunk_size;
bytes_written < external_object.size();) {
const int64_t chunk_size = std::min(
external_object.size() - bytes_written, GetMaxBlobSize().InBytes());
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO overflow_blob_chunks "
"(blob_row_id, chunk_index, bytes)"
"VALUES (?, ?, ?)"));
statement.BindInt64(0, blob_row_id);
statement.BindInt(1, chunk_index++);
statement.BindBlobForStreaming(2, chunk_size);
RUN_STATEMENT_RETURN_ON_ERROR(statement);
bytes_written += chunk_size;
}
}
external_object.set_blob_number(blob_row_id);
{
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO blob_references "
"(blob_row_id, record_row_id) "
"VALUES (?, ?)"));
statement.BindInt64(0, blob_row_id);
statement.BindInt64(1, record_row_id);
RUN_STATEMENT_RETURN_ON_ERROR(statement);
}
auto rv =
blobs_staged_for_commit_.emplace(blob_row_id,
std::move(external_object));
CHECK(rv.second);
}
OnRecordsModified(object_store_id);
return BackingStore::RecordIdentifier{record_row_id, std::move(encoded_key)};
}
Status DatabaseConnection::DeleteRange(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const blink::IndexedDBKeyRange& key_range) {
std::vector<std::string_view> query_pieces =
StartRecordRangeQuery("DELETE", key_range);
sql::Statement statement(db_->GetUniqueStatement(base::StrCat(query_pieces)));
BindRecordRangeQueryParams(statement, object_store_id, key_range);
RETURN_STATUS_ON_ERROR(statement.Run());
OnRecordsModified(object_store_id);
return Status::OK();
}
Status DatabaseConnection::ClearObjectStore(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id) {
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM records WHERE object_store_id = ?"));
statement.BindInt64(0, object_store_id);
RETURN_STATUS_ON_ERROR(statement.Run());
OnRecordsModified(object_store_id);
return Status::OK();
}
StatusOr<uint32_t> DatabaseConnection::GetObjectStoreKeyCount(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
blink::IndexedDBKeyRange key_range) {
std::vector<std::string_view> query_pieces =
StartRecordRangeQuery("SELECT COUNT()", key_range);
sql::Statement statement(
db_->GetReadonlyStatement(base::StrCat(query_pieces)));
BindRecordRangeQueryParams(statement, object_store_id, key_range);
if (!statement.Step()) {
RETURN_IF_STATEMENT_ERRORED(statement);
NOTREACHED();
}
return statement.ColumnInt(0);
}
Status DatabaseConnection::PutIndexDataForRecord(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id,
const blink::IndexedDBKey& key,
const BackingStore::RecordIdentifier& record) {
ValidateInputs(object_store_id, index_id);
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"INSERT OR IGNORE INTO index_references "
"(record_row_id, index_id, key, object_store_id, record_key) "
"VALUES (?, ?, ?, ?, ?)"));
statement.BindInt64(0, record.number);
statement.BindInt64(1, index_id);
statement.BindBlob(2, EncodeSortableIDBKey(key));
statement.BindInt64(3, object_store_id);
statement.BindBlob(4, record.data);
RETURN_STATUS_ON_ERROR(statement.Run());
return Status::OK();
}
StatusOr<blink::IndexedDBKey> DatabaseConnection::GetFirstPrimaryKeyForIndexKey(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id,
const blink::IndexedDBKey& key) {
ValidateInputs(object_store_id, index_id);
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE,
"SELECT MIN(record_key) FROM index_references "
"WHERE object_store_id = ? AND index_id = ? AND key = ?"));
statement.BindInt64(0, object_store_id);
statement.BindInt64(1, index_id);
statement.BindBlob(2, EncodeSortableIDBKey(key));
if (statement.Step()) {
return DecodeSortableIDBKey(statement.ColumnBlobAsString(0));
}
RETURN_IF_STATEMENT_ERRORED(statement);
return blink::IndexedDBKey();
}
StatusOr<uint32_t> DatabaseConnection::GetIndexKeyCount(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
int64_t index_id,
blink::IndexedDBKeyRange key_range) {
ValidateInputs(object_store_id, index_id);
std::vector<std::string_view> query_pieces{
"SELECT COUNT() FROM index_references WHERE object_store_id = ?"
" AND index_id = ?"};
if (key_range.lower().IsValid()) {
query_pieces.push_back(key_range.lower_open() ? " AND key > ?"
: " AND key >= ?");
}
if (key_range.upper().IsValid()) {
query_pieces.push_back(key_range.upper_open() ? " AND key < ?"
: " AND key <= ?");
}
sql::Statement statement(
db_->GetReadonlyStatement(base::StrCat(query_pieces)));
int param_index = 0;
statement.BindInt64(param_index++, object_store_id);
statement.BindInt64(param_index++, index_id);
if (key_range.lower().IsValid()) {
statement.BindBlob(param_index++, EncodeSortableIDBKey(key_range.lower()));
}
if (key_range.upper().IsValid()) {
statement.BindBlob(param_index++, EncodeSortableIDBKey(key_range.upper()));
}
if (!statement.Step()) {
RETURN_IF_STATEMENT_ERRORED(statement);
NOTREACHED();
}
return statement.ColumnInt(0);
}
std::vector<blink::mojom::IDBExternalObjectPtr>
DatabaseConnection::CreateAllExternalObjects(
base::PassKey<BackingStoreTransactionImpl>,
const std::vector<IndexedDBExternalObject>& objects,
DeserializeFsaCallback deserialize_fsa_handle) {
std::vector<blink::mojom::IDBExternalObjectPtr> mojo_objects;
IndexedDBExternalObject::ConvertToMojo(objects, &mojo_objects);
for (size_t i = 0; i < objects.size(); ++i) {
const IndexedDBExternalObject& object = objects[i];
blink::mojom::IDBExternalObjectPtr& mojo_object = mojo_objects[i];
if (object.object_type() ==
IndexedDBExternalObject::ObjectType::kFileSystemAccessHandle) {
mojo::PendingRemote<blink::mojom::FileSystemAccessTransferToken>
mojo_token;
if (object.is_file_system_access_remote_valid()) {
object.file_system_access_token_remote()->Clone(
mojo_token.InitWithNewPipeAndPassReceiver());
} else {
CHECK(!object.serialized_file_system_access_handle().empty());
deserialize_fsa_handle.Run(
object.serialized_file_system_access_handle(),
mojo_token.InitWithNewPipeAndPassReceiver());
}
mojo_object->set_file_system_access_token(std::move(mojo_token));
continue;
}
mojo::PendingReceiver<blink::mojom::Blob> receiver =
mojo_object->get_blob_or_file()->blob.InitWithNewPipeAndPassReceiver();
if (object.is_remote_valid()) {
object.Clone(std::move(receiver));
continue;
}
auto it = active_blobs_.find(object.blob_number());
if (it == active_blobs_.end()) {
auto streamer = std::make_unique<ActiveBlobStreamer>(
object,
base::BindRepeating(&DatabaseConnection::OpenBlobChunkForStreaming,
base::Unretained(this), object.blob_number(),
true),
GetMaxBlobSize().InBytes(),
base::BindOnce(&DatabaseConnection::OnBlobBecameInactive,
base::Unretained(this), object.blob_number()),
base::BindRepeating(&LogNetError, "IndexedDB.BackingStore.ReadBlob",
in_memory()));
it = active_blobs_.insert({object.blob_number(), std::move(streamer)})
.first;
if (!AddActiveBlobReference(object.blob_number())) {
LogEvent(SpecificEvent::kAddActiveBlobReferenceFailed);
}
}
it->second->AddReceiver(std::move(receiver),
backing_store_->blob_storage_context());
}
return mojo_objects;
}
void DatabaseConnection::DeleteIdbDatabase(
base::PassKey<BackingStoreDatabaseImpl>) {
marked_for_permanent_deletion_ = true;
metadata_ = blink::IndexedDBDatabaseMetadata(metadata_.name);
interface_wrapper_weak_factory_.InvalidateWeakPtrs();
CHECK(!blob_writers_weak_factory_.HasWeakPtrs());
if (CanSelfDestruct()) {
backing_store_->DestroyConnection(metadata_.name);
return;
}
cursor_weak_factory_.InvalidateWeakPtrs();
cursor_statements_.clear();
bool success =
db_->Execute(
"DELETE FROM blob_references WHERE record_row_id IS NOT NULL") &&
db_->Execute("DELETE FROM index_references") &&
db_->Execute("DELETE FROM indexes") &&
db_->Execute("DELETE FROM records") &&
db_->Execute("DELETE FROM object_stores") && [&]() {
sql::Statement statement(db_->GetUniqueStatement(
"UPDATE indexed_db_metadata SET version = ?"));
statement.BindInt64(0, blink::IndexedDBDatabaseMetadata::NO_VERSION);
return statement.Run();
}();
if (!success) {
backing_store_->DestroyConnection(metadata_.name);
}
}
void DatabaseConnection::OnBlobBecameInactive(int64_t blob_number) {
CHECK_EQ(active_blobs_.erase(blob_number), 1U);
if (active_rw_transaction_) {
sync_active_blobs_after_transaction_ = true;
}
{
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM blob_references "
"WHERE blob_row_id = ? "
"AND record_row_id IS NULL"));
statement.BindInt64(0, blob_number);
if (!statement.Run()) {
LogEvent(SpecificEvent::kRemoveActiveBlobReferenceFailed);
}
}
if (CanSelfDestruct()) {
backing_store_->DestroyConnection(metadata_.name);
return;
}
}
bool DatabaseConnection::AddActiveBlobReference(int64_t blob_number) {
if (active_rw_transaction_) {
sync_active_blobs_after_transaction_ = true;
}
sql::Statement statement(db_->GetCachedStatement(
SQL_FROM_HERE, "INSERT INTO blob_references (blob_row_id) VALUES (?)"));
statement.BindInt64(0, blob_number);
return statement.Run();
}
bool DatabaseConnection::CanSelfDestruct() const {
if (in_memory() && !marked_for_permanent_deletion_) {
return false;
}
return active_blobs_.empty() &&
!interface_wrapper_weak_factory_.HasWeakPtrs();
}
StatusOr<std::unique_ptr<BackingStore::Cursor>>
DatabaseConnection::OpenObjectStoreCursor(
base::PassKey<BackingStoreTransactionImpl>,
int64_t object_store_id,
const blink::IndexedDBKeyRange& key_range,
blink::mojom::IDBCursorDirection direction,
bool key_only) {
bool ascending_order =
(direction == blink::mojom::IDBCursorDirection::Next ||
direction == blink::mojom::IDBCursorDirection::NextNoDuplicate);
return ObjectStoreCursorImpl::Create(cursor_weak_factory_.GetWeakPtr(),
object_store_id, key_range, key_only,
ascending_order);
}
StatusOr<std::unique_ptr<BackingStore::Cursor>>
DatabaseConnection::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) {
bool ascending_order =
(direction == blink::mojom::IDBCursorDirection::Next ||
direction == blink::mojom::IDBCursorDirection::NextNoDuplicate);
bool first_primary_keys_only =
(direction == blink::mojom::IDBCursorDirection::NextNoDuplicate ||
direction == blink::mojom::IDBCursorDirection::PrevNoDuplicate);
return IndexCursorImpl::Create(cursor_weak_factory_.GetWeakPtr(),
object_store_id, index_id, key_range, key_only,
first_primary_keys_only, ascending_order);
}
std::tuple<uint64_t, sql::Statement*> DatabaseConnection::CreateCursorStatement(
base::PassKey<BackingStoreCursorImpl>,
std::string query,
int64_t object_store_id) {
auto [it, inserted] = cursor_statements_.emplace(
++next_statement_id_, std::make_tuple(std::make_unique<sql::Statement>(
db_->GetUniqueStatement(query)),
object_store_id));
CHECK(inserted);
return {next_statement_id_, std::get<0>(it->second).get()};
}
void DatabaseConnection::ReleaseCursorStatement(
base::PassKey<BackingStoreCursorImpl>,
uint64_t id) {
CHECK_EQ(1U, cursor_statements_.erase(id));
}
sql::Statement* DatabaseConnection::GetCursorStatement(
base::PassKey<BackingStoreCursorImpl>,
uint64_t id) {
auto it = cursor_statements_.find(id);
if (it == cursor_statements_.end()) {
return nullptr;
}
return std::get<0>(it->second).get();
}
void DatabaseConnection::OnRecordsModified(int64_t object_store_id) {
for (const auto& [_, statement_holder] : cursor_statements_) {
const auto& [statement, cursor_object_store_id] = statement_holder;
if (cursor_object_store_id == object_store_id) {
BackingStoreCursorImpl::InvalidateStatement(*statement);
}
}
}
Status DatabaseConnection::GetStatusOfLastOperation(
base::PassKey<BackingStoreCursorImpl>) {
return Status(*db_);
}
StatusOr<blink::IndexedDBDatabaseMetadata>
DatabaseConnection::GenerateIndexedDbMetadata() {
blink::IndexedDBDatabaseMetadata metadata;
{
sql::Statement statement(db_->GetReadonlyStatement(
"SELECT name, version FROM indexed_db_metadata"));
if (!statement.Step()) {
RETURN_IF_STATEMENT_ERRORED(statement);
return base::unexpected(
Fatal(Status::Corruption("Missing table `indexed_db_metadata`"),
SpecificEvent::kMissingMetadataTable));
}
ASSIGN_OR_RETURN(metadata.name, statement.ColumnBlobAsString16(0), [&]() {
return Fatal(Status::Corruption("Database name is unexpected size"),
SpecificEvent::kUtf16StringUnreadable);
});
metadata.version = statement.ColumnInt64(1);
}
{
sql::Statement statement(db_->GetReadonlyStatement(
"SELECT id, name, key_path, auto_increment FROM object_stores"));
int64_t max_object_store_id = 0;
while (statement.Step()) {
blink::IndexedDBObjectStoreMetadata store_metadata;
store_metadata.id = statement.ColumnInt64(0);
ASSIGN_OR_RETURN(
store_metadata.name, statement.ColumnBlobAsString16(1), [&]() {
return Fatal(
Status::Corruption("Object store name is unexpected size"),
SpecificEvent::kUtf16StringUnreadable);
});
ASSIGN_OR_RETURN(store_metadata.key_path, ColumnKeyPath(statement, 2),
[&](Status error) {
return Fatal(error,
SpecificEvent::kUtf16StringUnreadable);
});
store_metadata.auto_increment = statement.ColumnBool(3);
max_object_store_id = std::max(max_object_store_id, store_metadata.id);
metadata.object_stores[store_metadata.id] = std::move(store_metadata);
}
RETURN_IF_STATEMENT_ERRORED(statement);
metadata.max_object_store_id = max_object_store_id;
}
{
sql::Statement statement(db_->GetReadonlyStatement(
"SELECT object_store_id, id, name, key_path, is_unique, multi_entry "
"FROM indexes"));
while (statement.Step()) {
blink::IndexedDBIndexMetadata index_metadata;
int64_t object_store_id = statement.ColumnInt64(0);
index_metadata.id = statement.ColumnInt64(1);
ASSIGN_OR_RETURN(
index_metadata.name, statement.ColumnBlobAsString16(2), [&]() {
return Fatal(Status::Corruption("Index name is unexpected size"),
SpecificEvent::kUtf16StringUnreadable);
});
ASSIGN_OR_RETURN(index_metadata.key_path, ColumnKeyPath(statement, 3),
[&](Status error) {
return Fatal(error,
SpecificEvent::kUtf16StringUnreadable);
});
index_metadata.unique = statement.ColumnBool(4);
index_metadata.multi_entry = statement.ColumnBool(5);
blink::IndexedDBObjectStoreMetadata& store_metadata =
metadata.object_stores[object_store_id];
store_metadata.max_index_id =
std::max(store_metadata.max_index_id, index_metadata.id);
store_metadata.indexes[index_metadata.id] = std::move(index_metadata);
}
RETURN_IF_STATEMENT_ERRORED(statement);
}
return metadata;
}
void DatabaseConnection::LogEvent(SpecificEvent event) const {
if (in_memory()) {
base::UmaHistogramEnumeration("IndexedDB.SQLite.SpecificEvent.InMemory",
event);
} else {
base::UmaHistogramEnumeration("IndexedDB.SQLite.SpecificEvent.OnDisk",
event);
}
}
Status DatabaseConnection::Fatal(Status s, SpecificEvent event) {
LogEvent(event);
marked_for_permanent_deletion_ = true;
return s;
}
void DatabaseConnection::ValidateInputs(int64_t object_store_id,
int64_t index_id) {
auto iter = metadata_.object_stores.find(object_store_id);
CHECK(iter != metadata_.object_stores.end());
CHECK(iter->second.indexes.contains(index_id));
}
StatusOr<mojo_base::BigBuffer> DatabaseConnection::Decompress(
base::span<const uint8_t> compressed,
int compression_type) {
return DoDecompress(compressed, compression_type)
.transform_error([&](Status status) {
return Fatal(status, SpecificEvent::kDecompressionFailure);
});
}
void DatabaseConnection::OverrideMaxBlobSizeForTesting(base::ByteCount size) {
g_max_blob_size_override = size;
}
}