blob: 23d5523418b733a5ee6b3554a99f5b35089f415d [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/indexed_db/instance/database.h"
#include <stdint.h>
#include <array>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include "base/auto_reset.h"
#include "base/containers/span.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/updateable_sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h"
#include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom.h"
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
#include "content/browser/indexed_db/indexed_db_leveldb_coding.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/bucket_context.h"
#include "content/browser/indexed_db/instance/connection.h"
#include "content/browser/indexed_db/instance/cursor.h"
#include "content/browser/indexed_db/instance/database_callbacks.h"
#include "content/browser/indexed_db/instance/factory_client.h"
#include "content/browser/indexed_db/instance/fake_transaction.h"
#include "content/browser/indexed_db/instance/mock_factory_client.h"
#include "content/browser/indexed_db/instance/mock_file_system_access_context.h"
#include "content/browser/indexed_db/instance/transaction.h"
#include "content/browser/indexed_db/mock_mojo_indexed_db_database_callbacks.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "storage/browser/test/mock_quota_manager.h"
#include "storage/browser/test/mock_quota_manager_proxy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
using blink::IndexedDBDatabaseMetadata;
using blink::IndexedDBIndexKeys;
using blink::IndexedDBKey;
using blink::IndexedDBKeyPath;
namespace content::indexed_db {
namespace {
constexpr int64_t kTestObjectStoreId = 1001;
constexpr int64_t kTestIndexId = 2002;
constexpr char kTestForceCloseMessage[] =
"The database's connection is force-closed.";
// Contains a record's keys and value that tests use to populate the database.
struct TestIDBRecord {
TestIDBRecord(IndexedDBKey primary_key,
const IndexedDBValue& value,
std::optional<IndexedDBKey> index_key)
: primary_key(std::move(primary_key)),
value(value.Clone()),
index_key(std::move(index_key)) {}
TestIDBRecord(const TestIDBRecord& other) {
primary_key = other.primary_key.Clone();
value = other.value.Clone();
if (other.index_key) {
index_key = other.index_key->Clone();
}
}
IndexedDBKey primary_key;
IndexedDBValue value;
// Optional. Tests may skip index creation.
std::optional<IndexedDBKey> index_key;
};
// Contains the options used to create an object store. Initializes members
// to reasonable defaults that tests may override.
struct TestObjectStoreParameters {
int64_t object_store_id = 0;
std::u16string name{u"store"};
IndexedDBKeyPath key_path;
bool auto_increment = false;
};
// Contains the options used to create an index. Optional. Test setup skips
// index creation when `index_id` is `kInvalidId`. Initializes members to
// reasonable defaults that tests may override.
struct TestIndexParameters {
int64_t index_id = blink::IndexedDBIndexMetadata::kInvalidId;
std::u16string name{u"index"};
bool unique = false;
bool multi_entry = false;
IndexedDBKeyPath key_path;
bool auto_increment = false;
};
// Describes how test setup should create and populate an object store and
// optionally an index.
struct TestDatabaseParameters {
TestObjectStoreParameters object_store_parameters;
TestIndexParameters index_parameters;
std::vector<TestIDBRecord> records;
};
// Contains the arguments needed to call `Database::GetAllOperation`.
// Initializes members to reasonable defaults that tests may override.
struct TestGetAllParameters {
blink::mojom::IDBGetAllResultType result_type =
blink::mojom::IDBGetAllResultType::Keys;
blink::IndexedDBKeyRange key_range;
int64_t max_count = std::numeric_limits<int64_t>::max();
blink::mojom::IDBCursorDirection direction =
blink::mojom::IDBCursorDirection::Next;
};
// `Database::GetAllOperation` streams record results to a sink. This fake
// implementation enables test to wait for all the results and inspect them.
class FakeGetAllResultSink final
: public blink::mojom::IDBDatabaseGetAllResultSink {
public:
FakeGetAllResultSink() = default;
~FakeGetAllResultSink() final {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
FakeGetAllResultSink(const FakeGetAllResultSink&) = delete;
FakeGetAllResultSink& operator=(const FakeGetAllResultSink&) = delete;
blink::mojom::IDBError* GetError() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(is_done_);
return error_.get();
}
const std::vector<blink::mojom::IDBRecordPtr>& GetResults() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(is_done_);
return records_;
}
void BindReceiver(
mojo::PendingAssociatedReceiver<blink::mojom::IDBDatabaseGetAllResultSink>
receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!is_done_);
receiver_.Bind(std::move(receiver));
}
void WaitForResults() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
run_loop_.Run();
CHECK(is_done_);
}
private:
void ReceiveResults(std::vector<blink::mojom::IDBRecordPtr> records,
bool done) final {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!is_done_);
if (records_.empty()) {
records_ = std::move(records);
} else {
records_.reserve(records_.size() + records.size());
for (auto& record : records) {
records_.emplace_back(std::move(record));
}
}
if (done) {
is_done_ = true;
run_loop_.Quit();
}
}
void OnError(blink::mojom::IDBErrorPtr error) final {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!is_done_);
error_ = std::move(error);
is_done_ = true;
run_loop_.Quit();
}
SEQUENCE_CHECKER(sequence_checker_);
mojo::AssociatedReceiver<blink::mojom::IDBDatabaseGetAllResultSink> receiver_{
this};
// Used to wait until all results have been received.
base::RunLoop run_loop_;
bool is_done_ = false;
// Store results and error for later inspection. Must not be accessed until
// `is_done_` is true.
std::vector<blink::mojom::IDBRecordPtr> records_;
blink::mojom::IDBErrorPtr error_;
};
void ExpectEqualsIndexedDBKey(const IndexedDBKey& expected_primary_key,
const IndexedDBKey& actual_primary_key) {
ASSERT_EQ(actual_primary_key.IsValid(), expected_primary_key.IsValid());
if (expected_primary_key.IsValid()) {
EXPECT_TRUE(actual_primary_key.Equals(expected_primary_key))
<< "Expected " << expected_primary_key.DebugString() << " but got "
<< actual_primary_key.DebugString();
}
}
void ExpectEqualsOptionalIndexedDBKey(
const std::optional<IndexedDBKey>& expected_primary_key,
const std::optional<IndexedDBKey>& actual_primary_key) {
ASSERT_EQ(actual_primary_key.has_value(), expected_primary_key.has_value());
if (expected_primary_key.has_value()) {
ASSERT_NO_FATAL_FAILURE(
ExpectEqualsIndexedDBKey(*expected_primary_key, *actual_primary_key));
}
}
void ExpectEqualsIDBReturnValuePtr(
const blink::mojom::IDBReturnValuePtr& expected_return_value,
const blink::mojom::IDBReturnValuePtr& actual_return_value) {
ASSERT_EQ(actual_return_value.is_null(), expected_return_value.is_null());
if (!expected_return_value.is_null()) {
// Verify the value bits.
ASSERT_EQ(actual_return_value->value.is_null(),
expected_return_value->value.is_null());
if (!expected_return_value->value.is_null()) {
EXPECT_EQ(actual_return_value->value->bits,
expected_return_value->value->bits);
// Verify the external objects.
EXPECT_EQ(actual_return_value->value->external_objects.size(),
expected_return_value->value->external_objects.size());
}
// Verify the return value primary key and key path.
ASSERT_NO_FATAL_FAILURE(ExpectEqualsIndexedDBKey(
actual_return_value->primary_key, expected_return_value->primary_key));
EXPECT_EQ(expected_return_value->key_path, actual_return_value->key_path);
}
}
// Creates a `IDBReturnValuePtr` with the given bits. `primary_key`,
// `key_path` are optional, required only for object stores that generate keys.
// `external_objects` is not set and remains empty.
blink::mojom::IDBReturnValuePtr CreateIDBReturnValuePtr(
const std::string& bits,
const IndexedDBKey* primary_key = nullptr,
IndexedDBKeyPath key_path = {}) {
blink::mojom::IDBReturnValuePtr result = blink::mojom::IDBReturnValue::New();
result->value = blink::mojom::IDBValue::New();
result->value->bits.assign(bits.begin(), bits.end());
if (primary_key) {
result->primary_key = primary_key->Clone();
}
result->key_path = std::move(key_path);
return result;
}
} // namespace
class DatabaseTest : public ::testing::Test {
public:
DatabaseTest() = default;
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>(
/*is_incognito=*/false, temp_dir_.GetPath(),
base::SingleThreadTaskRunner::GetCurrentDefault(),
/*special_storage_policy=*/nullptr);
quota_manager_proxy_ = base::MakeRefCounted<storage::MockQuotaManagerProxy>(
quota_manager_.get(),
base::SingleThreadTaskRunner::GetCurrentDefault().get());
BucketContext::Delegate delegate;
delegate.on_ready_for_destruction =
base::BindOnce(&DatabaseTest::OnBucketContextReadyForDestruction,
weak_factory_.GetWeakPtr());
mojo::PendingRemote<storage::mojom::FileSystemAccessContext> fsa_context;
file_system_access_context_ =
std::make_unique<test::MockFileSystemAccessContext>();
file_system_access_context_->Clone(
fsa_context.InitWithNewPipeAndPassReceiver());
bucket_context_ = std::make_unique<BucketContext>(
storage::BucketInfo(), temp_dir_.GetPath(), std::move(delegate),
scoped_refptr<base::UpdateableSequencedTaskRunner>(),
quota_manager_proxy_,
/*blob_storage_context=*/mojo::NullRemote(),
/*file_system_access_context=*/std::move(fsa_context));
bucket_context_->InitBackingStoreIfNeeded(true);
db_ = bucket_context_->CreateAndAddDatabase(u"db");
}
void TearDown() override { db_ = nullptr; }
void OnBucketContextReadyForDestruction() { bucket_context_.reset(); }
void RunPostedTasks() {
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
protected:
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir temp_dir_;
std::unique_ptr<BucketContext> bucket_context_;
std::unique_ptr<test::MockFileSystemAccessContext>
file_system_access_context_;
scoped_refptr<storage::MockQuotaManager> quota_manager_;
scoped_refptr<storage::MockQuotaManagerProxy> quota_manager_proxy_;
// As this is owned by `bucket_context_`, tests that cause the database to
// be destroyed must manually reset this to null to avoid triggering dangling
// pointer warnings.
raw_ptr<Database> db_ = nullptr;
base::WeakPtrFactory<DatabaseTest> weak_factory_{this};
};
TEST_F(DatabaseTest, ConnectionLifecycle) {
MockMojoDatabaseCallbacks database_callbacks;
MockFactoryClient request1;
const int64_t transaction_id1 = 1;
auto connection1 = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<DatabaseCallbacks>(
database_callbacks.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection1));
RunPostedTasks();
MockMojoDatabaseCallbacks database_callbacks2;
MockFactoryClient request2;
const int64_t transaction_id2 = 2;
auto connection2 = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request2),
std::make_unique<DatabaseCallbacks>(
database_callbacks2.BindNewEndpointAndPassDedicatedRemote()),
transaction_id2, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection2));
RunPostedTasks();
db_ = nullptr;
EXPECT_TRUE(request1.connection());
request1.connection()->CloseAndReportForceClose(kTestForceCloseMessage);
EXPECT_FALSE(request1.connection()->IsConnected());
EXPECT_TRUE(request2.connection());
request2.connection()->CloseAndReportForceClose(kTestForceCloseMessage);
EXPECT_FALSE(request2.connection()->IsConnected());
RunPostedTasks();
EXPECT_TRUE(bucket_context_->GetDatabasesForTesting().empty());
}
TEST_F(DatabaseTest, ForcedClose) {
MockMojoDatabaseCallbacks database_callbacks;
MockFactoryClient request;
const int64_t upgrade_transaction_id = 3;
auto connection = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request),
std::make_unique<DatabaseCallbacks>(
database_callbacks.BindNewEndpointAndPassDedicatedRemote()),
upgrade_transaction_id, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection));
RunPostedTasks();
EXPECT_EQ(db_, request.connection()->database().get());
request.connection()->CreateTransaction(
mojo::NullAssociatedReceiver(), /*transaction_id=*/123,
/*object_store_ids=*/{}, blink::mojom::IDBTransactionMode::ReadOnly,
blink::mojom::IDBTransactionDurability::Relaxed);
db_ = nullptr;
base::RunLoop run_loop;
EXPECT_CALL(database_callbacks, ForcedClose)
.WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
request.connection()->CloseAndReportForceClose(kTestForceCloseMessage);
run_loop.Run();
}
namespace {
class FakeFactoryClient : public FactoryClient {
public:
FakeFactoryClient() : FactoryClient(mojo::NullAssociatedRemote()) {}
~FakeFactoryClient() override = default;
FakeFactoryClient(const FakeFactoryClient&) = delete;
FakeFactoryClient& operator=(const FakeFactoryClient&) = delete;
void OnBlocked(int64_t existing_version) override { blocked_called_ = true; }
void OnDeleteSuccess(int64_t old_version) override { success_called_ = true; }
void OnError(const DatabaseError& error) override { error_called_ = true; }
bool blocked_called() const { return blocked_called_; }
bool success_called() const { return success_called_; }
bool error_called() const { return error_called_; }
private:
bool blocked_called_ = false;
bool success_called_ = false;
bool error_called_ = false;
};
} // namespace
TEST_F(DatabaseTest, PendingDelete) {
MockFactoryClient request1;
const int64_t transaction_id1 = 1;
MockMojoDatabaseCallbacks database_callbacks1;
auto connection = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<DatabaseCallbacks>(
database_callbacks1.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection));
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
base::RunLoop run_loop;
FakeFactoryClient request2;
db_->ScheduleDeleteDatabase(std::make_unique<ThunkFactoryClient>(request2),
run_loop.QuitClosure());
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
EXPECT_FALSE(request2.blocked_called());
request1.connection()->VersionChangeIgnored();
EXPECT_TRUE(request2.blocked_called());
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
db_->ForceCloseAndRunTasks(kTestForceCloseMessage);
db_ = nullptr;
run_loop.Run();
EXPECT_FALSE(db_);
EXPECT_TRUE(request2.success_called());
}
TEST_F(DatabaseTest, OpenDeleteClear) {
const int64_t kDatabaseVersion = 1;
MockFactoryClient request1(
/*expect_connection=*/true);
MockMojoDatabaseCallbacks database_callbacks1;
const int64_t transaction_id1 = 1;
auto connection1 = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<DatabaseCallbacks>(
database_callbacks1.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, kDatabaseVersion, mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection1));
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
MockFactoryClient request2(
/*expect_connection=*/false);
MockMojoDatabaseCallbacks database_callbacks2;
const int64_t transaction_id2 = 2;
auto connection2 = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request2),
std::make_unique<DatabaseCallbacks>(
database_callbacks2.BindNewEndpointAndPassDedicatedRemote()),
transaction_id2, kDatabaseVersion, mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection2));
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 1UL);
MockFactoryClient request3(
/*expect_connection=*/false);
MockMojoDatabaseCallbacks database_callbacks3;
const int64_t transaction_id3 = 3;
auto connection3 = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request3),
std::make_unique<DatabaseCallbacks>(
database_callbacks3.BindNewEndpointAndPassDedicatedRemote()),
transaction_id3, kDatabaseVersion, mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection3));
RunPostedTasks();
EXPECT_TRUE(request1.upgrade_called());
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 2UL);
EXPECT_CALL(database_callbacks1, ForcedClose);
EXPECT_CALL(database_callbacks2, ForcedClose);
EXPECT_CALL(database_callbacks3, ForcedClose);
db_->ForceCloseAndRunTasks(kTestForceCloseMessage);
db_ = nullptr;
database_callbacks1.FlushForTesting();
EXPECT_TRUE(request1.error_called());
EXPECT_TRUE(request2.error_called());
EXPECT_TRUE(request3.error_called());
}
TEST_F(DatabaseTest, ForceDelete) {
MockFactoryClient request1;
MockMojoDatabaseCallbacks database_callbacks;
const int64_t transaction_id1 = 1;
auto connection = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<DatabaseCallbacks>(
database_callbacks.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection));
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
base::RunLoop run_loop;
FakeFactoryClient request2;
db_->ScheduleDeleteDatabase(std::make_unique<ThunkFactoryClient>(request2),
run_loop.QuitClosure());
RunPostedTasks();
EXPECT_FALSE(run_loop.AnyQuitCalled());
db_->ForceCloseAndRunTasks(kTestForceCloseMessage);
db_ = nullptr;
run_loop.Run();
EXPECT_FALSE(db_);
EXPECT_FALSE(request2.blocked_called());
EXPECT_TRUE(request2.success_called());
}
TEST_F(DatabaseTest, ForceCloseWhileOpenPending) {
// Verify that pending connection requests are handled correctly during a
// ForceClose.
MockFactoryClient request1;
MockMojoDatabaseCallbacks database_callbacks1;
const int64_t transaction_id1 = 1;
auto connection1 = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<DatabaseCallbacks>(
database_callbacks1.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection1));
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
MockFactoryClient request2(/*expect_connection=*/false);
MockMojoDatabaseCallbacks database_callbacks2;
const int64_t transaction_id2 = 2;
auto connection2 = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request2),
std::make_unique<DatabaseCallbacks>(
database_callbacks2.BindNewEndpointAndPassDedicatedRemote()),
transaction_id2, 3, mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection2));
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
db_->ForceCloseAndRunTasks(kTestForceCloseMessage);
db_ = nullptr;
RunPostedTasks();
EXPECT_FALSE(db_);
}
TEST_F(DatabaseTest, ForceCloseWhileOpenAndDeletePending) {
// Verify that pending connection requests are handled correctly during a
// ForceClose.
MockFactoryClient request1;
MockMojoDatabaseCallbacks database_callbacks1;
const int64_t transaction_id1 = 1;
auto connection1 = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<DatabaseCallbacks>(
database_callbacks1.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection1));
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
MockFactoryClient request2(false);
MockMojoDatabaseCallbacks database_callbacks2;
const int64_t transaction_id2 = 2;
auto connection2 = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request2),
std::make_unique<DatabaseCallbacks>(
database_callbacks2.BindNewEndpointAndPassDedicatedRemote()),
transaction_id2, 3, mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection2));
RunPostedTasks();
base::RunLoop run_loop;
auto request3 = std::make_unique<FakeFactoryClient>();
db_->ScheduleDeleteDatabase(std::move(request3), run_loop.QuitClosure());
RunPostedTasks();
EXPECT_FALSE(run_loop.AnyQuitCalled());
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 1UL);
db_->ForceCloseAndRunTasks(kTestForceCloseMessage);
db_ = nullptr;
run_loop.Run();
}
Status DummyOperation(Transaction* transaction) {
return Status::OK();
}
class DatabaseOperationTest : public DatabaseTest {
public:
DatabaseOperationTest() = default;
DatabaseOperationTest(const DatabaseOperationTest&) = delete;
DatabaseOperationTest& operator=(const DatabaseOperationTest&) = delete;
void SetUp() override {
DatabaseTest::SetUp();
const int64_t transaction_id = 1;
auto connection = std::make_unique<PendingConnection>(
std::make_unique<ThunkFactoryClient>(request_),
std::make_unique<DatabaseCallbacks>(mojo::NullAssociatedRemote()),
transaction_id, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection));
RunPostedTasks();
EXPECT_EQ(IndexedDBDatabaseMetadata::NO_VERSION, db_->metadata().version);
EXPECT_TRUE(request_.connection());
transaction_ = request_.connection()->CreateVersionChangeTransaction(
transaction_id, /*scope=*/std::set<int64_t>(),
std::make_unique<FakeTransaction>(
commit_success_,
db_->backing_store_db()->CreateTransaction(
blink::mojom::IDBTransactionDurability::Relaxed,
blink::mojom::IDBTransactionMode::VersionChange)));
std::vector<PartitionedLockManager::PartitionedLockRequest> lock_requests =
db_->BuildLockRequestsForTransaction(
blink::mojom::IDBTransactionMode::VersionChange, /*scope=*/{});
db_->lock_manager().AcquireLocks(
std::move(lock_requests), *transaction_->mutable_locks_receiver(),
base::BindOnce(&Transaction::Start, transaction_->AsWeakPtr()));
// Add a dummy task which takes the place of the VersionChangeOperation
// which kicks off the upgrade. This ensures that the transaction has
// processed at least one task before the CreateObjectStore call.
transaction_->ScheduleTask(base::BindOnce(&DummyOperation));
// Run posted tasks to execute the dummy operation and ensure that it is
// stored in the connection.
RunPostedTasks();
}
// Populates an object store and optionally an index with `database_records`.
// After setup, calls `Database::GetAllOperation` with `get_all_parameters`.
// Verifies that the results match `expected_results`.
void TestGetAll(
TestDatabaseParameters database_parameters,
TestGetAllParameters get_all_parameters,
base::span<const blink::mojom::IDBRecordPtr> expected_results) {
// Create the object store.
ASSERT_EQ(0u, db_->metadata().object_stores.size());
const auto& object_store_parameters =
database_parameters.object_store_parameters;
const int64_t store_id = object_store_parameters.object_store_id;
Status status = transaction_->BackingStoreTransaction()->CreateObjectStore(
store_id, object_store_parameters.name,
object_store_parameters.key_path,
object_store_parameters.auto_increment);
EXPECT_TRUE(status.ok()) << status.ToString();
EXPECT_EQ(1u, db_->metadata().object_stores.size());
// Optionally, create an index when the test provides a valid index id.
const blink::IndexedDBIndexMetadata index(
database_parameters.index_parameters.name,
database_parameters.index_parameters.index_id,
database_parameters.index_parameters.key_path,
database_parameters.index_parameters.unique,
database_parameters.index_parameters.multi_entry);
const int64_t index_id = index.id;
const bool has_index =
index_id != blink::IndexedDBIndexMetadata::kInvalidId;
if (has_index) {
status =
transaction_->BackingStoreTransaction()->CreateIndex(store_id, index);
}
EXPECT_TRUE(status.ok()) << status.ToString();
// Populate the object store and optionally the index with the provided
// records.
for (TestIDBRecord& record : database_parameters.records) {
std::vector<IndexedDBIndexKeys> index_keys;
ASSERT_EQ(record.index_key.has_value(), has_index);
if (has_index) {
IndexedDBIndexKeys index_key{index_id, {}};
index_key.keys.emplace_back(std::move(*record.index_key));
index_keys.emplace_back(std::move(index_key));
}
testing::NiceMock<
base::MockCallback<blink::mojom::IDBTransaction::PutCallback>>
callback;
// Set in-flight memory to a reasonably large number to prevent underflow
// in `PutOperation`
transaction_->in_flight_memory_ += 1000;
status = transaction_->DoPut(
store_id, record.value.Clone(), std::move(record.primary_key),
blink::mojom::IDBPutMode::AddOnly, std::move(index_keys),
callback.Get(), transaction_);
EXPECT_TRUE(status.ok()) << status.ToString();
}
// Call `Database::GetAllOperation` with the provided parameters.
FakeGetAllResultSink result_sink;
blink::mojom::IDBDatabase::GetAllCallback get_all_callback = base::BindOnce(
&FakeGetAllResultSink::BindReceiver, base::Unretained(&result_sink));
std::unique_ptr<Database::GetAllResultSinkWrapper> result_sink_wrapper =
std::make_unique<Database::GetAllResultSinkWrapper>(
transaction_->AsWeakPtr(), std::move(get_all_callback));
result_sink_wrapper->UseDedicatedReceiverForTesting();
status = db_->GetAllOperation(
store_id, index_id, std::move(get_all_parameters.key_range),
get_all_parameters.result_type, get_all_parameters.max_count,
get_all_parameters.direction, std::move(result_sink_wrapper),
transaction_);
EXPECT_TRUE(status.ok()) << status.ToString();
result_sink.WaitForResults();
EXPECT_EQ(result_sink.GetError(), nullptr);
// Verify that the actual results match the expected results.
const std::vector<blink::mojom::IDBRecordPtr>& actual_results =
result_sink.GetResults();
ASSERT_EQ(actual_results.size(), expected_results.size());
for (size_t i = 0u; i < expected_results.size(); ++i) {
ASSERT_FALSE(actual_results[i].is_null());
// Verify the primary key.
ASSERT_NO_FATAL_FAILURE(ExpectEqualsOptionalIndexedDBKey(
expected_results[i]->primary_key, actual_results[i]->primary_key));
// Verify the record value.
ASSERT_NO_FATAL_FAILURE(ExpectEqualsIDBReturnValuePtr(
expected_results[i]->return_value, actual_results[i]->return_value));
// Verify the index key.
ASSERT_NO_FATAL_FAILURE(ExpectEqualsOptionalIndexedDBKey(
expected_results[i]->index_key, actual_results[i]->index_key));
}
// Perform cleanup.
transaction_->SetCommitFlag();
transaction_ = nullptr;
RunPostedTasks();
// A transaction error would have resulted in a deleted db.
EXPECT_FALSE(bucket_context_->GetDatabasesForTesting().empty());
}
protected:
MockFactoryClient request_;
// As this is owned by `Connection`, tests that cause the transaction
// to be committed must manually reset this to null to avoid triggering
// dangling pointer warnings.
raw_ptr<Transaction> transaction_ = nullptr;
Status commit_success_;
};
TEST_F(DatabaseOperationTest, CreateObjectStore) {
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
const int64_t store_id = 1001;
Status s = transaction_->BackingStoreTransaction()->CreateObjectStore(
store_id, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false);
EXPECT_TRUE(s.ok());
transaction_->SetCommitFlag();
transaction_ = nullptr;
RunPostedTasks();
EXPECT_TRUE(bucket_context_);
EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
}
TEST_F(DatabaseOperationTest, CreateIndex) {
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
const int64_t store_id = 1001;
Status s = transaction_->BackingStoreTransaction()->CreateObjectStore(
store_id, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false);
EXPECT_TRUE(s.ok());
EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
const int64_t index_id = 2002;
s = transaction_->BackingStoreTransaction()->CreateIndex(
store_id, blink::IndexedDBIndexMetadata(
u"index", index_id, IndexedDBKeyPath(), /*unique=*/false,
/*multi_entry=*/false));
EXPECT_TRUE(s.ok());
EXPECT_EQ(
1ULL,
db_->metadata().object_stores.find(store_id)->second.indexes.size());
transaction_->SetCommitFlag();
transaction_ = nullptr;
RunPostedTasks();
EXPECT_TRUE(bucket_context_);
EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
EXPECT_EQ(
1ULL,
db_->metadata().object_stores.find(store_id)->second.indexes.size());
}
class DatabaseOperationAbortTest : public DatabaseOperationTest {
public:
DatabaseOperationAbortTest() {
commit_success_ = Status::NotFound("Bummer.");
}
DatabaseOperationAbortTest(const DatabaseOperationAbortTest&) = delete;
DatabaseOperationAbortTest& operator=(const DatabaseOperationAbortTest&) =
delete;
};
TEST_F(DatabaseOperationAbortTest, CreateObjectStore) {
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
const int64_t store_id = 1001;
Status s = transaction_->BackingStoreTransaction()->CreateObjectStore(
store_id, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false);
EXPECT_TRUE(s.ok());
EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
db_ = nullptr;
transaction_->SetCommitFlag();
RunPostedTasks();
// A transaction error results in a deleted db.
EXPECT_TRUE(bucket_context_->GetDatabasesForTesting().empty());
}
TEST_F(DatabaseOperationAbortTest, CreateIndex) {
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
const int64_t store_id = 1001;
Status s = transaction_->BackingStoreTransaction()->CreateObjectStore(
store_id, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false);
EXPECT_TRUE(s.ok());
EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
const int64_t index_id = 2002;
s = transaction_->BackingStoreTransaction()->CreateIndex(
store_id, blink::IndexedDBIndexMetadata(
u"index", index_id, IndexedDBKeyPath(), /*unique=*/false,
/*multi_entry=*/false));
EXPECT_TRUE(s.ok());
EXPECT_EQ(
1ULL,
db_->metadata().object_stores.find(store_id)->second.indexes.size());
db_ = nullptr;
transaction_->SetCommitFlag();
RunPostedTasks();
// A transaction error results in a deleted db.
EXPECT_TRUE(bucket_context_->GetDatabasesForTesting().empty());
}
TEST_F(DatabaseOperationTest, CreatePutDelete) {
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
const int64_t store_id = 1001;
Status s = transaction_->BackingStoreTransaction()->CreateObjectStore(
store_id, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false);
EXPECT_TRUE(s.ok());
EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
base::MockCallback<blink::mojom::IDBTransaction::PutCallback> callback;
// Set in-flight memory to a reasonably large number to prevent underflow in
// `PutOperation`
transaction_->in_flight_memory_ += 1000;
s = transaction_->DoPut(
store_id, IndexedDBValue("value1", {}), IndexedDBKey("key"),
blink::mojom::IDBPutMode::AddOnly, std::vector<IndexedDBIndexKeys>(),
callback.Get(), transaction_);
EXPECT_TRUE(s.ok());
s = transaction_->BackingStoreTransaction()->DeleteObjectStore(store_id);
EXPECT_TRUE(s.ok());
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
transaction_->SetCommitFlag();
transaction_ = nullptr;
RunPostedTasks();
// A transaction error would have resulted in a deleted db.
EXPECT_FALSE(bucket_context_->GetDatabasesForTesting().empty());
EXPECT_TRUE(s.ok());
}
TEST_F(DatabaseOperationTest, ObjectStoreGetAllKeysWhenEmpty) {
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/{},
/*database_records=*/{},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Keys,
},
/*expected_results=*/{}));
}
TEST_F(DatabaseOperationTest, IndexGetAllValuesWhenEmpty) {
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/
{
.index_id = kTestIndexId,
},
/*database_records=*/{},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Values,
},
/*expected_results=*/{}));
}
TEST_F(DatabaseOperationTest, ObjectStoreGetAllKeys) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(IndexedDBKey{"key1"},
/*value=*/nullptr,
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(IndexedDBKey{"key2"},
/*value=*/nullptr,
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(IndexedDBKey{"key3"},
/*value=*/nullptr,
/*index_key=*/std::nullopt),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/{},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Keys,
},
expected_results));
}
TEST_F(DatabaseOperationTest, ObjectStoreGetAllValues) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(
/*primary_key=*/std::nullopt,
/*value=*/CreateIDBReturnValuePtr("value1"),
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(
/*primary_key=*/std::nullopt,
/*value=*/CreateIDBReturnValuePtr("value2"),
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(
/*primary_key=*/std::nullopt,
/*value=*/CreateIDBReturnValuePtr("value3"),
/*index_key=*/std::nullopt),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/{},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Values,
},
expected_results));
}
TEST_F(DatabaseOperationTest, ObjectStoreGetAllRecords) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(IndexedDBKey{"key1"},
/*value=*/CreateIDBReturnValuePtr("value1"),
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(IndexedDBKey{"key2"},
/*value=*/CreateIDBReturnValuePtr("value2"),
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(IndexedDBKey{"key3"},
/*value=*/CreateIDBReturnValuePtr("value3"),
/*index_key=*/std::nullopt),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/{},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Records,
},
expected_results));
}
TEST_F(DatabaseOperationTest, IndexGetAllKeys) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(IndexedDBKey{"key3"},
/*value=*/nullptr,
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(IndexedDBKey{"key2"},
/*value=*/nullptr,
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(IndexedDBKey{"key1"},
/*value=*/nullptr,
/*index_key=*/std::nullopt),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/
{
.index_id = kTestIndexId,
},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
IndexedDBKey("index_key3"),
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
IndexedDBKey("index_key2"),
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
IndexedDBKey("index_key1"),
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Keys,
},
expected_results));
}
TEST_F(DatabaseOperationTest, IndexGetAllValues) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(
/*primary_key=*/std::nullopt,
/*value=*/CreateIDBReturnValuePtr("value3"),
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(
/*primary_key=*/std::nullopt,
/*value=*/CreateIDBReturnValuePtr("value2"),
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(
/*primary_key=*/std::nullopt,
/*value=*/CreateIDBReturnValuePtr("value1"),
/*index_key=*/std::nullopt),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/
{
.index_id = kTestIndexId,
},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
IndexedDBKey("index_key3"),
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
IndexedDBKey("index_key2"),
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
IndexedDBKey("index_key1"),
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Values,
},
expected_results));
}
TEST_F(DatabaseOperationTest, IndexGetAllRecords) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(IndexedDBKey{"key3"},
/*value=*/CreateIDBReturnValuePtr("value3"),
IndexedDBKey{"index_key1"}),
blink::mojom::IDBRecord::New(IndexedDBKey{"key2"},
/*value=*/CreateIDBReturnValuePtr("value2"),
IndexedDBKey{"index_key2"}),
blink::mojom::IDBRecord::New(IndexedDBKey{"key1"},
/*value=*/CreateIDBReturnValuePtr("value1"),
IndexedDBKey{"index_key3"}),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/
{
.index_id = kTestIndexId,
},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
IndexedDBKey("index_key3"),
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
IndexedDBKey("index_key2"),
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
IndexedDBKey("index_key1"),
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Records,
},
expected_results));
}
TEST_F(DatabaseOperationTest, ObjectStoreGetAllKeysWithRange) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(IndexedDBKey{"key2"},
/*value=*/nullptr,
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(IndexedDBKey{"key3"},
/*value=*/nullptr,
/*index_key=*/std::nullopt),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/{},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Keys,
.key_range =
{
IndexedDBKey("key2"),
IndexedDBKey("key9"),
/*lower_open=*/false,
/*upper_open=*/false,
},
},
expected_results));
}
TEST_F(DatabaseOperationTest, ObjectStoreGetAllKeysWithRangeThatDoesNotExist) {
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/{},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Keys,
.key_range =
{
IndexedDBKey("key7"),
IndexedDBKey("key9"),
/*lower_open=*/false,
/*upper_open=*/false,
},
},
/*expected_results=*/{}));
}
TEST_F(DatabaseOperationTest, ObjectStoreGetAllKeysWithInvalidRange) {
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/{},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Keys,
.key_range =
{
IndexedDBKey("key9"),
IndexedDBKey("key7"),
/*lower_open=*/false,
/*upper_open=*/false,
},
},
/*expected_results=*/{}));
}
TEST_F(DatabaseOperationTest, ObjectStoreGetAllKeysWithMaxCount) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(IndexedDBKey{"key1"},
/*value=*/nullptr,
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(IndexedDBKey{"key2"},
/*value=*/nullptr,
/*index_key=*/std::nullopt),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/{},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Keys,
.max_count = 2,
},
expected_results));
}
TEST_F(DatabaseOperationTest, ObjectStoreGetAllRecordsWithPrevDirection) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(IndexedDBKey{"key3"},
/*value=*/CreateIDBReturnValuePtr("value3"),
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(IndexedDBKey{"key2"},
/*value=*/CreateIDBReturnValuePtr("value2"),
/*index_key=*/std::nullopt),
blink::mojom::IDBRecord::New(IndexedDBKey{"key1"},
/*value=*/CreateIDBReturnValuePtr("value1"),
/*index_key=*/std::nullopt),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/{},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
/*index_key=*/std::nullopt,
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Records,
.direction = blink::mojom::IDBCursorDirection::Prev,
},
expected_results));
}
TEST_F(DatabaseOperationTest, IndexGetAllRecordsWithNextNoDuplicateDirection) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(IndexedDBKey{"key1"},
/*value=*/CreateIDBReturnValuePtr("value1"),
IndexedDBKey{"index_key1"}),
blink::mojom::IDBRecord::New(IndexedDBKey{"key2"},
/*value=*/CreateIDBReturnValuePtr("value2"),
IndexedDBKey{"index_key2"}),
blink::mojom::IDBRecord::New(IndexedDBKey{"key4"},
/*value=*/CreateIDBReturnValuePtr("value4"),
IndexedDBKey{"index_key3"}),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/
{
.index_id = kTestIndexId,
},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
IndexedDBKey("index_key1"),
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
IndexedDBKey("index_key2"),
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
IndexedDBKey("index_key2"),
},
{
IndexedDBKey{"key4"},
{"value4", /*external_objects=*/{}},
IndexedDBKey("index_key3"),
},
{
IndexedDBKey{"key5"},
{"value5", /*external_objects=*/{}},
IndexedDBKey("index_key3"),
},
{
IndexedDBKey{"key6"},
{"value6", /*external_objects=*/{}},
IndexedDBKey("index_key3"),
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Records,
.direction = blink::mojom::IDBCursorDirection::NextNoDuplicate,
},
expected_results));
}
TEST_F(DatabaseOperationTest, IndexGetAllRecordsWithPrevNoDuplicateDirection) {
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(IndexedDBKey{"key4"},
/*value=*/CreateIDBReturnValuePtr("value4"),
IndexedDBKey{"index_key3"}),
blink::mojom::IDBRecord::New(IndexedDBKey{"key2"},
/*value=*/CreateIDBReturnValuePtr("value2"),
IndexedDBKey{"index_key2"}),
blink::mojom::IDBRecord::New(IndexedDBKey{"key1"},
/*value=*/CreateIDBReturnValuePtr("value1"),
IndexedDBKey{"index_key1"}),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/
{
.index_id = kTestIndexId,
},
/*database_records=*/
{
{
IndexedDBKey{"key1"},
{"value1", /*external_objects=*/{}},
IndexedDBKey("index_key1"),
},
{
IndexedDBKey{"key2"},
{"value2", /*external_objects=*/{}},
IndexedDBKey("index_key2"),
},
{
IndexedDBKey{"key3"},
{"value3", /*external_objects=*/{}},
IndexedDBKey("index_key2"),
},
{
IndexedDBKey{"key4"},
{"value4", /*external_objects=*/{}},
IndexedDBKey("index_key3"),
},
{
IndexedDBKey{"key5"},
{"value5", /*external_objects=*/{}},
IndexedDBKey("index_key3"),
},
{
IndexedDBKey{"key6"},
{"value6", /*external_objects=*/{}},
IndexedDBKey("index_key3"),
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Records,
.direction = blink::mojom::IDBCursorDirection::PrevNoDuplicate,
},
expected_results));
}
TEST_F(DatabaseOperationTest, ObjectStoreGetAllKeysWithInvalidObjectStoreId) {
ASSERT_EQ(0u, db_->metadata().object_stores.size());
// Call `Database::GetAllOperation` with an invalid object store id, which
// must fail with an invalid argument status.
FakeGetAllResultSink result_sink;
blink::mojom::IDBDatabase::GetAllCallback get_all_callback = base::BindOnce(
&FakeGetAllResultSink::BindReceiver, base::Unretained(&result_sink));
std::unique_ptr<Database::GetAllResultSinkWrapper> result_sink_wrapper =
std::make_unique<Database::GetAllResultSinkWrapper>(
transaction_->AsWeakPtr(), std::move(get_all_callback));
result_sink_wrapper->UseDedicatedReceiverForTesting();
TestGetAllParameters get_all_parameters;
Status status = db_->GetAllOperation(
kTestObjectStoreId,
/*index_id=*/blink::IndexedDBIndexMetadata::kInvalidId,
std::move(get_all_parameters.key_range), get_all_parameters.result_type,
get_all_parameters.max_count, get_all_parameters.direction,
std::move(result_sink_wrapper), transaction_);
ASSERT_TRUE(status.IsInvalidArgument()) << status.ToString();
// Verify that the result sink received an error.
result_sink.WaitForResults();
ASSERT_NE(result_sink.GetError(), nullptr);
EXPECT_EQ(result_sink.GetError()->error_code,
blink::mojom::IDBException::kUnknownError);
// Perform cleanup.
transaction_->SetCommitFlag();
transaction_ = nullptr;
RunPostedTasks();
}
TEST_F(DatabaseOperationTest, IndexGetAllKeysWithInvalidIndexId) {
// Create an object store.
ASSERT_EQ(0u, db_->metadata().object_stores.size());
Status status = transaction_->BackingStoreTransaction()->CreateObjectStore(
kTestObjectStoreId, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false);
ASSERT_TRUE(status.ok()) << status.ToString();
ASSERT_EQ(1u, db_->metadata().object_stores.size());
// Call `Database::GetAllOperation` with an invalid index id, which must fail
// with an invalid argument status.
FakeGetAllResultSink result_sink;
blink::mojom::IDBDatabase::GetAllCallback get_all_callback = base::BindOnce(
&FakeGetAllResultSink::BindReceiver, base::Unretained(&result_sink));
std::unique_ptr<Database::GetAllResultSinkWrapper> result_sink_wrapper =
std::make_unique<Database::GetAllResultSinkWrapper>(
transaction_->AsWeakPtr(), std::move(get_all_callback));
result_sink_wrapper->UseDedicatedReceiverForTesting();
TestGetAllParameters get_all_parameters;
status = db_->GetAllOperation(
kTestObjectStoreId, kTestIndexId, std::move(get_all_parameters.key_range),
get_all_parameters.result_type, get_all_parameters.max_count,
get_all_parameters.direction, std::move(result_sink_wrapper),
transaction_);
ASSERT_TRUE(status.IsInvalidArgument()) << status.ToString();
// Verify that the result sink received an error.
result_sink.WaitForResults();
ASSERT_NE(result_sink.GetError(), nullptr);
EXPECT_EQ(result_sink.GetError()->error_code,
blink::mojom::IDBException::kUnknownError);
// Perform cleanup.
transaction_->SetCommitFlag();
transaction_ = nullptr;
RunPostedTasks();
}
TEST_F(DatabaseOperationTest,
ObjectStoreGetAllRecordsWithMultipleResultChunks) {
// Generate 2.5 chunks of results.
const size_t record_count = (blink::mojom::kIDBGetAllChunkSize * 2) +
(blink::mojom::kIDBGetAllChunkSize / 2);
std::vector<TestIDBRecord> database_records;
std::vector<blink::mojom::IDBRecordPtr> expected_results;
for (size_t i = 0u; i < record_count; ++i) {
const std::string primary_key = base::StringPrintf("key%zu", i);
const std::string value = base::StringPrintf("value%zu", i);
database_records.emplace_back(IndexedDBKey{primary_key},
IndexedDBValue{value, {}},
/*index_key=*/std::nullopt);
expected_results.emplace_back(
blink::mojom::IDBRecord::New(IndexedDBKey{primary_key},
/*value=*/CreateIDBReturnValuePtr(value),
/*index_key=*/std::nullopt));
}
// Sort the expected results by primary key.
std::sort(expected_results.begin(), expected_results.end(),
[](const blink::mojom::IDBRecordPtr& left,
const blink::mojom::IDBRecordPtr& right) {
return left->primary_key->IsLessThan(*right->primary_key);
});
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
},
/*index_parameters=*/{},
std::move(database_records),
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Records,
},
expected_results));
}
TEST_F(DatabaseOperationTest, IndexGetAllRecordsWithAutoIncrementingKeys) {
const IndexedDBKeyPath object_store_key_path{u"id"};
const auto expected_generated_keys = std::to_array<IndexedDBKey>({
IndexedDBKey(1.0, blink::mojom::IDBKeyType::Number),
IndexedDBKey(2.0, blink::mojom::IDBKeyType::Number),
IndexedDBKey(3.0, blink::mojom::IDBKeyType::Number),
});
const blink::mojom::IDBRecordPtr expected_results[] = {
blink::mojom::IDBRecord::New(
/*primary_key=*/expected_generated_keys[2].Clone(),
/*value=*/
CreateIDBReturnValuePtr("value3", &expected_generated_keys[2],
object_store_key_path),
/*index_key=*/IndexedDBKey{"index_key1"}),
blink::mojom::IDBRecord::New(
/*primary_key=*/expected_generated_keys[1].Clone(),
/*value=*/
CreateIDBReturnValuePtr("value2", &expected_generated_keys[1],
object_store_key_path),
/*index_key=*/IndexedDBKey{"index_key2"}),
blink::mojom::IDBRecord::New(
/*primary_key=*/expected_generated_keys[0].Clone(),
/*value=*/
CreateIDBReturnValuePtr("value1", &expected_generated_keys[0],
object_store_key_path),
/*index_key=*/IndexedDBKey{"index_key3"}),
};
ASSERT_NO_FATAL_FAILURE(TestGetAll(
/*database_parameters=*/
{
/*object_store_parameters=*/
{
.object_store_id = kTestObjectStoreId,
.key_path = object_store_key_path,
.auto_increment = true,
},
/*index_parameters=*/
{
.index_id = kTestIndexId,
},
/*database_records=*/
{
{
/*primary_key (generated)=*/IndexedDBKey(),
{"value1", /*external_objects=*/{}},
IndexedDBKey("index_key3"),
},
{
/*primary_key (generated)=*/IndexedDBKey(),
{"value2", /*external_objects=*/{}},
IndexedDBKey("index_key2"),
},
{
/*primary_key (generated)=*/IndexedDBKey(),
{"value3", /*external_objects=*/{}},
IndexedDBKey("index_key1"),
},
},
},
/*get_all_parameters=*/
{
.result_type = blink::mojom::IDBGetAllResultType::Records,
},
expected_results));
}
} // namespace content::indexed_db