blob: f04bfc1cb6fb8676b9c25db5ff92f4aa2dea65ad [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// 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/indexed_db_backing_store.h"
#include <stddef.h>
#include <stdint.h>
#include <utility>
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/default_clock.h"
#include "content/browser/indexed_db/indexed_db_class_factory.h"
#include "content/browser/indexed_db/indexed_db_context_impl.h"
#include "content/browser/indexed_db/indexed_db_factory_impl.h"
#include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
#include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
#include "content/browser/indexed_db/indexed_db_metadata_coding.h"
#include "content/browser/indexed_db/indexed_db_value.h"
#include "content/browser/indexed_db/leveldb/leveldb_factory.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "net/url_request/url_request_test_util.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "storage/browser/test/mock_quota_manager_proxy.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/indexeddb/web_idb_types.h"
using base::ASCIIToUTF16;
using blink::IndexedDBKey;
using url::Origin;
namespace content {
namespace indexed_db_backing_store_unittest {
static const size_t kDefaultMaxOpenIteratorsPerDatabase = 50;
// Write |content| to |file|. Returns true on success.
bool WriteFile(const base::FilePath& file, base::StringPiece content) {
int write_size = base::WriteFile(file, content.data(), content.length());
return write_size >= 0 && write_size == static_cast<int>(content.length());
}
class Comparator : public LevelDBComparator {
public:
int Compare(const base::StringPiece& a,
const base::StringPiece& b) const override {
return content::Compare(a, b, false /*index_keys*/);
}
const char* Name() const override { return "idb_cmp1"; }
};
class DefaultLevelDBFactory : public LevelDBFactory {
public:
DefaultLevelDBFactory() {}
leveldb::Status OpenLevelDB(const base::FilePath& file_name,
const LevelDBComparator* comparator,
std::unique_ptr<LevelDBDatabase>* db,
bool* is_disk_full) override {
return LevelDBDatabase::Open(file_name, comparator,
kDefaultMaxOpenIteratorsPerDatabase, db,
is_disk_full);
}
leveldb::Status DestroyLevelDB(const base::FilePath& file_name) override {
return LevelDBDatabase::Destroy(file_name);
}
private:
DISALLOW_COPY_AND_ASSIGN(DefaultLevelDBFactory);
};
class TestableIndexedDBBackingStore : public IndexedDBBackingStore {
public:
static scoped_refptr<TestableIndexedDBBackingStore> Open(
IndexedDBFactory* indexed_db_factory,
const Origin& origin,
const base::FilePath& path_base,
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
LevelDBFactory* leveldb_factory,
base::SequencedTaskRunner* task_runner,
leveldb::Status* status) {
DCHECK(!path_base.empty());
std::unique_ptr<LevelDBComparator> comparator =
std::make_unique<Comparator>();
if (!base::CreateDirectory(path_base)) {
*status = leveldb::Status::IOError("Unable to create base dir");
return scoped_refptr<TestableIndexedDBBackingStore>();
}
const base::FilePath file_path = path_base.AppendASCII("test_db_path");
const base::FilePath blob_path = path_base.AppendASCII("test_blob_path");
std::unique_ptr<LevelDBDatabase> db;
bool is_disk_full = false;
*status = leveldb_factory->OpenLevelDB(
file_path, comparator.get(), &db, &is_disk_full);
if (!db || !status->ok())
return scoped_refptr<TestableIndexedDBBackingStore>();
scoped_refptr<TestableIndexedDBBackingStore> backing_store(
new TestableIndexedDBBackingStore(indexed_db_factory, origin, blob_path,
request_context_getter, std::move(db),
std::move(comparator), task_runner));
*status = backing_store->SetUpMetadata();
if (!status->ok())
return scoped_refptr<TestableIndexedDBBackingStore>();
return backing_store;
}
const std::vector<IndexedDBBackingStore::Transaction::WriteDescriptor>&
writes() const {
return writes_;
}
void ClearWrites() { writes_.clear(); }
const std::vector<int64_t>& removals() const { return removals_; }
void ClearRemovals() { removals_.clear(); }
void StartJournalCleaningTimer() override {
IndexedDBBackingStore::StartJournalCleaningTimer();
}
protected:
~TestableIndexedDBBackingStore() override {}
bool WriteBlobFile(
int64_t database_id,
const Transaction::WriteDescriptor& descriptor,
Transaction::ChainedBlobWriter* chained_blob_writer) override {
if (KeyPrefix::IsValidDatabaseId(database_id_)) {
if (database_id_ != database_id) {
return false;
}
} else {
database_id_ = database_id;
}
writes_.push_back(descriptor);
task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&Transaction::ChainedBlobWriter::ReportWriteCompletion,
chained_blob_writer, true, 1));
return true;
}
bool RemoveBlobFile(int64_t database_id, int64_t key) const override {
if (database_id_ != database_id ||
!KeyPrefix::IsValidDatabaseId(database_id)) {
return false;
}
removals_.push_back(key);
return true;
}
private:
TestableIndexedDBBackingStore(
IndexedDBFactory* indexed_db_factory,
const Origin& origin,
const base::FilePath& blob_path,
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
std::unique_ptr<LevelDBDatabase> db,
std::unique_ptr<LevelDBComparator> comparator,
base::SequencedTaskRunner* task_runner)
: IndexedDBBackingStore(indexed_db_factory,
origin,
blob_path,
request_context_getter,
std::move(db),
std::move(comparator),
task_runner),
database_id_(0) {}
int64_t database_id_;
std::vector<Transaction::WriteDescriptor> writes_;
// This is modified in an overridden virtual function that is properly const
// in the real implementation, therefore must be mutable here.
mutable std::vector<int64_t> removals_;
DISALLOW_COPY_AND_ASSIGN(TestableIndexedDBBackingStore);
};
class TestIDBFactory : public IndexedDBFactoryImpl {
public:
explicit TestIDBFactory(IndexedDBContextImpl* idb_context)
: IndexedDBFactoryImpl(idb_context, base::DefaultClock::GetInstance()) {}
scoped_refptr<TestableIndexedDBBackingStore> OpenBackingStoreForTest(
const Origin& origin,
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter) {
IndexedDBDataLossInfo data_loss_info;
bool disk_full;
leveldb::Status status;
scoped_refptr<IndexedDBBackingStore> backing_store = OpenBackingStore(
origin, context()->data_path(), url_request_context_getter,
&data_loss_info, &disk_full, &status);
scoped_refptr<TestableIndexedDBBackingStore> testable_store =
static_cast<TestableIndexedDBBackingStore*>(backing_store.get());
return testable_store;
}
protected:
~TestIDBFactory() override {}
scoped_refptr<IndexedDBBackingStore> OpenBackingStoreHelper(
const Origin& origin,
const base::FilePath& data_directory,
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
IndexedDBDataLossInfo* data_loss_info,
bool* disk_full,
bool first_time,
leveldb::Status* status) override {
DefaultLevelDBFactory leveldb_factory;
return TestableIndexedDBBackingStore::Open(
this, origin, data_directory, request_context_getter, &leveldb_factory,
context()->TaskRunner(), status);
}
private:
DISALLOW_COPY_AND_ASSIGN(TestIDBFactory);
};
class IndexedDBBackingStoreTest : public testing::Test {
public:
IndexedDBBackingStoreTest()
: url_request_context_getter_(
base::MakeRefCounted<net::TestURLRequestContextGetter>(
BrowserThread::GetTaskRunnerForThread(BrowserThread::UI))),
special_storage_policy_(
base::MakeRefCounted<MockSpecialStoragePolicy>()),
quota_manager_proxy_(
base::MakeRefCounted<MockQuotaManagerProxy>(nullptr, nullptr)) {}
void CreateFactoryAndBackingStore() {
// Factory and backing store must be created on IDB task runner.
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTest* test) {
const Origin origin = Origin::Create(GURL("http://localhost:81"));
test->idb_factory_ = base::MakeRefCounted<TestIDBFactory>(
test->idb_context_.get());
test->backing_store_ =
test->idb_factory_->OpenBackingStoreForTest(
origin, test->url_request_context_getter_);
},
base::Unretained(this)));
RunAllTasksUntilIdle();
}
void DestroyFactoryAndBackingStore() {
// Factory and backing store must be destroyed on IDB task runner.
idb_context_->TaskRunner()->PostTask(
FROM_HERE, base::BindOnce(
[](IndexedDBBackingStoreTest* test) {
test->idb_factory_ = nullptr;
test->backing_store_ = nullptr;
},
base::Unretained(this)));
RunAllTasksUntilIdle();
}
void SetUp() override {
special_storage_policy_->SetAllUnlimited(true);
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
idb_context_ = base::MakeRefCounted<IndexedDBContextImpl>(
temp_dir_.GetPath(), special_storage_policy_, quota_manager_proxy_);
CreateFactoryAndBackingStore();
// useful keys and values during tests
value1_ = IndexedDBValue("value1", std::vector<IndexedDBBlobInfo>());
value2_ = IndexedDBValue("value2", std::vector<IndexedDBBlobInfo>());
key1_ = IndexedDBKey(99, blink::kWebIDBKeyTypeNumber);
key2_ = IndexedDBKey(ASCIIToUTF16("key2"));
}
void TearDown() override {
DestroyFactoryAndBackingStore();
quota_manager_proxy_->SimulateQuotaManagerDestroyed();
}
TestableIndexedDBBackingStore* backing_store() const {
return backing_store_.get();
}
// Sample keys and values that are consistent. Public so that posted lambdas
// passed |this| can access them.
IndexedDBKey key1_;
IndexedDBKey key2_;
IndexedDBValue value1_;
IndexedDBValue value2_;
protected:
// Must be initialized before url_request_context_getter_
TestBrowserThreadBundle thread_bundle_;
base::ScopedTempDir temp_dir_;
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_;
scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_;
scoped_refptr<IndexedDBContextImpl> idb_context_;
scoped_refptr<TestIDBFactory> idb_factory_;
scoped_refptr<TestableIndexedDBBackingStore> backing_store_;
private:
DISALLOW_COPY_AND_ASSIGN(IndexedDBBackingStoreTest);
};
class IndexedDBBackingStoreTestWithBlobs : public IndexedDBBackingStoreTest {
public:
IndexedDBBackingStoreTestWithBlobs() {}
void SetUp() override {
IndexedDBBackingStoreTest::SetUp();
blob_context_ = std::make_unique<storage::BlobStorageContext>();
// useful keys and values during tests
blob_info_.push_back(
IndexedDBBlobInfo(CreateBlob(), base::UTF8ToUTF16("blob type"), 1));
blob_info_.push_back(IndexedDBBlobInfo(
CreateBlob(), base::FilePath(FILE_PATH_LITERAL("path/to/file")),
base::UTF8ToUTF16("file name"), base::UTF8ToUTF16("file type")));
blob_info_.push_back(IndexedDBBlobInfo(CreateBlob(), base::FilePath(),
base::UTF8ToUTF16("file name"),
base::UTF8ToUTF16("file type")));
value3_ = IndexedDBValue("value3", blob_info_);
key3_ = IndexedDBKey(ASCIIToUTF16("key3"));
}
std::unique_ptr<storage::BlobDataHandle> CreateBlob() {
return blob_context_->AddFinishedBlob(
std::make_unique<storage::BlobDataBuilder>(base::GenerateGUID()));
}
// This just checks the data that survive getting stored and recalled, e.g.
// the file path and UUID will change and thus aren't verified.
bool CheckBlobInfoMatches(const std::vector<IndexedDBBlobInfo>& reads) const {
DCHECK(idb_context_->TaskRunner()->RunsTasksInCurrentSequence());
if (blob_info_.size() != reads.size())
return false;
for (size_t i = 0; i < blob_info_.size(); ++i) {
const IndexedDBBlobInfo& a = blob_info_[i];
const IndexedDBBlobInfo& b = reads[i];
if (a.is_file() != b.is_file())
return false;
if (a.type() != b.type())
return false;
if (a.is_file()) {
if (a.file_name() != b.file_name())
return false;
} else {
if (a.size() != b.size())
return false;
}
}
return true;
}
bool CheckBlobReadsMatchWrites(
const std::vector<IndexedDBBlobInfo>& reads) const {
DCHECK(idb_context_->TaskRunner()->RunsTasksInCurrentSequence());
if (backing_store_->writes().size() != reads.size())
return false;
std::set<int64_t> ids;
for (const auto& write : backing_store_->writes())
ids.insert(write.key());
if (ids.size() != backing_store_->writes().size())
return false;
for (const auto& read : reads) {
if (ids.count(read.key()) != 1)
return false;
}
return true;
}
bool CheckBlobWrites() const {
DCHECK(idb_context_->TaskRunner()->RunsTasksInCurrentSequence());
if (backing_store_->writes().size() != blob_info_.size())
return false;
for (size_t i = 0; i < backing_store_->writes().size(); ++i) {
const IndexedDBBackingStore::Transaction::WriteDescriptor& desc =
backing_store_->writes()[i];
const IndexedDBBlobInfo& info = blob_info_[i];
if (desc.is_file() != info.is_file()) {
if (!info.is_file() || !info.file_path().empty())
return false;
} else if (desc.is_file()) {
if (desc.file_path() != info.file_path())
return false;
} else {
if (desc.blob()->uuid() != info.blob_handle()->uuid())
return false;
}
}
return true;
}
bool CheckBlobRemovals() const {
DCHECK(idb_context_->TaskRunner()->RunsTasksInCurrentSequence());
if (backing_store_->removals().size() != backing_store_->writes().size())
return false;
for (size_t i = 0; i < backing_store_->writes().size(); ++i) {
if (backing_store_->writes()[i].key() != backing_store_->removals()[i])
return false;
}
return true;
}
// Sample keys and values that are consistent. Public so that posted lambdas
// passed |this| can access them.
IndexedDBKey key3_;
IndexedDBValue value3_;
private:
std::unique_ptr<storage::BlobStorageContext> blob_context_;
// Blob details referenced by |value3_|. The various CheckBlob*() methods
// can be used to verify the state as a test progresses.
std::vector<IndexedDBBlobInfo> blob_info_;
DISALLOW_COPY_AND_ASSIGN(IndexedDBBackingStoreTestWithBlobs);
};
class TestCallback : public IndexedDBBackingStore::BlobWriteCallback {
public:
TestCallback() : called(false), succeeded(false) {}
leveldb::Status Run(IndexedDBBackingStore::BlobWriteResult result) override {
called = true;
switch (result) {
case IndexedDBBackingStore::BlobWriteResult::FAILURE_ASYNC:
succeeded = false;
break;
case IndexedDBBackingStore::BlobWriteResult::SUCCESS_ASYNC:
case IndexedDBBackingStore::BlobWriteResult::SUCCESS_SYNC:
succeeded = true;
break;
}
return leveldb::Status::OK();
}
bool called;
bool succeeded;
protected:
~TestCallback() override {}
private:
DISALLOW_COPY_AND_ASSIGN(TestCallback);
};
TEST_F(IndexedDBBackingStoreTest, PutGetConsistency) {
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStore* backing_store, IndexedDBKey key,
IndexedDBValue value) {
{
IndexedDBBackingStore::Transaction transaction1(backing_store);
transaction1.Begin();
IndexedDBBackingStore::RecordIdentifier record;
leveldb::Status s = backing_store->PutRecord(
&transaction1, 1, 1, key, &value, &record);
EXPECT_TRUE(s.ok());
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction1.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction1.CommitPhaseTwo().ok());
}
{
IndexedDBBackingStore::Transaction transaction2(backing_store);
transaction2.Begin();
IndexedDBValue result_value;
EXPECT_TRUE(
backing_store
->GetRecord(&transaction2, 1, 1, key, &result_value)
.ok());
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
EXPECT_EQ(value.bits, result_value.bits);
}
},
base::Unretained(backing_store()), key1_, value1_));
RunAllTasksUntilIdle();
}
TEST_F(IndexedDBBackingStoreTestWithBlobs, PutGetConsistencyWithBlobs) {
struct TestState {
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction1;
scoped_refptr<TestCallback> callback1;
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction3;
scoped_refptr<TestCallback> callback3;
} state;
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
// Initiate transaction1 - writing blobs.
state->transaction1 =
std::make_unique<IndexedDBBackingStore::Transaction>(
test->backing_store());
state->transaction1->Begin();
IndexedDBBackingStore::RecordIdentifier record;
EXPECT_TRUE(test->backing_store()
->PutRecord(state->transaction1.get(), 1, 1,
test->key3_, &test->value3_, &record)
.ok());
state->callback1 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction1->CommitPhaseOne(state->callback1).ok());
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
// Finish up transaction1, verifying blob writes.
EXPECT_TRUE(state->callback1->called);
EXPECT_TRUE(state->callback1->succeeded);
EXPECT_TRUE(test->CheckBlobWrites());
EXPECT_TRUE(state->transaction1->CommitPhaseTwo().ok());
// Initiate transaction2, reading blobs.
IndexedDBBackingStore::Transaction transaction2(
test->backing_store());
transaction2.Begin();
IndexedDBValue result_value;
EXPECT_TRUE(
test->backing_store()
->GetRecord(&transaction2, 1, 1, test->key3_, &result_value)
.ok());
// Finish up transaction2, verifying blob reads.
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
EXPECT_EQ(test->value3_.bits, result_value.bits);
EXPECT_TRUE(test->CheckBlobInfoMatches(result_value.blob_info));
EXPECT_TRUE(
test->CheckBlobReadsMatchWrites(result_value.blob_info));
// Initiate transaction3, deleting blobs.
state->transaction3 =
std::make_unique<IndexedDBBackingStore::Transaction>(
test->backing_store());
state->transaction3->Begin();
EXPECT_TRUE(test->backing_store()
->DeleteRange(state->transaction3.get(), 1, 1,
IndexedDBKeyRange(test->key3_))
.ok());
state->callback3 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction3->CommitPhaseOne(state->callback3).ok());
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
// Finish up transaction 3, verifying blob deletes.
EXPECT_TRUE(state->transaction3->CommitPhaseTwo().ok());
EXPECT_TRUE(test->CheckBlobRemovals());
// Clean up Transactions, etc on the IDB thread.
*state = TestState();
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
}
TEST_F(IndexedDBBackingStoreTestWithBlobs, DeleteRange) {
const std::vector<IndexedDBKey> keys = {
IndexedDBKey(ASCIIToUTF16("key0")), IndexedDBKey(ASCIIToUTF16("key1")),
IndexedDBKey(ASCIIToUTF16("key2")), IndexedDBKey(ASCIIToUTF16("key3"))};
const IndexedDBKeyRange ranges[] = {
IndexedDBKeyRange(keys[1], keys[2], false, false),
IndexedDBKeyRange(keys[1], keys[2], false, false),
IndexedDBKeyRange(keys[0], keys[2], true, false),
IndexedDBKeyRange(keys[1], keys[3], false, true),
IndexedDBKeyRange(keys[0], keys[3], true, true)};
for (size_t i = 0; i < arraysize(ranges); ++i) {
const int64_t database_id = 1;
const int64_t object_store_id = i + 1;
const IndexedDBKeyRange& range = ranges[i];
struct TestState {
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction1;
scoped_refptr<TestCallback> callback1;
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction2;
scoped_refptr<TestCallback> callback2;
std::vector<std::unique_ptr<storage::BlobDataHandle>> blobs;
} state;
for (size_t j = 0; j < 4; ++j)
state.blobs.push_back(CreateBlob());
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](TestableIndexedDBBackingStore* backing_store, TestState* state,
const std::vector<IndexedDBKey>& keys, int64_t database_id,
int64_t object_store_id) {
// Reset from previous iteration.
backing_store->ClearWrites();
backing_store->ClearRemovals();
std::vector<IndexedDBValue> values = {
IndexedDBValue("value0",
{IndexedDBBlobInfo(
std::make_unique<storage::BlobDataHandle>(
*state->blobs[0]),
base::UTF8ToUTF16("type 0"), 1)}),
IndexedDBValue("value1",
{IndexedDBBlobInfo(
std::make_unique<storage::BlobDataHandle>(
*state->blobs[1]),
base::UTF8ToUTF16("type 1"), 1)}),
IndexedDBValue("value2",
{IndexedDBBlobInfo(
std::make_unique<storage::BlobDataHandle>(
*state->blobs[2]),
base::UTF8ToUTF16("type 2"), 1)}),
IndexedDBValue("value3",
{IndexedDBBlobInfo(
std::make_unique<storage::BlobDataHandle>(
*state->blobs[3]),
base::UTF8ToUTF16("type 3"), 1)})};
ASSERT_GE(keys.size(), values.size());
// Initiate transaction1 - write records.
state->transaction1 =
std::make_unique<IndexedDBBackingStore::Transaction>(
backing_store);
state->transaction1->Begin();
IndexedDBBackingStore::RecordIdentifier record;
for (size_t i = 0; i < values.size(); ++i) {
EXPECT_TRUE(backing_store
->PutRecord(state->transaction1.get(),
database_id, object_store_id,
keys[i], &values[i], &record)
.ok());
}
// Start committing transaction1.
state->callback1 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction1->CommitPhaseOne(state->callback1).ok());
},
base::Unretained(backing_store()), base::Unretained(&state),
base::ConstRef(keys), database_id, object_store_id));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](TestableIndexedDBBackingStore* backing_store, TestState* state,
IndexedDBKeyRange range, int64_t database_id,
int64_t object_store_id) {
// Finish committing transaction1.
EXPECT_TRUE(state->callback1->called);
EXPECT_TRUE(state->callback1->succeeded);
EXPECT_TRUE(state->transaction1->CommitPhaseTwo().ok());
// Initiate transaction 2 - delete range.
state->transaction2 =
std::make_unique<IndexedDBBackingStore::Transaction>(
backing_store);
state->transaction2->Begin();
IndexedDBValue result_value;
EXPECT_TRUE(backing_store
->DeleteRange(state->transaction2.get(),
database_id, object_store_id, range)
.ok());
// Start committing transaction2.
state->callback2 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction2->CommitPhaseOne(state->callback2).ok());
},
base::Unretained(backing_store()), base::Unretained(&state), range,
database_id, object_store_id));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](TestableIndexedDBBackingStore* backing_store, TestState* state) {
// Finish committing transaction2.
EXPECT_TRUE(state->callback2->called);
EXPECT_TRUE(state->callback2->succeeded);
EXPECT_TRUE(state->transaction2->CommitPhaseTwo().ok());
// Verify blob removals.
ASSERT_EQ(2UL, backing_store->removals().size());
EXPECT_EQ(backing_store->writes()[1].key(),
backing_store->removals()[0]);
EXPECT_EQ(backing_store->writes()[2].key(),
backing_store->removals()[1]);
// Clean up Transactions, etc on the IDB thread.
*state = TestState();
},
base::Unretained(backing_store()), base::Unretained(&state)));
RunAllTasksUntilIdle();
}
}
TEST_F(IndexedDBBackingStoreTestWithBlobs, DeleteRangeEmptyRange) {
const std::vector<IndexedDBKey> keys = {
IndexedDBKey(ASCIIToUTF16("key0")), IndexedDBKey(ASCIIToUTF16("key1")),
IndexedDBKey(ASCIIToUTF16("key2")), IndexedDBKey(ASCIIToUTF16("key3")),
IndexedDBKey(ASCIIToUTF16("key4"))};
const IndexedDBKeyRange ranges[] = {
IndexedDBKeyRange(keys[3], keys[4], true, false),
IndexedDBKeyRange(keys[2], keys[1], false, false),
IndexedDBKeyRange(keys[2], keys[1], true, true)};
for (size_t i = 0; i < arraysize(ranges); ++i) {
const int64_t database_id = 1;
const int64_t object_store_id = i + 1;
const IndexedDBKeyRange& range = ranges[i];
struct TestState {
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction1;
scoped_refptr<TestCallback> callback1;
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction2;
scoped_refptr<TestCallback> callback2;
std::vector<std::unique_ptr<storage::BlobDataHandle>> blobs;
} state;
for (size_t j = 0; j < 4; ++j)
state.blobs.push_back(CreateBlob());
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](TestableIndexedDBBackingStore* backing_store, TestState* state,
const std::vector<IndexedDBKey>& keys, int64_t database_id,
int64_t object_store_id) {
// Reset from previous iteration.
backing_store->ClearWrites();
backing_store->ClearRemovals();
std::vector<IndexedDBValue> values = {
IndexedDBValue("value0",
{IndexedDBBlobInfo(
std::make_unique<storage::BlobDataHandle>(
*state->blobs[0]),
base::UTF8ToUTF16("type 0"), 1)}),
IndexedDBValue("value1",
{IndexedDBBlobInfo(
std::make_unique<storage::BlobDataHandle>(
*state->blobs[1]),
base::UTF8ToUTF16("type 1"), 1)}),
IndexedDBValue("value2",
{IndexedDBBlobInfo(
std::make_unique<storage::BlobDataHandle>(
*state->blobs[2]),
base::UTF8ToUTF16("type 2"), 1)}),
IndexedDBValue("value3",
{IndexedDBBlobInfo(
std::make_unique<storage::BlobDataHandle>(
*state->blobs[3]),
base::UTF8ToUTF16("type 3"), 1)})};
ASSERT_GE(keys.size(), values.size());
// Initiate transaction1 - write records.
state->transaction1 =
std::make_unique<IndexedDBBackingStore::Transaction>(
backing_store);
state->transaction1->Begin();
IndexedDBBackingStore::RecordIdentifier record;
for (size_t i = 0; i < values.size(); ++i) {
EXPECT_TRUE(backing_store
->PutRecord(state->transaction1.get(),
database_id, object_store_id,
keys[i], &values[i], &record)
.ok());
}
// Start committing transaction1.
state->callback1 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction1->CommitPhaseOne(state->callback1).ok());
},
base::Unretained(backing_store()), base::Unretained(&state),
base::ConstRef(keys), database_id, object_store_id));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](TestableIndexedDBBackingStore* backing_store, TestState* state,
IndexedDBKeyRange range, int64_t database_id,
int64_t object_store_id) {
// Finish committing transaction1.
EXPECT_TRUE(state->callback1->called);
EXPECT_TRUE(state->callback1->succeeded);
EXPECT_TRUE(state->transaction1->CommitPhaseTwo().ok());
// Initiate transaction 2 - delete range.
state->transaction2 =
std::make_unique<IndexedDBBackingStore::Transaction>(
backing_store);
state->transaction2->Begin();
IndexedDBValue result_value;
EXPECT_TRUE(backing_store
->DeleteRange(state->transaction2.get(),
database_id, object_store_id, range)
.ok());
// Start committing transaction2.
state->callback2 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction2->CommitPhaseOne(state->callback2).ok());
},
base::Unretained(backing_store()), base::Unretained(&state), range,
database_id, object_store_id));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](TestableIndexedDBBackingStore* backing_store, TestState* state) {
// Finish committing transaction2.
EXPECT_TRUE(state->callback2->called);
EXPECT_TRUE(state->callback2->succeeded);
EXPECT_TRUE(state->transaction2->CommitPhaseTwo().ok());
// Verify blob removals.
EXPECT_EQ(0UL, backing_store->removals().size());
// Clean up Transactions, etc on the IDB thread.
*state = TestState();
},
base::Unretained(backing_store()), base::Unretained(&state)));
RunAllTasksUntilIdle();
}
}
TEST_F(IndexedDBBackingStoreTestWithBlobs, BlobJournalInterleavedTransactions) {
struct TestState {
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction1;
scoped_refptr<TestCallback> callback1;
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction2;
scoped_refptr<TestCallback> callback2;
} state;
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
// Initiate transaction1.
state->transaction1 =
std::make_unique<IndexedDBBackingStore::Transaction>(
test->backing_store());
state->transaction1->Begin();
IndexedDBBackingStore::RecordIdentifier record1;
EXPECT_TRUE(test->backing_store()
->PutRecord(state->transaction1.get(), 1, 1,
test->key3_, &test->value3_, &record1)
.ok());
state->callback1 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction1->CommitPhaseOne(state->callback1).ok());
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
// Verify transaction1 phase one completed.
EXPECT_TRUE(state->callback1->called);
EXPECT_TRUE(state->callback1->succeeded);
EXPECT_TRUE(test->CheckBlobWrites());
EXPECT_EQ(0U, test->backing_store()->removals().size());
// Initiate transaction2.
state->transaction2 =
std::make_unique<IndexedDBBackingStore::Transaction>(
test->backing_store());
state->transaction2->Begin();
IndexedDBBackingStore::RecordIdentifier record2;
EXPECT_TRUE(test->backing_store()
->PutRecord(state->transaction2.get(), 1, 1,
test->key1_, &test->value1_, &record2)
.ok());
state->callback2 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction2->CommitPhaseOne(state->callback2).ok());
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
// Verify transaction2 phase one completed.
EXPECT_TRUE(state->callback2->called);
EXPECT_TRUE(state->callback2->succeeded);
EXPECT_TRUE(test->CheckBlobWrites());
EXPECT_EQ(0U, test->backing_store()->removals().size());
// Finalize both transactions.
EXPECT_TRUE(state->transaction1->CommitPhaseTwo().ok());
EXPECT_EQ(0U, test->backing_store()->removals().size());
EXPECT_TRUE(state->transaction2->CommitPhaseTwo().ok());
EXPECT_EQ(0U, test->backing_store()->removals().size());
// Clean up Transactions, etc on the IDB thread.
*state = TestState();
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
}
TEST_F(IndexedDBBackingStoreTestWithBlobs, LiveBlobJournal) {
struct TestState {
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction1;
scoped_refptr<TestCallback> callback1;
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction3;
scoped_refptr<TestCallback> callback3;
IndexedDBValue read_result_value;
} state;
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
state->transaction1 =
std::make_unique<IndexedDBBackingStore::Transaction>(
test->backing_store());
state->transaction1->Begin();
IndexedDBBackingStore::RecordIdentifier record;
EXPECT_TRUE(test->backing_store()
->PutRecord(state->transaction1.get(), 1, 1,
test->key3_, &test->value3_, &record)
.ok());
state->callback1 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction1->CommitPhaseOne(state->callback1).ok());
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
EXPECT_TRUE(state->callback1->called);
EXPECT_TRUE(state->callback1->succeeded);
EXPECT_TRUE(test->CheckBlobWrites());
EXPECT_TRUE(state->transaction1->CommitPhaseTwo().ok());
IndexedDBBackingStore::Transaction transaction2(
test->backing_store());
transaction2.Begin();
EXPECT_TRUE(test->backing_store()
->GetRecord(&transaction2, 1, 1, test->key3_,
&state->read_result_value)
.ok());
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
EXPECT_EQ(test->value3_.bits, state->read_result_value.bits);
EXPECT_TRUE(
test->CheckBlobInfoMatches(state->read_result_value.blob_info));
EXPECT_TRUE(test->CheckBlobReadsMatchWrites(
state->read_result_value.blob_info));
for (size_t i = 0; i < state->read_result_value.blob_info.size();
++i) {
state->read_result_value.blob_info[i].mark_used_callback().Run();
}
state->transaction3 =
std::make_unique<IndexedDBBackingStore::Transaction>(
test->backing_store());
state->transaction3->Begin();
EXPECT_TRUE(test->backing_store()
->DeleteRange(state->transaction3.get(), 1, 1,
IndexedDBKeyRange(test->key3_))
.ok());
state->callback3 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction3->CommitPhaseOne(state->callback3).ok());
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
EXPECT_TRUE(state->callback3->called);
EXPECT_TRUE(state->callback3->succeeded);
EXPECT_TRUE(state->transaction3->CommitPhaseTwo().ok());
EXPECT_EQ(0U, test->backing_store()->removals().size());
for (size_t i = 0; i < state->read_result_value.blob_info.size();
++i) {
state->read_result_value.blob_info[i].release_callback().Run(
state->read_result_value.blob_info[i].file_path());
}
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
EXPECT_TRUE(test->backing_store()->IsBlobCleanupPending());
#if DCHECK_IS_ON()
EXPECT_EQ(3,
test->backing_store()
->NumAggregatedJournalCleaningRequestsForTesting());
#endif
for (int i = 3; i < IndexedDBBackingStore::kMaxJournalCleanRequests;
++i) {
test->backing_store()->StartJournalCleaningTimer();
}
EXPECT_NE(0U, test->backing_store()->removals().size());
EXPECT_TRUE(test->CheckBlobRemovals());
#if DCHECK_IS_ON()
EXPECT_EQ(0,
test->backing_store()->NumBlobFilesDeletedForTesting());
#endif
EXPECT_FALSE(test->backing_store()->IsBlobCleanupPending());
// Clean up Transactions, etc on the IDB thread.
*state = TestState();
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
}
// Make sure that using very high ( more than 32 bit ) values for database_id
// and object_store_id still work.
TEST_F(IndexedDBBackingStoreTest, HighIds) {
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStore* backing_store, IndexedDBKey key1,
IndexedDBKey key2, IndexedDBValue value1) {
const int64_t high_database_id = 1ULL << 35;
const int64_t high_object_store_id = 1ULL << 39;
// index_ids are capped at 32 bits for storage purposes.
const int64_t high_index_id = 1ULL << 29;
const int64_t invalid_high_index_id = 1ULL << 37;
const IndexedDBKey& index_key = key2;
std::string index_key_raw;
EncodeIDBKey(index_key, &index_key_raw);
{
IndexedDBBackingStore::Transaction transaction1(backing_store);
transaction1.Begin();
IndexedDBBackingStore::RecordIdentifier record;
leveldb::Status s = backing_store->PutRecord(
&transaction1, high_database_id, high_object_store_id, key1,
&value1, &record);
EXPECT_TRUE(s.ok());
s = backing_store->PutIndexDataForRecord(
&transaction1, high_database_id, high_object_store_id,
invalid_high_index_id, index_key, record);
EXPECT_FALSE(s.ok());
s = backing_store->PutIndexDataForRecord(
&transaction1, high_database_id, high_object_store_id,
high_index_id, index_key, record);
EXPECT_TRUE(s.ok());
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction1.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction1.CommitPhaseTwo().ok());
}
{
IndexedDBBackingStore::Transaction transaction2(backing_store);
transaction2.Begin();
IndexedDBValue result_value;
leveldb::Status s = backing_store->GetRecord(
&transaction2, high_database_id, high_object_store_id, key1,
&result_value);
EXPECT_TRUE(s.ok());
EXPECT_EQ(value1.bits, result_value.bits);
std::unique_ptr<IndexedDBKey> new_primary_key;
s = backing_store->GetPrimaryKeyViaIndex(
&transaction2, high_database_id, high_object_store_id,
invalid_high_index_id, index_key, &new_primary_key);
EXPECT_FALSE(s.ok());
s = backing_store->GetPrimaryKeyViaIndex(
&transaction2, high_database_id, high_object_store_id,
high_index_id, index_key, &new_primary_key);
EXPECT_TRUE(s.ok());
EXPECT_TRUE(new_primary_key->Equals(key1));
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
}
},
base::Unretained(backing_store()), key1_, key2_, value1_));
RunAllTasksUntilIdle();
}
// Make sure that other invalid ids do not crash.
TEST_F(IndexedDBBackingStoreTest, InvalidIds) {
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStore* backing_store, IndexedDBKey key,
IndexedDBValue value) {
// valid ids for use when testing invalid ids
const int64_t database_id = 1;
const int64_t object_store_id = 1;
const int64_t index_id = kMinimumIndexId;
// index_ids must be > kMinimumIndexId
const int64_t invalid_low_index_id = 19;
IndexedDBValue result_value;
IndexedDBBackingStore::Transaction transaction1(backing_store);
transaction1.Begin();
IndexedDBBackingStore::RecordIdentifier record;
leveldb::Status s = backing_store->PutRecord(
&transaction1, database_id, KeyPrefix::kInvalidId, key, &value,
&record);
EXPECT_FALSE(s.ok());
s = backing_store->PutRecord(&transaction1, database_id, 0, key,
&value, &record);
EXPECT_FALSE(s.ok());
s = backing_store->PutRecord(&transaction1, KeyPrefix::kInvalidId,
object_store_id, key, &value, &record);
EXPECT_FALSE(s.ok());
s = backing_store->PutRecord(&transaction1, 0, object_store_id, key,
&value, &record);
EXPECT_FALSE(s.ok());
s = backing_store->GetRecord(&transaction1, database_id,
KeyPrefix::kInvalidId, key,
&result_value);
EXPECT_FALSE(s.ok());
s = backing_store->GetRecord(&transaction1, database_id, 0, key,
&result_value);
EXPECT_FALSE(s.ok());
s = backing_store->GetRecord(&transaction1, KeyPrefix::kInvalidId,
object_store_id, key, &result_value);
EXPECT_FALSE(s.ok());
s = backing_store->GetRecord(&transaction1, 0, object_store_id, key,
&result_value);
EXPECT_FALSE(s.ok());
std::unique_ptr<IndexedDBKey> new_primary_key;
s = backing_store->GetPrimaryKeyViaIndex(
&transaction1, database_id, object_store_id,
KeyPrefix::kInvalidId, key, &new_primary_key);
EXPECT_FALSE(s.ok());
s = backing_store->GetPrimaryKeyViaIndex(
&transaction1, database_id, object_store_id,
invalid_low_index_id, key, &new_primary_key);
EXPECT_FALSE(s.ok());
s = backing_store->GetPrimaryKeyViaIndex(&transaction1, database_id,
object_store_id, 0, key,
&new_primary_key);
EXPECT_FALSE(s.ok());
s = backing_store->GetPrimaryKeyViaIndex(
&transaction1, KeyPrefix::kInvalidId, object_store_id, index_id,
key, &new_primary_key);
EXPECT_FALSE(s.ok());
s = backing_store->GetPrimaryKeyViaIndex(
&transaction1, database_id, KeyPrefix::kInvalidId, index_id,
key, &new_primary_key);
EXPECT_FALSE(s.ok());
},
base::Unretained(backing_store()), key1_, value1_));
RunAllTasksUntilIdle();
}
TEST_F(IndexedDBBackingStoreTest, CreateDatabase) {
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStore* backing_store) {
const base::string16 database_name(ASCIIToUTF16("db1"));
int64_t database_id;
const int64_t version = 9;
const int64_t object_store_id = 99;
const base::string16 object_store_name(
ASCIIToUTF16("object_store1"));
const bool auto_increment = true;
const IndexedDBKeyPath object_store_key_path(
ASCIIToUTF16("object_store_key"));
const int64_t index_id = 999;
const base::string16 index_name(ASCIIToUTF16("index1"));
const bool unique = true;
const bool multi_entry = true;
const IndexedDBKeyPath index_key_path(ASCIIToUTF16("index_key"));
IndexedDBMetadataCoding metadata_coding;
{
IndexedDBDatabaseMetadata database;
leveldb::Status s = metadata_coding.CreateDatabase(
backing_store->db(), backing_store->origin_identifier(),
database_name, version, &database);
EXPECT_TRUE(s.ok());
EXPECT_GT(database.id, 0);
database_id = database.id;
IndexedDBBackingStore::Transaction transaction(backing_store);
transaction.Begin();
IndexedDBObjectStoreMetadata object_store;
s = metadata_coding.CreateObjectStore(
transaction.transaction(), database.id, object_store_id,
object_store_name, object_store_key_path, auto_increment,
&object_store);
EXPECT_TRUE(s.ok());
IndexedDBIndexMetadata index;
s = metadata_coding.CreateIndex(
transaction.transaction(), database.id, object_store.id,
index_id, index_name, index_key_path, unique, multi_entry,
&index);
EXPECT_TRUE(s.ok());
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction.CommitPhaseTwo().ok());
}
{
IndexedDBDatabaseMetadata database;
bool found;
leveldb::Status s = metadata_coding.ReadMetadataForDatabaseName(
backing_store->db(), backing_store->origin_identifier(),
database_name, &database, &found);
EXPECT_TRUE(s.ok());
EXPECT_TRUE(found);
// database.name is not filled in by the implementation.
EXPECT_EQ(version, database.version);
EXPECT_EQ(database_id, database.id);
EXPECT_EQ(1UL, database.object_stores.size());
IndexedDBObjectStoreMetadata object_store =
database.object_stores[object_store_id];
EXPECT_EQ(object_store_name, object_store.name);
EXPECT_EQ(object_store_key_path, object_store.key_path);
EXPECT_EQ(auto_increment, object_store.auto_increment);
EXPECT_EQ(1UL, object_store.indexes.size());
IndexedDBIndexMetadata index = object_store.indexes[index_id];
EXPECT_EQ(index_name, index.name);
EXPECT_EQ(index_key_path, index.key_path);
EXPECT_EQ(unique, index.unique);
EXPECT_EQ(multi_entry, index.multi_entry);
}
},
base::Unretained(backing_store())));
RunAllTasksUntilIdle();
}
TEST_F(IndexedDBBackingStoreTest, GetDatabaseNames) {
idb_context_->TaskRunner()->PostTask(
FROM_HERE, base::BindOnce(
[](IndexedDBBackingStore* backing_store) {
const base::string16 db1_name(ASCIIToUTF16("db1"));
const int64_t db1_version = 1LL;
// Database records with DEFAULT_VERSION represent
// stale data, and should not be enumerated.
const base::string16 db2_name(ASCIIToUTF16("db2"));
const int64_t db2_version =
IndexedDBDatabaseMetadata::DEFAULT_VERSION;
IndexedDBMetadataCoding metadata_coding;
IndexedDBDatabaseMetadata db1;
leveldb::Status s = metadata_coding.CreateDatabase(
backing_store->db(),
backing_store->origin_identifier(), db1_name,
db1_version, &db1);
EXPECT_TRUE(s.ok());
EXPECT_GT(db1.id, 0LL);
IndexedDBDatabaseMetadata db2;
s = metadata_coding.CreateDatabase(
backing_store->db(),
backing_store->origin_identifier(), db2_name,
db2_version, &db2);
EXPECT_TRUE(s.ok());
EXPECT_GT(db2.id, db1.id);
std::vector<base::string16> names;
s = metadata_coding.ReadDatabaseNames(
backing_store->db(),
backing_store->origin_identifier(), &names);
EXPECT_TRUE(s.ok());
ASSERT_EQ(1U, names.size());
EXPECT_EQ(db1_name, names[0]);
},
base::Unretained(backing_store())));
RunAllTasksUntilIdle();
}
TEST_F(IndexedDBBackingStoreTest, ReadCorruptionInfo) {
// No |path_base|.
std::string message;
EXPECT_FALSE(IndexedDBBackingStore::ReadCorruptionInfo(base::FilePath(),
Origin(), &message));
EXPECT_TRUE(message.empty());
message.clear();
const base::FilePath path_base = temp_dir_.GetPath();
const Origin origin = Origin::Create(GURL("http://www.google.com/"));
ASSERT_FALSE(path_base.empty());
ASSERT_TRUE(PathIsWritable(path_base));
// File not found.
EXPECT_FALSE(
IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
EXPECT_TRUE(message.empty());
message.clear();
const base::FilePath info_path =
path_base.AppendASCII("http_www.google.com_0.indexeddb.leveldb")
.AppendASCII("corruption_info.json");
ASSERT_TRUE(CreateDirectory(info_path.DirName()));
// Empty file.
std::string dummy_data;
ASSERT_TRUE(WriteFile(info_path, dummy_data));
EXPECT_FALSE(
IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
EXPECT_FALSE(PathExists(info_path));
EXPECT_TRUE(message.empty());
message.clear();
// File size > 4 KB.
dummy_data.resize(5000, 'c');
ASSERT_TRUE(WriteFile(info_path, dummy_data));
EXPECT_FALSE(
IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
EXPECT_FALSE(PathExists(info_path));
EXPECT_TRUE(message.empty());
message.clear();
// Random string.
ASSERT_TRUE(WriteFile(info_path, "foo bar"));
EXPECT_FALSE(
IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
EXPECT_FALSE(PathExists(info_path));
EXPECT_TRUE(message.empty());
message.clear();
// Not a dictionary.
ASSERT_TRUE(WriteFile(info_path, "[]"));
EXPECT_FALSE(
IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
EXPECT_FALSE(PathExists(info_path));
EXPECT_TRUE(message.empty());
message.clear();
// Empty dictionary.
ASSERT_TRUE(WriteFile(info_path, "{}"));
EXPECT_FALSE(
IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
EXPECT_FALSE(PathExists(info_path));
EXPECT_TRUE(message.empty());
message.clear();
// Dictionary, no message key.
ASSERT_TRUE(WriteFile(info_path, "{\"foo\":\"bar\"}"));
EXPECT_FALSE(
IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
EXPECT_FALSE(PathExists(info_path));
EXPECT_TRUE(message.empty());
message.clear();
// Dictionary, message key.
ASSERT_TRUE(WriteFile(info_path, "{\"message\":\"bar\"}"));
EXPECT_TRUE(
IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
EXPECT_FALSE(PathExists(info_path));
EXPECT_EQ("bar", message);
message.clear();
// Dictionary, message key and more.
ASSERT_TRUE(WriteFile(info_path, "{\"message\":\"foo\",\"bar\":5}"));
EXPECT_TRUE(
IndexedDBBackingStore::ReadCorruptionInfo(path_base, origin, &message));
EXPECT_FALSE(PathExists(info_path));
EXPECT_EQ("foo", message);
}
// There was a wrong migration from schema 2 to 3, which always delete IDB
// blobs and doesn't actually write the new schema version. This tests the
// upgrade path where the database doesn't have blob entries, so it' safe to
// keep the database.
// https://crbug.com/756447, https://crbug.com/829125, https://crbug.com/829141
TEST_F(IndexedDBBackingStoreTest, SchemaUpgradeWithoutBlobsSurvives) {
struct TestState {
int64_t database_id;
int64_t object_store_id = 99;
} state;
// The database metadata needs to be written so we can verify the blob entry
// keys are not detected.
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStore* backing_store, TestState* state) {
const base::string16 database_name(ASCIIToUTF16("db1"));
const int64_t version = 9;
const base::string16 object_store_name(
ASCIIToUTF16("object_store1"));
const bool auto_increment = true;
const IndexedDBKeyPath object_store_key_path(
ASCIIToUTF16("object_store_key"));
IndexedDBMetadataCoding metadata_coding;
{
IndexedDBDatabaseMetadata database;
leveldb::Status s = metadata_coding.CreateDatabase(
backing_store->db(), backing_store->origin_identifier(),
database_name, version, &database);
EXPECT_TRUE(s.ok());
EXPECT_GT(database.id, 0);
state->database_id = database.id;
IndexedDBBackingStore::Transaction transaction(backing_store);
transaction.Begin();
IndexedDBObjectStoreMetadata object_store;
s = metadata_coding.CreateObjectStore(
transaction.transaction(), database.id,
state->object_store_id, object_store_name,
object_store_key_path, auto_increment, &object_store);
EXPECT_TRUE(s.ok());
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction.CommitPhaseTwo().ok());
}
},
base::Unretained(backing_store()), base::Unretained(&state)));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStore* backing_store, IndexedDBKey key,
IndexedDBValue value, TestState* state) {
// Save a value.
IndexedDBBackingStore::Transaction transaction1(backing_store);
transaction1.Begin();
IndexedDBBackingStore::RecordIdentifier record;
leveldb::Status s = backing_store->PutRecord(
&transaction1, state->database_id, state->object_store_id, key,
&value, &record);
EXPECT_TRUE(s.ok());
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction1.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction1.CommitPhaseTwo().ok());
// Set the schema to 2, which was before blob support.
scoped_refptr<LevelDBTransaction> transaction =
IndexedDBClassFactory::Get()->CreateLevelDBTransaction(
backing_store->db());
const std::string schema_version_key = SchemaVersionKey::Encode();
indexed_db::PutInt(transaction.get(), schema_version_key, 2);
ASSERT_TRUE(transaction->Commit().ok());
},
base::Unretained(backing_store()), key1_, value1_,
base::Unretained(&state)));
RunAllTasksUntilIdle();
DestroyFactoryAndBackingStore();
CreateFactoryAndBackingStore();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStore* backing_store, IndexedDBKey key,
IndexedDBValue value, TestState* state) {
IndexedDBBackingStore::Transaction transaction2(backing_store);
transaction2.Begin();
IndexedDBValue result_value;
EXPECT_TRUE(backing_store
->GetRecord(&transaction2, state->database_id,
state->object_store_id, key,
&result_value)
.ok());
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
EXPECT_EQ(value.bits, result_value.bits);
// Test that we upgraded.
scoped_refptr<LevelDBTransaction> transaction =
IndexedDBClassFactory::Get()->CreateLevelDBTransaction(
backing_store->db());
const std::string schema_version_key = SchemaVersionKey::Encode();
int64_t found_int = 0;
bool found = false;
bool success =
indexed_db::GetInt(transaction.get(), schema_version_key,
&found_int, &found)
.ok();
ASSERT_TRUE(success);
ASSERT_TRUE(transaction->Commit().ok());
EXPECT_TRUE(found);
EXPECT_EQ(3, found_int);
// Clean up Transactions, etc on the IDB thread.
*state = TestState();
},
base::Unretained(backing_store()), key1_, value1_,
base::Unretained(&state)));
RunAllTasksUntilIdle();
}
// Our v2->v3 schema migration code forgot to bump the on-disk version number.
// This test covers migrating a v3 database mislabeled as v2 to a properly
// labeled v3 database. When the mislabeled database has blob entries, we must
// treat it as corrupt and delete it.
// https://crbug.com/756447, https://crbug.com/829125, https://crbug.com/829141
TEST_F(IndexedDBBackingStoreTestWithBlobs, SchemaUpgradeWithBlobsCorrupt) {
struct TestState {
int64_t database_id;
int64_t object_store_id = 99;
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction1;
scoped_refptr<TestCallback> callback1;
std::unique_ptr<IndexedDBBackingStore::Transaction> transaction3;
scoped_refptr<TestCallback> callback3;
} state;
// The database metadata needs to be written so the blob entry keys can
// be detected.
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStore* backing_store, TestState* state) {
const base::string16 database_name(ASCIIToUTF16("db1"));
const int64_t version = 9;
const base::string16 object_store_name(
ASCIIToUTF16("object_store1"));
const bool auto_increment = true;
const IndexedDBKeyPath object_store_key_path(
ASCIIToUTF16("object_store_key"));
IndexedDBMetadataCoding metadata_coding;
{
IndexedDBDatabaseMetadata database;
leveldb::Status s = metadata_coding.CreateDatabase(
backing_store->db(), backing_store->origin_identifier(),
database_name, version, &database);
EXPECT_TRUE(s.ok());
EXPECT_GT(database.id, 0);
state->database_id = database.id;
IndexedDBBackingStore::Transaction transaction(backing_store);
transaction.Begin();
IndexedDBObjectStoreMetadata object_store;
s = metadata_coding.CreateObjectStore(
transaction.transaction(), database.id,
state->object_store_id, object_store_name,
object_store_key_path, auto_increment, &object_store);
EXPECT_TRUE(s.ok());
scoped_refptr<TestCallback> callback(
base::MakeRefCounted<TestCallback>());
EXPECT_TRUE(transaction.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction.CommitPhaseTwo().ok());
}
},
base::Unretained(backing_store()), base::Unretained(&state)));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
// Initiate transaction1 - writing blobs.
state->transaction1 =
std::make_unique<IndexedDBBackingStore::Transaction>(
test->backing_store());
state->transaction1->Begin();
IndexedDBBackingStore::RecordIdentifier record;
EXPECT_TRUE(test->backing_store()
->PutRecord(state->transaction1.get(),
state->database_id,
state->object_store_id, test->key3_,
&test->value3_, &record)
.ok());
state->callback1 = base::MakeRefCounted<TestCallback>();
EXPECT_TRUE(
state->transaction1->CommitPhaseOne(state->callback1).ok());
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
idb_context_->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](IndexedDBBackingStoreTestWithBlobs* test, TestState* state) {
// Finish up transaction1, verifying blob writes.
EXPECT_TRUE(state->callback1->called);
EXPECT_TRUE(state->callback1->succeeded);
EXPECT_TRUE(test->CheckBlobWrites());
EXPECT_TRUE(state->transaction1->CommitPhaseTwo().ok());
// Set the schema to 2, which was before blob support.
scoped_refptr<LevelDBTransaction> transaction =
IndexedDBClassFactory::Get()->CreateLevelDBTransaction(
test->backing_store()->db());
const std::string schema_version_key = SchemaVersionKey::Encode();
indexed_db::PutInt(transaction.get(), schema_version_key, 2);
ASSERT_TRUE(transaction->Commit().ok());
// Clean up Transactions, etc on the IDB thread.
*state = TestState();
},
base::Unretained(this), base::Unretained(&state)));
RunAllTasksUntilIdle();
DestroyFactoryAndBackingStore();
CreateFactoryAndBackingStore();
// The factory returns a null backing store pointer when there is a corrupt
// database.
EXPECT_EQ(nullptr, backing_store());
}
} // namespace indexed_db_backing_store_unittest
} // namespace content