blob: 7ca56ff5e80438e8690b69f262f88c544435ddb2 [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/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/test/test_simple_task_runner.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_value.h"
#include "content/browser/indexed_db/leveldb/leveldb_factory.h"
#include "content/browser/quota/mock_quota_manager_proxy.h"
#include "content/public/test/mock_special_storage_policy.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/url_request/url_request_test_util.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/modules/indexeddb/WebIDBTypes.h"
using base::ASCIIToUTF16;
using url::Origin;
namespace content {
namespace {
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, 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,
net::URLRequestContext* request_context,
LevelDBFactory* leveldb_factory,
base::SequencedTaskRunner* task_runner,
leveldb::Status* status) {
DCHECK(!path_base.empty());
std::unique_ptr<LevelDBComparator> comparator(new 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, 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(); }
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::Bind(&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;
}
// Timers don't play nicely with unit tests.
void StartJournalCleaningTimer() override {
CleanPrimaryJournalIgnoreReturn();
}
private:
TestableIndexedDBBackingStore(IndexedDBFactory* indexed_db_factory,
const Origin& origin,
const base::FilePath& blob_path,
net::URLRequestContext* request_context,
std::unique_ptr<LevelDBDatabase> db,
std::unique_ptr<LevelDBComparator> comparator,
base::SequencedTaskRunner* task_runner)
: IndexedDBBackingStore(indexed_db_factory,
origin,
blob_path,
request_context,
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) {}
scoped_refptr<TestableIndexedDBBackingStore> OpenBackingStoreForTest(
const Origin& origin,
net::URLRequestContext* url_request_context) {
blink::WebIDBDataLoss data_loss;
std::string data_loss_reason;
bool disk_full;
leveldb::Status status;
scoped_refptr<IndexedDBBackingStore> backing_store =
OpenBackingStore(origin,
context()->data_path(),
url_request_context,
&data_loss,
&data_loss_reason,
&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,
net::URLRequestContext* request_context,
blink::WebIDBDataLoss* data_loss,
std::string* data_loss_message,
bool* disk_full,
bool first_time,
leveldb::Status* status) override {
DefaultLevelDBFactory leveldb_factory;
return TestableIndexedDBBackingStore::Open(
this, origin, data_directory, request_context, &leveldb_factory,
context()->TaskRunner(), status);
}
private:
DISALLOW_COPY_AND_ASSIGN(TestIDBFactory);
};
class IndexedDBBackingStoreTest : public testing::Test {
public:
IndexedDBBackingStoreTest() {}
void SetUp() override {
const Origin origin(GURL("http://localhost:81"));
task_runner_ = new base::TestSimpleTaskRunner();
special_storage_policy_ = new MockSpecialStoragePolicy();
quota_manager_proxy_ = new MockQuotaManagerProxy(nullptr, nullptr);
special_storage_policy_->SetAllUnlimited(true);
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
idb_context_ = new IndexedDBContextImpl(
temp_dir_.path(), special_storage_policy_.get(),
quota_manager_proxy_.get(), task_runner_.get());
idb_factory_ = new TestIDBFactory(idb_context_.get());
backing_store_ =
idb_factory_->OpenBackingStoreForTest(origin, &url_request_context_);
// useful keys and values during tests
m_value1 = IndexedDBValue("value1", std::vector<IndexedDBBlobInfo>());
m_value2 = IndexedDBValue("value2", std::vector<IndexedDBBlobInfo>());
m_blob_info.push_back(
IndexedDBBlobInfo("uuid 3", base::UTF8ToUTF16("blob type"), 1));
m_blob_info.push_back(
IndexedDBBlobInfo("uuid 4",
base::FilePath(FILE_PATH_LITERAL("path/to/file")),
base::UTF8ToUTF16("file name"),
base::UTF8ToUTF16("file type")));
m_blob_info.push_back(
IndexedDBBlobInfo("uuid 5",
base::FilePath(),
base::UTF8ToUTF16("file name"),
base::UTF8ToUTF16("file type")));
m_value3 = IndexedDBValue("value3", m_blob_info);
m_key1 = IndexedDBKey(99, blink::WebIDBKeyTypeNumber);
m_key2 = IndexedDBKey(ASCIIToUTF16("key2"));
m_key3 = IndexedDBKey(ASCIIToUTF16("key3"));
}
void TearDown() override {
quota_manager_proxy_->SimulateQuotaManagerDestroyed();
}
// 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 {
if (m_blob_info.size() != reads.size())
return false;
for (size_t i = 0; i < m_blob_info.size(); ++i) {
const IndexedDBBlobInfo& a = m_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 {
if (backing_store_->writes().size() != reads.size())
return false;
std::set<int64_t> ids;
for (size_t i = 0; i < backing_store_->writes().size(); ++i)
ids.insert(backing_store_->writes()[i].key());
if (ids.size() != backing_store_->writes().size())
return false;
for (size_t i = 0; i < reads.size(); ++i) {
if (ids.count(reads[i].key()) != 1)
return false;
}
return true;
}
bool CheckBlobWrites() const {
if (backing_store_->writes().size() != m_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 = m_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.url() != GURL("blob:uuid/" + info.uuid()))
return false;
}
}
return true;
}
bool CheckBlobRemovals() const {
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;
}
protected:
// Must be initialized before url_request_context_
content::TestBrowserThreadBundle thread_bundle_;
base::ScopedTempDir temp_dir_;
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_;
scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_;
scoped_refptr<IndexedDBContextImpl> idb_context_;
scoped_refptr<TestIDBFactory> idb_factory_;
net::TestURLRequestContext url_request_context_;
scoped_refptr<TestableIndexedDBBackingStore> backing_store_;
// Sample keys and values that are consistent.
IndexedDBKey m_key1;
IndexedDBKey m_key2;
IndexedDBKey m_key3;
IndexedDBValue m_value1;
IndexedDBValue m_value2;
IndexedDBValue m_value3;
std::vector<IndexedDBBlobInfo> m_blob_info;
private:
DISALLOW_COPY_AND_ASSIGN(IndexedDBBackingStoreTest);
};
class TestCallback : public IndexedDBBackingStore::BlobWriteCallback {
public:
TestCallback() : called(false), succeeded(false) {}
void Run(bool succeeded_in) override {
called = true;
succeeded = succeeded_in;
}
bool called;
bool succeeded;
protected:
~TestCallback() override {}
private:
DISALLOW_COPY_AND_ASSIGN(TestCallback);
};
TEST_F(IndexedDBBackingStoreTest, PutGetConsistency) {
{
IndexedDBBackingStore::Transaction transaction1(backing_store_.get());
transaction1.Begin();
ScopedVector<storage::BlobDataHandle> handles;
IndexedDBBackingStore::RecordIdentifier record;
leveldb::Status s = backing_store_->PutRecord(
&transaction1, 1, 1, m_key1, &m_value1, &handles, &record);
EXPECT_TRUE(s.ok());
scoped_refptr<TestCallback> callback(new 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_.get());
transaction2.Begin();
IndexedDBValue result_value;
EXPECT_TRUE(
backing_store_->GetRecord(&transaction2, 1, 1, m_key1, &result_value)
.ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
EXPECT_EQ(m_value1.bits, result_value.bits);
}
}
TEST_F(IndexedDBBackingStoreTest, PutGetConsistencyWithBlobs) {
{
IndexedDBBackingStore::Transaction transaction1(backing_store_.get());
transaction1.Begin();
ScopedVector<storage::BlobDataHandle> handles;
IndexedDBBackingStore::RecordIdentifier record;
EXPECT_TRUE(backing_store_->PutRecord(&transaction1,
1,
1,
m_key3,
&m_value3,
&handles,
&record).ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction1.CommitPhaseOne(callback).ok());
task_runner_->RunUntilIdle();
EXPECT_TRUE(CheckBlobWrites());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction1.CommitPhaseTwo().ok());
}
{
IndexedDBBackingStore::Transaction transaction2(backing_store_.get());
transaction2.Begin();
IndexedDBValue result_value;
EXPECT_TRUE(
backing_store_->GetRecord(&transaction2, 1, 1, m_key3, &result_value)
.ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
EXPECT_EQ(m_value3.bits, result_value.bits);
EXPECT_TRUE(CheckBlobInfoMatches(result_value.blob_info));
EXPECT_TRUE(CheckBlobReadsMatchWrites(result_value.blob_info));
}
{
IndexedDBBackingStore::Transaction transaction3(backing_store_.get());
transaction3.Begin();
IndexedDBValue result_value;
EXPECT_TRUE(backing_store_->DeleteRange(&transaction3,
1,
1,
IndexedDBKeyRange(m_key3)).ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction3.CommitPhaseOne(callback).ok());
task_runner_->RunUntilIdle();
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction3.CommitPhaseTwo().ok());
EXPECT_TRUE(CheckBlobRemovals());
}
}
TEST_F(IndexedDBBackingStoreTest, DeleteRange) {
IndexedDBKey key0 = IndexedDBKey(ASCIIToUTF16("key0"));
IndexedDBKey key1 = IndexedDBKey(ASCIIToUTF16("key1"));
IndexedDBKey key2 = IndexedDBKey(ASCIIToUTF16("key2"));
IndexedDBKey key3 = IndexedDBKey(ASCIIToUTF16("key3"));
IndexedDBBlobInfo blob0("uuid 0", base::UTF8ToUTF16("type 0"), 1);
IndexedDBBlobInfo blob1("uuid 1", base::UTF8ToUTF16("type 1"), 1);
IndexedDBBlobInfo blob2("uuid 2", base::UTF8ToUTF16("type 2"), 1);
IndexedDBBlobInfo blob3("uuid 3", base::UTF8ToUTF16("type 3"), 1);
IndexedDBKeyRange ranges[] = {IndexedDBKeyRange(key1, key2, false, false),
IndexedDBKeyRange(key1, key2, false, false),
IndexedDBKeyRange(key0, key2, true, false),
IndexedDBKeyRange(key1, key3, false, true),
IndexedDBKeyRange(key0, key3, true, true)};
for (unsigned i = 0; i < sizeof(ranges) / sizeof(IndexedDBKeyRange); ++i) {
backing_store_->ClearWrites();
backing_store_->ClearRemovals();
{
std::vector<IndexedDBBlobInfo> blob_info0, blob_info1, blob_info2,
blob_info3;
blob_info0.push_back(blob0);
blob_info1.push_back(blob1);
blob_info2.push_back(blob2);
blob_info3.push_back(blob3);
IndexedDBValue value0 = IndexedDBValue("value0", blob_info0);
IndexedDBValue value1 = IndexedDBValue("value1", blob_info1);
IndexedDBValue value2 = IndexedDBValue("value2", blob_info2);
IndexedDBValue value3 = IndexedDBValue("value3", blob_info3);
IndexedDBBackingStore::Transaction transaction1(backing_store_.get());
transaction1.Begin();
ScopedVector<storage::BlobDataHandle> handles;
IndexedDBBackingStore::RecordIdentifier record;
EXPECT_TRUE(backing_store_->PutRecord(&transaction1,
1,
i + 1,
key0,
&value0,
&handles,
&record).ok());
EXPECT_TRUE(backing_store_->PutRecord(&transaction1,
1,
i + 1,
key1,
&value1,
&handles,
&record).ok());
EXPECT_TRUE(backing_store_->PutRecord(&transaction1,
1,
i + 1,
key2,
&value2,
&handles,
&record).ok());
EXPECT_TRUE(backing_store_->PutRecord(&transaction1,
1,
i + 1,
key3,
&value3,
&handles,
&record).ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction1.CommitPhaseOne(callback).ok());
task_runner_->RunUntilIdle();
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction1.CommitPhaseTwo().ok());
}
{
IndexedDBBackingStore::Transaction transaction2(backing_store_.get());
transaction2.Begin();
IndexedDBValue result_value;
EXPECT_TRUE(
backing_store_->DeleteRange(&transaction2, 1, i + 1, ranges[i]).ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback).ok());
task_runner_->RunUntilIdle();
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
EXPECT_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]);
}
}
}
TEST_F(IndexedDBBackingStoreTest, DeleteRangeEmptyRange) {
IndexedDBKey key0 = IndexedDBKey(ASCIIToUTF16("key0"));
IndexedDBKey key1 = IndexedDBKey(ASCIIToUTF16("key1"));
IndexedDBKey key2 = IndexedDBKey(ASCIIToUTF16("key2"));
IndexedDBKey key3 = IndexedDBKey(ASCIIToUTF16("key3"));
IndexedDBKey key4 = IndexedDBKey(ASCIIToUTF16("key4"));
IndexedDBBlobInfo blob0("uuid 0", base::UTF8ToUTF16("type 0"), 1);
IndexedDBBlobInfo blob1("uuid 1", base::UTF8ToUTF16("type 1"), 1);
IndexedDBBlobInfo blob2("uuid 2", base::UTF8ToUTF16("type 2"), 1);
IndexedDBBlobInfo blob3("uuid 3", base::UTF8ToUTF16("type 3"), 1);
IndexedDBKeyRange ranges[] = {IndexedDBKeyRange(key3, key4, true, false),
IndexedDBKeyRange(key2, key1, false, false),
IndexedDBKeyRange(key2, key1, true, true)};
for (unsigned i = 0; i < arraysize(ranges); ++i) {
backing_store_->ClearWrites();
backing_store_->ClearRemovals();
{
std::vector<IndexedDBBlobInfo> blob_info0, blob_info1, blob_info2,
blob_info3;
blob_info0.push_back(blob0);
blob_info1.push_back(blob1);
blob_info2.push_back(blob2);
blob_info3.push_back(blob3);
IndexedDBValue value0 = IndexedDBValue("value0", blob_info0);
IndexedDBValue value1 = IndexedDBValue("value1", blob_info1);
IndexedDBValue value2 = IndexedDBValue("value2", blob_info2);
IndexedDBValue value3 = IndexedDBValue("value3", blob_info3);
IndexedDBBackingStore::Transaction transaction1(backing_store_.get());
transaction1.Begin();
ScopedVector<storage::BlobDataHandle> handles;
IndexedDBBackingStore::RecordIdentifier record;
EXPECT_TRUE(backing_store_->PutRecord(&transaction1,
1,
i + 1,
key0,
&value0,
&handles,
&record).ok());
EXPECT_TRUE(backing_store_->PutRecord(&transaction1,
1,
i + 1,
key1,
&value1,
&handles,
&record).ok());
EXPECT_TRUE(backing_store_->PutRecord(&transaction1,
1,
i + 1,
key2,
&value2,
&handles,
&record).ok());
EXPECT_TRUE(backing_store_->PutRecord(&transaction1,
1,
i + 1,
key3,
&value3,
&handles,
&record).ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction1.CommitPhaseOne(callback).ok());
task_runner_->RunUntilIdle();
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction1.CommitPhaseTwo().ok());
}
{
IndexedDBBackingStore::Transaction transaction2(backing_store_.get());
transaction2.Begin();
IndexedDBValue result_value;
EXPECT_TRUE(
backing_store_->DeleteRange(&transaction2, 1, i + 1, ranges[i]).ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback).ok());
task_runner_->RunUntilIdle();
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
EXPECT_EQ(0UL, backing_store_->removals().size());
}
}
}
TEST_F(IndexedDBBackingStoreTest, BlobJournalInterleavedTransactions) {
IndexedDBBackingStore::Transaction transaction1(backing_store_.get());
transaction1.Begin();
ScopedVector<storage::BlobDataHandle> handles1;
IndexedDBBackingStore::RecordIdentifier record1;
EXPECT_TRUE(backing_store_->PutRecord(&transaction1, 1, 1, m_key3, &m_value3,
&handles1, &record1).ok());
scoped_refptr<TestCallback> callback1(new TestCallback());
EXPECT_TRUE(transaction1.CommitPhaseOne(callback1).ok());
task_runner_->RunUntilIdle();
EXPECT_TRUE(CheckBlobWrites());
EXPECT_TRUE(callback1->called);
EXPECT_TRUE(callback1->succeeded);
EXPECT_EQ(0U, backing_store_->removals().size());
IndexedDBBackingStore::Transaction transaction2(backing_store_.get());
transaction2.Begin();
ScopedVector<storage::BlobDataHandle> handles2;
IndexedDBBackingStore::RecordIdentifier record2;
EXPECT_TRUE(backing_store_->PutRecord(&transaction2, 1, 1, m_key1, &m_value1,
&handles2, &record2).ok());
scoped_refptr<TestCallback> callback2(new TestCallback());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback2).ok());
task_runner_->RunUntilIdle();
EXPECT_TRUE(CheckBlobWrites());
EXPECT_TRUE(callback2->called);
EXPECT_TRUE(callback2->succeeded);
EXPECT_EQ(0U, backing_store_->removals().size());
EXPECT_TRUE(transaction1.CommitPhaseTwo().ok());
EXPECT_EQ(0U, backing_store_->removals().size());
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
EXPECT_EQ(0U, backing_store_->removals().size());
}
TEST_F(IndexedDBBackingStoreTest, LiveBlobJournal) {
{
IndexedDBBackingStore::Transaction transaction1(backing_store_.get());
transaction1.Begin();
ScopedVector<storage::BlobDataHandle> handles;
IndexedDBBackingStore::RecordIdentifier record;
EXPECT_TRUE(backing_store_->PutRecord(&transaction1,
1,
1,
m_key3,
&m_value3,
&handles,
&record).ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction1.CommitPhaseOne(callback).ok());
task_runner_->RunUntilIdle();
EXPECT_TRUE(CheckBlobWrites());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction1.CommitPhaseTwo().ok());
}
IndexedDBValue read_result_value;
{
IndexedDBBackingStore::Transaction transaction2(backing_store_.get());
transaction2.Begin();
EXPECT_TRUE(
backing_store_->GetRecord(
&transaction2, 1, 1, m_key3, &read_result_value)
.ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction2.CommitPhaseOne(callback).ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction2.CommitPhaseTwo().ok());
EXPECT_EQ(m_value3.bits, read_result_value.bits);
EXPECT_TRUE(CheckBlobInfoMatches(read_result_value.blob_info));
EXPECT_TRUE(CheckBlobReadsMatchWrites(read_result_value.blob_info));
for (size_t i = 0; i < read_result_value.blob_info.size(); ++i) {
read_result_value.blob_info[i].mark_used_callback().Run();
}
}
{
IndexedDBBackingStore::Transaction transaction3(backing_store_.get());
transaction3.Begin();
EXPECT_TRUE(backing_store_->DeleteRange(&transaction3,
1,
1,
IndexedDBKeyRange(m_key3)).ok());
scoped_refptr<TestCallback> callback(new TestCallback());
EXPECT_TRUE(transaction3.CommitPhaseOne(callback).ok());
task_runner_->RunUntilIdle();
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
EXPECT_TRUE(transaction3.CommitPhaseTwo().ok());
EXPECT_EQ(0U, backing_store_->removals().size());
for (size_t i = 0; i < read_result_value.blob_info.size(); ++i) {
read_result_value.blob_info[i].release_callback().Run(
read_result_value.blob_info[i].file_path());
}
task_runner_->RunUntilIdle();
EXPECT_NE(0U, backing_store_->removals().size());
EXPECT_TRUE(CheckBlobRemovals());
}
}
// Make sure that using very high ( more than 32 bit ) values for database_id
// and object_store_id still work.
TEST_F(IndexedDBBackingStoreTest, HighIds) {
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 = m_key2;
std::string index_key_raw;
EncodeIDBKey(index_key, &index_key_raw);
{
IndexedDBBackingStore::Transaction transaction1(backing_store_.get());
transaction1.Begin();
ScopedVector<storage::BlobDataHandle> handles;
IndexedDBBackingStore::RecordIdentifier record;
leveldb::Status s = backing_store_->PutRecord(&transaction1,
high_database_id,
high_object_store_id,
m_key1,
&m_value1,
&handles,
&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(new TestCallback());
s = transaction1.CommitPhaseOne(callback);
EXPECT_TRUE(s.ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
s = transaction1.CommitPhaseTwo();
EXPECT_TRUE(s.ok());
}
{
IndexedDBBackingStore::Transaction transaction2(backing_store_.get());
transaction2.Begin();
IndexedDBValue result_value;
leveldb::Status s = backing_store_->GetRecord(&transaction2,
high_database_id,
high_object_store_id,
m_key1,
&result_value);
EXPECT_TRUE(s.ok());
EXPECT_EQ(m_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(m_key1));
scoped_refptr<TestCallback> callback(new TestCallback());
s = transaction2.CommitPhaseOne(callback);
EXPECT_TRUE(s.ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
s = transaction2.CommitPhaseTwo();
EXPECT_TRUE(s.ok());
}
}
// Make sure that other invalid ids do not crash.
TEST_F(IndexedDBBackingStoreTest, InvalidIds) {
// 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;
const int64_t invalid_low_index_id =
19; // index_ids must be > kMinimumIndexId
IndexedDBValue result_value;
IndexedDBBackingStore::Transaction transaction1(backing_store_.get());
transaction1.Begin();
ScopedVector<storage::BlobDataHandle> handles;
IndexedDBBackingStore::RecordIdentifier record;
leveldb::Status s = backing_store_->PutRecord(&transaction1,
database_id,
KeyPrefix::kInvalidId,
m_key1,
&m_value1,
&handles,
&record);
EXPECT_FALSE(s.ok());
s = backing_store_->PutRecord(
&transaction1, database_id, 0, m_key1, &m_value1, &handles, &record);
EXPECT_FALSE(s.ok());
s = backing_store_->PutRecord(&transaction1,
KeyPrefix::kInvalidId,
object_store_id,
m_key1,
&m_value1,
&handles,
&record);
EXPECT_FALSE(s.ok());
s = backing_store_->PutRecord(
&transaction1, 0, object_store_id, m_key1, &m_value1, &handles, &record);
EXPECT_FALSE(s.ok());
s = backing_store_->GetRecord(
&transaction1, database_id, KeyPrefix::kInvalidId, m_key1, &result_value);
EXPECT_FALSE(s.ok());
s = backing_store_->GetRecord(
&transaction1, database_id, 0, m_key1, &result_value);
EXPECT_FALSE(s.ok());
s = backing_store_->GetRecord(&transaction1,
KeyPrefix::kInvalidId,
object_store_id,
m_key1,
&result_value);
EXPECT_FALSE(s.ok());
s = backing_store_->GetRecord(
&transaction1, 0, object_store_id, m_key1, &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,
m_key1,
&new_primary_key);
EXPECT_FALSE(s.ok());
s = backing_store_->GetPrimaryKeyViaIndex(&transaction1,
database_id,
object_store_id,
invalid_low_index_id,
m_key1,
&new_primary_key);
EXPECT_FALSE(s.ok());
s = backing_store_->GetPrimaryKeyViaIndex(
&transaction1, database_id, object_store_id, 0, m_key1, &new_primary_key);
EXPECT_FALSE(s.ok());
s = backing_store_->GetPrimaryKeyViaIndex(&transaction1,
KeyPrefix::kInvalidId,
object_store_id,
index_id,
m_key1,
&new_primary_key);
EXPECT_FALSE(s.ok());
s = backing_store_->GetPrimaryKeyViaIndex(&transaction1,
database_id,
KeyPrefix::kInvalidId,
index_id,
m_key1,
&new_primary_key);
EXPECT_FALSE(s.ok());
}
TEST_F(IndexedDBBackingStoreTest, CreateDatabase) {
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"));
{
leveldb::Status s = backing_store_->CreateIDBDatabaseMetaData(
database_name, version, &database_id);
EXPECT_TRUE(s.ok());
EXPECT_GT(database_id, 0);
IndexedDBBackingStore::Transaction transaction(backing_store_.get());
transaction.Begin();
s = backing_store_->CreateObjectStore(&transaction,
database_id,
object_store_id,
object_store_name,
object_store_key_path,
auto_increment);
EXPECT_TRUE(s.ok());
s = backing_store_->CreateIndex(&transaction,
database_id,
object_store_id,
index_id,
index_name,
index_key_path,
unique,
multi_entry);
EXPECT_TRUE(s.ok());
scoped_refptr<TestCallback> callback(new TestCallback());
s = transaction.CommitPhaseOne(callback);
EXPECT_TRUE(s.ok());
EXPECT_TRUE(callback->called);
EXPECT_TRUE(callback->succeeded);
s = transaction.CommitPhaseTwo();
EXPECT_TRUE(s.ok());
}
{
IndexedDBDatabaseMetadata database;
bool found;
leveldb::Status s = backing_store_->GetIDBDatabaseMetaData(
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);
s = backing_store_->GetObjectStores(database.id, &database.object_stores);
EXPECT_TRUE(s.ok());
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);
}
}
TEST_F(IndexedDBBackingStoreTest, GetDatabaseNames) {
const base::string16 db1_name(ASCIIToUTF16("db1"));
const int64_t db1_version = 1LL;
int64_t db1_id;
// 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;
int64_t db2_id;
leveldb::Status s =
backing_store_->CreateIDBDatabaseMetaData(db1_name, db1_version, &db1_id);
EXPECT_TRUE(s.ok());
EXPECT_GT(db1_id, 0LL);
s = backing_store_->CreateIDBDatabaseMetaData(db2_name, db2_version, &db2_id);
EXPECT_TRUE(s.ok());
EXPECT_GT(db2_id, db1_id);
std::vector<base::string16> names = backing_store_->GetDatabaseNames(&s);
EXPECT_TRUE(s.ok());
EXPECT_EQ(names.size(), 1ULL);
EXPECT_EQ(names[0], db1_name);
}
} // namespace
} // namespace content