blob: c149a7e01c27c5b38207f4eca9cd0a0bda4181c9 [file] [log] [blame]
// Copyright 2017 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/leveldb/transactional_leveldb_transaction.h"
#include <stddef.h>
#include <algorithm>
#include <cstring>
#include <memory>
#include <string>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/no_destructor.h"
#include "base/strings/string_piece.h"
#include "base/test/bind_test_util.h"
#include "content/browser/indexed_db/leveldb/leveldb_env.h"
#include "content/browser/indexed_db/leveldb/transactional_leveldb_database.h"
#include "content/browser/indexed_db/leveldb/transactional_leveldb_iterator.h"
#include "content/browser/indexed_db/scopes/disjoint_range_lock_manager.h"
#include "content/browser/indexed_db/scopes/leveldb_scope.h"
#include "content/browser/indexed_db/scopes/leveldb_scopes.h"
#include "content/browser/indexed_db/scopes/leveldb_scopes_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "third_party/leveldatabase/src/include/leveldb/comparator.h"
namespace content {
namespace {
static const size_t kTestingMaxOpenCursors = 3;
} // namespace
class TransactionalLevelDBTransactionTest : public LevelDBScopesTestBase {
public:
TransactionalLevelDBTransactionTest() {}
void TearDown() override {
leveldb_database_.reset();
LevelDBScopesTestBase::TearDown();
}
protected:
void SetupLevelDBDatabase() {
ASSERT_TRUE(leveldb_);
std::unique_ptr<LevelDBScopes> scopes_system =
std::make_unique<LevelDBScopes>(
metadata_prefix_, kWriteBatchSizeForTesting, leveldb_,
&lock_manager_,
base::BindLambdaForTesting(
[this](leveldb::Status s) { this->failure_status_ = s; }));
leveldb::Status s = scopes_system->Initialize();
ASSERT_TRUE(s.ok()) << s.ToString();
s = scopes_system->StartRecoveryAndCleanupTasks(
LevelDBScopes::TaskRunnerMode::kNewCleanupAndRevertSequences);
ASSERT_TRUE(s.ok()) << s.ToString();
leveldb_database_ = leveldb_factory_->CreateLevelDBDatabase(
leveldb_, std::move(scopes_system),
base::SequencedTaskRunnerHandle::Get(), kTestingMaxOpenCursors);
}
std::vector<ScopeLock> AcquireLocksSync(
ScopesLockManager* lock_manager,
base::flat_set<ScopesLockManager::ScopeLockRequest> lock_requests) {
base::RunLoop loop;
ScopesLocksHolder locks_receiver;
bool success = lock_manager->AcquireLocks(
lock_requests, locks_receiver.AsWeakPtr(),
base::BindLambdaForTesting([&loop]() { loop.Quit(); }));
EXPECT_TRUE(success);
if (success)
loop.Run();
return std::move(locks_receiver.locks);
}
// Convenience methods to access the database outside any
// transaction to cut down on boilerplate around calls.
void Put(const base::StringPiece& key, const std::string& value) {
std::string put_value = value;
leveldb::Status s = leveldb_database_->Put(key, &put_value);
ASSERT_TRUE(s.ok());
}
void Get(const base::StringPiece& key, std::string* value, bool* found) {
leveldb::Status s = leveldb_database_->Get(key, value, found);
ASSERT_TRUE(s.ok());
}
bool Has(const base::StringPiece& key) {
bool found;
std::string value;
leveldb::Status s = leveldb_database_->Get(key, &value, &found);
EXPECT_TRUE(s.ok());
return found;
}
// Convenience wrappers for LevelDBTransaction operations to
// avoid boilerplate in tests.
bool TransactionHas(TransactionalLevelDBTransaction* transaction,
const base::StringPiece& key) {
std::string value;
bool found;
leveldb::Status s = transaction->Get(key, &value, &found);
EXPECT_TRUE(s.ok());
return found;
}
void TransactionPut(TransactionalLevelDBTransaction* transaction,
const base::StringPiece& key,
const std::string& value) {
std::string put_value = value;
leveldb::Status s = transaction->Put(key, &put_value);
EXPECT_TRUE(s.ok());
}
void TransactionRemove(TransactionalLevelDBTransaction* transaction,
const base::StringPiece& key) {
leveldb::Status s = transaction->Remove(key);
ASSERT_TRUE(s.ok());
}
int Compare(const base::StringPiece& a, const base::StringPiece& b) const {
return leveldb_database_->leveldb_state()->comparator()->Compare(
leveldb_env::MakeSlice(a), leveldb_env::MakeSlice(b));
}
bool KeysEqual(const base::StringPiece& a, const base::StringPiece& b) const {
return Compare(a, b) == 0;
}
TransactionalLevelDBDatabase* db() { return leveldb_database_.get(); }
scoped_refptr<TransactionalLevelDBTransaction> CreateTransaction() {
return indexed_db::LevelDBFactory::Get()->CreateLevelDBTransaction(
db(),
db()->scopes()->CreateScope(
AcquireLocksSync(&lock_manager_, {CreateSimpleSharedLock()}), {}));
}
leveldb::Status failure_status_;
private:
std::unique_ptr<TransactionalLevelDBDatabase> leveldb_database_;
DisjointRangeLockManager lock_manager_ = {3};
DISALLOW_COPY_AND_ASSIGN(TransactionalLevelDBTransactionTest);
};
TEST_F(TransactionalLevelDBTransactionTest, GetPutDelete) {
SetUpRealDatabase();
SetupLevelDBDatabase();
leveldb::Status status;
const std::string key("key");
std::string got_value;
const std::string value("value");
Put(key, value);
bool found = false;
Get(key, &got_value, &found);
EXPECT_TRUE(found);
EXPECT_EQ(Compare(got_value, value), 0);
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
status = transaction->Get(key, &got_value, &found);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(found);
EXPECT_EQ(Compare(got_value, value), 0);
const std::string added_key("b-added key");
const std::string added_value("b-added value");
Put(added_key, added_value);
Get(added_key, &got_value, &found);
EXPECT_TRUE(found);
EXPECT_EQ(Compare(got_value, added_value), 0);
EXPECT_TRUE(TransactionHas(transaction.get(), added_key));
const std::string another_key("b-another key");
const std::string another_value("b-another value");
EXPECT_EQ(12ull, transaction->GetTransactionSize());
TransactionPut(transaction.get(), another_key, another_value);
EXPECT_EQ(43ull, transaction->GetTransactionSize());
status = transaction->Get(another_key, &got_value, &found);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(found);
EXPECT_EQ(Compare(got_value, another_value), 0);
TransactionRemove(transaction.get(), another_key);
EXPECT_EQ(136ull, transaction->GetTransactionSize());
status = transaction->Get(another_key, &got_value, &found);
EXPECT_FALSE(found);
}
TEST_F(TransactionalLevelDBTransactionTest, Iterator) {
SetUpRealDatabase();
SetupLevelDBDatabase();
const std::string key1("b-key1");
const std::string value1("value1");
const std::string key2("b-key2");
const std::string value2("value2");
Put(key1, value1);
Put(key2, value2);
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction->CreateIterator();
it->Seek(std::string("b"));
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key1), 0) << it->Key() << ", " << key1;
EXPECT_EQ(Compare(it->Value(), value1), 0);
it->Next();
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key2), 0);
EXPECT_EQ(Compare(it->Value(), value2), 0);
it->Next();
EXPECT_FALSE(it->IsValid());
}
TEST_F(TransactionalLevelDBTransactionTest, Commit) {
SetUpRealDatabase();
SetupLevelDBDatabase();
const std::string key1("b-key1");
const std::string key2("b-key2");
const std::string value1("value1");
const std::string value2("value2");
const std::string value3("value3");
std::string got_value;
bool found;
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
TransactionPut(transaction.get(), key1, value1);
TransactionPut(transaction.get(), key2, value2);
TransactionPut(transaction.get(), key2, value3);
leveldb::Status status = transaction->Commit(/*sync_on_commit=*/false);
EXPECT_TRUE(status.ok());
Get(key1, &got_value, &found);
EXPECT_TRUE(found);
EXPECT_EQ(value1, got_value);
Get(key2, &got_value, &found);
EXPECT_TRUE(found);
EXPECT_EQ(value3, got_value);
}
TEST_F(TransactionalLevelDBTransactionTest, IterationWithEvictedCursors) {
SetUpRealDatabase();
SetupLevelDBDatabase();
leveldb::Status status;
Put("b-key1", "value1");
Put("b-key2", "value2");
Put("b-key3", "value3");
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
std::unique_ptr<TransactionalLevelDBIterator> evicted_normal_location =
transaction->CreateIterator();
std::unique_ptr<TransactionalLevelDBIterator> evicted_before_start =
transaction->CreateIterator();
std::unique_ptr<TransactionalLevelDBIterator> evicted_after_end =
transaction->CreateIterator();
std::unique_ptr<TransactionalLevelDBIterator> it1 =
transaction->CreateIterator();
std::unique_ptr<TransactionalLevelDBIterator> it2 =
transaction->CreateIterator();
std::unique_ptr<TransactionalLevelDBIterator> it3 =
transaction->CreateIterator();
evicted_normal_location->Seek("b-key1");
evicted_before_start->Seek("b-key1");
evicted_before_start->Prev();
evicted_after_end->SeekToLast();
evicted_after_end->Next();
EXPECT_FALSE(evicted_before_start->IsValid());
EXPECT_TRUE(evicted_normal_location->IsValid());
EXPECT_FALSE(evicted_after_end->IsValid());
// Nothing is purged, as we just have 3 iterators used.
EXPECT_FALSE(evicted_normal_location->IsEvicted());
EXPECT_FALSE(evicted_before_start->IsEvicted());
EXPECT_FALSE(evicted_after_end->IsEvicted());
// Should purge all of our earlier iterators.
status = it1->Seek("b-key1");
EXPECT_TRUE(status.ok());
status = it2->Seek("b-key2");
EXPECT_TRUE(status.ok());
status = it3->Seek("b-key3");
EXPECT_TRUE(status.ok());
EXPECT_TRUE(evicted_normal_location->IsEvicted());
EXPECT_TRUE(evicted_before_start->IsEvicted());
EXPECT_TRUE(evicted_after_end->IsEvicted());
EXPECT_FALSE(evicted_before_start->IsValid());
EXPECT_TRUE(evicted_normal_location->IsValid());
EXPECT_FALSE(evicted_after_end->IsValid());
// Check we don't need to reload for just the key.
EXPECT_EQ("b-key1", evicted_normal_location->Key());
EXPECT_TRUE(evicted_normal_location->IsEvicted());
// Make sure iterators are reloaded correctly.
EXPECT_TRUE(evicted_normal_location->IsValid());
EXPECT_EQ("value1", evicted_normal_location->Value());
// The iterator isn't reloaded because it caches the value.
EXPECT_TRUE(evicted_normal_location->IsEvicted());
status = evicted_normal_location->Next();
EXPECT_TRUE(status.ok());
EXPECT_FALSE(evicted_normal_location->IsEvicted());
EXPECT_FALSE(evicted_before_start->IsValid());
EXPECT_FALSE(evicted_after_end->IsValid());
// And our |Value()| call purged the earlier iterator.
EXPECT_TRUE(it1->IsEvicted());
}
TEST_F(TransactionalLevelDBTransactionTest, IteratorReloadingNext) {
SetUpRealDatabase();
SetupLevelDBDatabase();
const std::string key1("b-key1");
const std::string key2("b-key2");
const std::string key3("b-key3");
const std::string key4("b-key4");
const std::string value("value");
Put(key1, value);
Put(key2, value);
Put(key3, value);
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction->CreateIterator();
it->Seek(std::string("b"));
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key1), 0) << it->Key() << ", " << key1;
// Remove key2, so the next key should be key3.
TransactionRemove(transaction.get(), key2);
it->Next();
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key3), 0) << it->Key() << ", " << key3;
// Add key4.
TransactionPut(transaction.get(), key4, value);
it->Next();
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key4), 0) << it->Key() << ", " << key4;
}
TEST_F(TransactionalLevelDBTransactionTest, IteratorReloadingPrev) {
SetUpRealDatabase();
SetupLevelDBDatabase();
const std::string key1("b-key1");
const std::string key2("b-key2");
const std::string key3("b-key3");
const std::string key4("b-key4");
const std::string value("value");
Put(key2, value);
Put(key3, value);
Put(key4, value);
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction->CreateIterator();
it->SeekToLast();
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key4), 0) << it->Key() << ", " << key4;
// Remove key3, so the prev key should be key2.
TransactionRemove(transaction.get(), key3);
it->Prev();
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key2), 0) << it->Key() << ", " << key2;
// Add key1.
TransactionPut(transaction.get(), key1, value);
it->Prev();
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key1), 0) << it->Key() << ", " << key1;
}
TEST_F(TransactionalLevelDBTransactionTest, IteratorSkipsScopesMetadata) {
SetUpRealDatabase();
SetupLevelDBDatabase();
const std::string key1("b-key1");
const std::string pre_key("0");
const std::string value("value");
Put(key1, value);
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction->CreateIterator();
// Should skip metadata, and go to key1.
it->Seek("");
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key1), 0) << it->Key() << ", " << key1;
// Should skip metadata, and go to the beginning.
it->Prev();
ASSERT_FALSE(it->IsValid());
TransactionRemove(transaction.get(), key1);
TransactionPut(transaction.get(), pre_key, value);
it->SeekToLast();
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), pre_key), 0) << it->Key() << ", " << pre_key;
}
TEST_F(TransactionalLevelDBTransactionTest, IteratorReflectsInitialChanges) {
SetUpRealDatabase();
SetupLevelDBDatabase();
const std::string key1("b-key1");
const std::string value("value");
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
TransactionPut(transaction.get(), key1, value);
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction->CreateIterator();
it->Seek("");
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key1), 0) << it->Key() << ", " << key1;
}
namespace {
enum RangePrepareMode {
DataInMemory,
DataInDatabase,
DataMixed,
};
} // namespace
class LevelDBTransactionRangeTest
: public TransactionalLevelDBTransactionTest,
public testing::WithParamInterface<RangePrepareMode> {
public:
LevelDBTransactionRangeTest() {}
void SetUp() override {
TransactionalLevelDBTransactionTest::SetUp();
SetUpRealDatabase();
SetupLevelDBDatabase();
switch (GetParam()) {
case DataInMemory:
transaction_ = CreateTransaction();
TransactionPut(transaction_.get(), key_before_range_, value_);
TransactionPut(transaction_.get(), range_start_, value_);
TransactionPut(transaction_.get(), key_in_range1_, value_);
TransactionPut(transaction_.get(), key_in_range2_, value_);
TransactionPut(transaction_.get(), range_end_, value_);
TransactionPut(transaction_.get(), key_after_range_, value_);
break;
case DataInDatabase:
Put(key_before_range_, value_);
Put(range_start_, value_);
Put(key_in_range1_, value_);
Put(key_in_range2_, value_);
Put(range_end_, value_);
Put(key_after_range_, value_);
transaction_ = CreateTransaction();
break;
case DataMixed:
Put(key_before_range_, value_);
Put(key_in_range1_, value_);
Put(range_end_, value_);
transaction_ = CreateTransaction();
TransactionPut(transaction_.get(), range_start_, value_);
TransactionPut(transaction_.get(), key_in_range2_, value_);
TransactionPut(transaction_.get(), key_after_range_, value_);
break;
}
}
protected:
const std::string key_before_range_ = "b1";
const std::string range_start_ = "b2";
const std::string key_in_range1_ = "b3";
const std::string key_in_range2_ = "b4";
const std::string range_end_ = "b5";
const std::string key_after_range_ = "b6";
const std::string value_ = "value";
scoped_refptr<TransactionalLevelDBTransaction> transaction_;
private:
DISALLOW_COPY_AND_ASSIGN(LevelDBTransactionRangeTest);
};
TEST_P(LevelDBTransactionRangeTest, RemoveRangeUpperClosed) {
leveldb::Status status;
status = transaction_->RemoveRange(
range_start_, range_end_,
LevelDBScopeDeletionMode::kImmediateWithRangeEndInclusive);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(TransactionHas(transaction_.get(), key_before_range_));
EXPECT_FALSE(TransactionHas(transaction_.get(), range_start_));
EXPECT_FALSE(TransactionHas(transaction_.get(), key_in_range1_));
EXPECT_FALSE(TransactionHas(transaction_.get(), key_in_range2_));
EXPECT_FALSE(TransactionHas(transaction_.get(), range_end_));
EXPECT_TRUE(TransactionHas(transaction_.get(), key_after_range_));
status = transaction_->Commit(/*sync_on_commit=*/false);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(Has(key_before_range_));
EXPECT_FALSE(Has(range_start_));
EXPECT_FALSE(Has(key_in_range1_));
EXPECT_FALSE(Has(key_in_range2_));
EXPECT_FALSE(Has(range_end_));
EXPECT_TRUE(Has(key_after_range_));
}
TEST_P(LevelDBTransactionRangeTest, RemoveRangeUpperOpen) {
leveldb::Status status;
status = transaction_->RemoveRange(
range_start_, range_end_,
LevelDBScopeDeletionMode::kImmediateWithRangeEndExclusive);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(TransactionHas(transaction_.get(), key_before_range_));
EXPECT_FALSE(TransactionHas(transaction_.get(), range_start_));
EXPECT_FALSE(TransactionHas(transaction_.get(), key_in_range1_));
EXPECT_FALSE(TransactionHas(transaction_.get(), key_in_range2_));
EXPECT_TRUE(TransactionHas(transaction_.get(), range_end_));
EXPECT_TRUE(TransactionHas(transaction_.get(), key_after_range_));
status = transaction_->Commit(/*sync_on_commit=*/false);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(Has(key_before_range_));
EXPECT_FALSE(Has(range_start_));
EXPECT_FALSE(Has(key_in_range1_));
EXPECT_FALSE(Has(key_in_range2_));
EXPECT_TRUE(Has(range_end_));
EXPECT_TRUE(Has(key_after_range_));
}
TEST_P(LevelDBTransactionRangeTest, RemoveRangeIteratorRetainsKey) {
leveldb::Status status;
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction_->CreateIterator();
status = it->Seek(key_in_range1_);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(it->IsValid());
EXPECT_EQ(Compare(key_in_range1_, it->Key()), 0);
status = it->Next();
EXPECT_TRUE(status.ok());
EXPECT_TRUE(it->IsValid());
EXPECT_EQ(Compare(key_in_range2_, it->Key()), 0);
status = transaction_->RemoveRange(
range_start_, range_end_,
LevelDBScopeDeletionMode::kImmediateWithRangeEndInclusive);
EXPECT_TRUE(status.ok());
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(key_in_range2_, it->Key()), 0)
<< key_in_range2_ << " != " << it->Key().as_string();
status = it->Next();
EXPECT_TRUE(status.ok());
ASSERT_TRUE(it->IsValid());
EXPECT_EQ(Compare(key_after_range_, it->Key()), 0)
<< key_after_range_ << " != " << it->Key().as_string();
status = transaction_->Commit(/*sync_on_commit=*/false);
EXPECT_TRUE(status.ok());
}
INSTANTIATE_TEST_SUITE_P(LevelDBTransactionRangeTests,
LevelDBTransactionRangeTest,
::testing::Values(DataInMemory,
DataInDatabase,
DataMixed));
TEST_F(TransactionalLevelDBTransactionTest, IteratorValueStaysTheSame) {
SetUpRealDatabase();
SetupLevelDBDatabase();
const std::string key1("b-key1");
const std::string value1("value1");
const std::string value2("value2");
Put(key1, value1);
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction->CreateIterator();
leveldb::Status s = it->Seek(std::string("b-key1"));
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
EXPECT_EQ(value1, it->Value());
TransactionPut(transaction.get(), key1, value2);
EXPECT_EQ(value1, it->Value());
it->EvictLevelDBIterator();
EXPECT_EQ(value1, it->Value());
s = it->Seek(std::string("b-key1"));
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
EXPECT_EQ(value2, it->Value());
}
TEST_F(TransactionalLevelDBTransactionTest, IteratorPutInvalidation) {
SetUpRealDatabase();
SetupLevelDBDatabase();
// This tests edge cases of using 'Put' with an iterator from the same
// transaction in all combinations of these edge cases:
// * 'next' value changes, and we go next,
// * the iterator was detached, and
// * 'prev' value changes and we go back.
const std::string key1("b-key1");
const std::string key2("b-key2");
const std::string key3("b-key3");
const std::string value1("value1");
const std::string value2("value2");
const std::string value3("value3");
Put(key1, value1);
Put(key2, value1);
Put(key3, value1);
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction->CreateIterator();
leveldb::Status s = it->Seek(std::string("b-key1"));
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
// Change the 'next' value.
TransactionPut(transaction.get(), key2, value2);
s = it->Next();
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
EXPECT_TRUE(KeysEqual(it->Key(), key2)) << it->Key() << ", " << key2;
EXPECT_EQ(value2, it->Value());
// Change the 'next' value and Detach.
TransactionPut(transaction.get(), key3, value2);
it->EvictLevelDBIterator();
s = it->Next();
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
EXPECT_TRUE(KeysEqual(it->Key(), key3)) << it->Key() << ", " << key3;
EXPECT_EQ(value2, it->Value());
// Seek past the end.
s = it->Next();
EXPECT_TRUE(s.ok());
ASSERT_FALSE(it->IsValid());
// Go to the last key (key3).
s = it->SeekToLast();
EXPECT_TRUE(s.ok());
ASSERT_TRUE(it->IsValid());
// Change the 'prev' value.
TransactionPut(transaction.get(), key2, value1);
s = it->Prev();
EXPECT_TRUE(s.ok());
EXPECT_TRUE(KeysEqual(it->Key(), key2)) << it->Key() << ", " << key2;
EXPECT_EQ(value1, it->Value());
// Change the 'prev' value and detach.
TransactionPut(transaction.get(), key1, value1);
it->EvictLevelDBIterator();
s = it->Prev();
EXPECT_TRUE(s.ok());
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(KeysEqual(it->Key(), key1)) << it->Key() << ", " << key1;
EXPECT_EQ(value1, it->Value());
s = it->Prev();
EXPECT_TRUE(s.ok());
ASSERT_FALSE(it->IsValid());
}
TEST_F(TransactionalLevelDBTransactionTest, IteratorRemoveInvalidation) {
SetUpRealDatabase();
SetupLevelDBDatabase();
// This tests edge cases of using 'Remove' with an iterator from the same
// transaction in all combinations of these edge cases:
// * 'next' key is removed, and we go next
// * the iterator was detached
// * 'prev' key was removed we go back
const std::string key0("b-key0");
const std::string key1("b-key1");
const std::string key2("b-key2");
const std::string key3("b-key3");
const std::string key4("b-key4");
const std::string key5("b-key5");
const std::string value("value");
Put(key0, value);
Put(key1, value);
Put(key2, value);
Put(key3, value);
Put(key4, value);
Put(key5, value);
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction->CreateIterator();
leveldb::Status s = it->Seek(std::string("b-key1"));
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
// Remove the 'next' value.
TransactionRemove(transaction.get(), key2);
s = it->Next();
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
EXPECT_TRUE(KeysEqual(it->Key(), key3)) << it->Key() << ", " << key3;
// Remove the 'next' value and detach.
TransactionRemove(transaction.get(), key4);
it->EvictLevelDBIterator();
s = it->Next();
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
EXPECT_TRUE(KeysEqual(it->Key(), key5)) << it->Key() << ", " << key5;
// Seek past the end.
s = it->Next();
EXPECT_TRUE(s.ok());
ASSERT_FALSE(it->IsValid());
// Go to the last key (key5).
s = it->SeekToLast();
EXPECT_TRUE(s.ok());
ASSERT_TRUE(it->IsValid());
// Remove the 'prev' value.
TransactionRemove(transaction.get(), key3);
s = it->Prev();
EXPECT_TRUE(s.ok());
EXPECT_TRUE(KeysEqual(it->Key(), key1)) << it->Key() << ", " << key1;
// Remove the 'prev' value and detach.
TransactionRemove(transaction.get(), key1);
it->EvictLevelDBIterator();
s = it->Prev();
EXPECT_TRUE(s.ok());
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(KeysEqual(it->Key(), key0)) << it->Key() << ", " << key0;
s = it->Prev();
EXPECT_TRUE(s.ok());
ASSERT_FALSE(it->IsValid());
}
TEST_F(TransactionalLevelDBTransactionTest, IteratorGoesInvalidAfterRemove) {
SetUpRealDatabase();
SetupLevelDBDatabase();
// Tests that the iterator correctly goes 'next' or 'prev' to invalid past a
// removed key, and tests that it does the same after being detached.
const std::string key1("b-key1");
const std::string key2("b-key2");
const std::string value("value");
Put(key1, value);
Put(key2, value);
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction->CreateIterator();
leveldb::Status s = it->Seek(std::string("b-key1"));
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
// Remove the 'next' value, and the iterator should go to the end.
TransactionRemove(transaction.get(), key2);
s = it->Next();
EXPECT_TRUE(s.ok());
ASSERT_FALSE(it->IsValid());
// Put key2 back, seek to it.
TransactionPut(transaction.get(), key2, value);
s = it->SeekToLast();
EXPECT_TRUE(s.ok());
ASSERT_TRUE(it->IsValid());
// Remove the 'prev' value.
TransactionRemove(transaction.get(), key1);
s = it->Prev();
EXPECT_TRUE(s.ok());
ASSERT_FALSE(it->IsValid());
// Put key1 back, seek to it.
TransactionPut(transaction.get(), key1, value);
s = it->Seek(std::string("b-key1"));
EXPECT_TRUE(s.ok());
ASSERT_TRUE(it->IsValid());
// Remove the 'next' value & detach the iterator.
TransactionRemove(transaction.get(), key2);
it->EvictLevelDBIterator();
s = it->Next();
EXPECT_TRUE(s.ok());
ASSERT_FALSE(it->IsValid());
// Put key2 back, seek to it.
TransactionPut(transaction.get(), key2, value);
s = it->SeekToLast();
EXPECT_TRUE(s.ok());
ASSERT_TRUE(it->IsValid());
// Remove the 'prev' value and detach the iterator.
TransactionRemove(transaction.get(), key1);
it->EvictLevelDBIterator();
s = it->Prev();
EXPECT_TRUE(s.ok());
ASSERT_FALSE(it->IsValid());
}
TEST_F(TransactionalLevelDBTransactionTest,
IteratorNextAfterRemovingCurrentKey) {
SetUpRealDatabase();
SetupLevelDBDatabase();
// This tests that the iterator reloading logic correctly handles not calling
// Next when it reloads after the current key was removed.
const std::string key1("b-key1");
const std::string key2("b-key2");
const std::string key3("b-key3");
const std::string value("value");
Put(key1, value);
Put(key2, value);
Put(key3, value);
scoped_refptr<TransactionalLevelDBTransaction> transaction =
CreateTransaction();
std::unique_ptr<TransactionalLevelDBIterator> it =
transaction->CreateIterator();
leveldb::Status s = it->Seek(std::string("b-key1"));
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
// Make sure the iterator is detached, and remove the current key.
it->EvictLevelDBIterator();
TransactionRemove(transaction.get(), key1);
// This call reloads the iterator at key "b-key1".
s = it->Next();
ASSERT_TRUE(it->IsValid());
EXPECT_TRUE(s.ok());
EXPECT_TRUE(KeysEqual(it->Key(), key2)) << it->Key() << ", " << key2;
}
} // namespace content