dc9d70de65
Summary: Optimistic transactions supporting begin/commit/rollback semantics. Currently relies on checking the memtable to determine if there are any collisions at commit time. Not yet implemented would be a way of enuring the memtable has some minimum amount of history so that we won't fail to commit when the memtable is empty. You should probably start with transaction.h to get an overview of what is currently supported. Test Plan: Added a new test, but still need to look into stress testing. Reviewers: yhchiang, igor, rven, sdong Reviewed By: sdong Subscribers: adamretter, MarkCallaghan, leveldb, dhruba Differential Revision: https://reviews.facebook.net/D33435
847 lines
22 KiB
C++
847 lines
22 KiB
C++
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under the BSD-style license found in the
|
|
// LICENSE file in the root directory of this source tree. An additional grant
|
|
// of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
#ifndef ROCKSDB_LITE
|
|
|
|
#include <string>
|
|
|
|
#include "rocksdb/db.h"
|
|
#include "rocksdb/utilities/optimistic_transaction.h"
|
|
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
|
#include "util/logging.h"
|
|
#include "util/testharness.h"
|
|
|
|
using std::string;
|
|
|
|
namespace rocksdb {
|
|
|
|
class OptimisticTransactionTest : public testing::Test {
|
|
public:
|
|
OptimisticTransactionDB* txn_db;
|
|
DB* db;
|
|
string dbname;
|
|
Options options;
|
|
|
|
OptimisticTransactionTest() {
|
|
options.create_if_missing = true;
|
|
options.max_write_buffer_number = 2;
|
|
dbname = test::TmpDir() + "/optimistic_transaction_testdb";
|
|
|
|
DestroyDB(dbname, options);
|
|
Status s = OptimisticTransactionDB::Open(options, dbname, &txn_db);
|
|
assert(s.ok());
|
|
db = txn_db->GetBaseDB();
|
|
}
|
|
|
|
~OptimisticTransactionTest() {
|
|
delete txn_db;
|
|
DestroyDB(dbname, options);
|
|
}
|
|
};
|
|
|
|
TEST_F(OptimisticTransactionTest, SuccessTest) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options;
|
|
string value;
|
|
Status s;
|
|
|
|
db->Put(write_options, Slice("foo"), Slice("bar"));
|
|
db->Put(write_options, Slice("foo2"), Slice("bar"));
|
|
|
|
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
txn->GetForUpdate(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "bar");
|
|
|
|
txn->Put(Slice("foo"), Slice("bar2"));
|
|
|
|
txn->GetForUpdate(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "bar2");
|
|
|
|
s = txn->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
db->Get(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "bar2");
|
|
|
|
delete txn;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, WriteConflictTest) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options;
|
|
string value;
|
|
Status s;
|
|
|
|
db->Put(write_options, "foo", "bar");
|
|
db->Put(write_options, "foo2", "bar");
|
|
|
|
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
txn->Put("foo", "bar2");
|
|
|
|
// This Put outside of a transaction will conflict with the previous write
|
|
s = db->Put(write_options, "foo", "barz");
|
|
ASSERT_OK(s);
|
|
|
|
s = db->Get(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "barz");
|
|
|
|
s = txn->Commit();
|
|
ASSERT_NOK(s); // Txn should not commit
|
|
|
|
// Verify that transaction did not write anything
|
|
db->Get(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "barz");
|
|
db->Get(read_options, "foo2", &value);
|
|
ASSERT_EQ(value, "bar");
|
|
|
|
delete txn;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, WriteConflictTest2) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options;
|
|
OptimisticTransactionOptions txn_options;
|
|
string value;
|
|
Status s;
|
|
|
|
db->Put(write_options, "foo", "bar");
|
|
db->Put(write_options, "foo2", "bar");
|
|
|
|
txn_options.set_snapshot = true;
|
|
OptimisticTransaction* txn =
|
|
txn_db->BeginTransaction(write_options, txn_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
// This Put outside of a transaction will conflict with a later write
|
|
s = db->Put(write_options, "foo", "barz");
|
|
ASSERT_OK(s);
|
|
|
|
txn->Put("foo", "bar2"); // Conflicts with write done after snapshot taken
|
|
|
|
s = db->Get(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "barz");
|
|
|
|
s = txn->Commit();
|
|
ASSERT_NOK(s); // Txn should not commit
|
|
|
|
// Verify that transaction did not write anything
|
|
db->Get(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "barz");
|
|
db->Get(read_options, "foo2", &value);
|
|
ASSERT_EQ(value, "bar");
|
|
|
|
delete txn;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, ReadConflictTest) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options, snapshot_read_options;
|
|
OptimisticTransactionOptions txn_options;
|
|
string value;
|
|
Status s;
|
|
|
|
db->Put(write_options, "foo", "bar");
|
|
db->Put(write_options, "foo2", "bar");
|
|
|
|
txn_options.set_snapshot = true;
|
|
OptimisticTransaction* txn =
|
|
txn_db->BeginTransaction(write_options, txn_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
txn->SetSnapshot();
|
|
snapshot_read_options.snapshot = txn->GetSnapshot();
|
|
|
|
txn->GetForUpdate(snapshot_read_options, "foo", &value);
|
|
ASSERT_EQ(value, "bar");
|
|
|
|
// This Put outside of a transaction will conflict with the previous read
|
|
s = db->Put(write_options, "foo", "barz");
|
|
ASSERT_OK(s);
|
|
|
|
s = db->Get(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "barz");
|
|
|
|
s = txn->Commit();
|
|
ASSERT_NOK(s); // Txn should not commit
|
|
|
|
// Verify that transaction did not write anything
|
|
txn->GetForUpdate(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "barz");
|
|
txn->GetForUpdate(read_options, "foo2", &value);
|
|
ASSERT_EQ(value, "bar");
|
|
|
|
delete txn;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, TxnOnlyTest) {
|
|
// Test to make sure transactions work when there are no other writes in an
|
|
// empty db.
|
|
|
|
WriteOptions write_options;
|
|
ReadOptions read_options;
|
|
string value;
|
|
Status s;
|
|
|
|
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
txn->Put("x", "y");
|
|
|
|
s = txn->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
delete txn;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, FlushTest) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options, snapshot_read_options;
|
|
string value;
|
|
Status s;
|
|
|
|
db->Put(write_options, Slice("foo"), Slice("bar"));
|
|
db->Put(write_options, Slice("foo2"), Slice("bar"));
|
|
|
|
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
snapshot_read_options.snapshot = txn->GetSnapshot();
|
|
|
|
txn->GetForUpdate(snapshot_read_options, "foo", &value);
|
|
ASSERT_EQ(value, "bar");
|
|
|
|
txn->Put(Slice("foo"), Slice("bar2"));
|
|
|
|
txn->GetForUpdate(snapshot_read_options, "foo", &value);
|
|
ASSERT_EQ(value, "bar2");
|
|
|
|
// Put a random key so we have a memtable to flush
|
|
s = db->Put(write_options, "dummy", "dummy");
|
|
ASSERT_OK(s);
|
|
|
|
// force a memtable flush
|
|
FlushOptions flush_ops;
|
|
db->Flush(flush_ops);
|
|
|
|
s = txn->Commit();
|
|
// txn should commit since the flushed table is still in MemtableList History
|
|
ASSERT_OK(s);
|
|
|
|
db->Get(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "bar2");
|
|
|
|
delete txn;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, FlushTest2) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options, snapshot_read_options;
|
|
string value;
|
|
Status s;
|
|
|
|
db->Put(write_options, Slice("foo"), Slice("bar"));
|
|
db->Put(write_options, Slice("foo2"), Slice("bar"));
|
|
|
|
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
snapshot_read_options.snapshot = txn->GetSnapshot();
|
|
|
|
txn->GetForUpdate(snapshot_read_options, "foo", &value);
|
|
ASSERT_EQ(value, "bar");
|
|
|
|
txn->Put(Slice("foo"), Slice("bar2"));
|
|
|
|
txn->GetForUpdate(snapshot_read_options, "foo", &value);
|
|
ASSERT_EQ(value, "bar2");
|
|
|
|
// Put a random key so we have a MemTable to flush
|
|
s = db->Put(write_options, "dummy", "dummy");
|
|
ASSERT_OK(s);
|
|
|
|
// force a memtable flush
|
|
FlushOptions flush_ops;
|
|
db->Flush(flush_ops);
|
|
|
|
// Put a random key so we have a MemTable to flush
|
|
s = db->Put(write_options, "dummy", "dummy2");
|
|
ASSERT_OK(s);
|
|
|
|
// force a memtable flush
|
|
db->Flush(flush_ops);
|
|
|
|
s = db->Put(write_options, "dummy", "dummy3");
|
|
ASSERT_OK(s);
|
|
|
|
// force a memtable flush
|
|
// Since our test db has max_write_buffer_number=2, this flush will cause
|
|
// the first memtable to get purged from the MemtableList history.
|
|
db->Flush(flush_ops);
|
|
|
|
s = txn->Commit();
|
|
// txn should not commit since MemTableList History is not large enough
|
|
ASSERT_NOK(s);
|
|
|
|
db->Get(read_options, "foo", &value);
|
|
ASSERT_EQ(value, "bar");
|
|
|
|
delete txn;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, NoSnapshotTest) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options;
|
|
string value;
|
|
Status s;
|
|
|
|
db->Put(write_options, "AAA", "bar");
|
|
|
|
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
// Modify key after transaction start
|
|
db->Put(write_options, "AAA", "bar1");
|
|
|
|
// Read and write without a snapshot
|
|
txn->GetForUpdate(read_options, "AAA", &value);
|
|
ASSERT_EQ(value, "bar1");
|
|
txn->Put("AAA", "bar2");
|
|
|
|
// Should commit since read/write was done after data changed
|
|
s = txn->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
txn->GetForUpdate(read_options, "AAA", &value);
|
|
ASSERT_EQ(value, "bar2");
|
|
|
|
delete txn;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, MultipleSnapshotTest) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options, snapshot_read_options;
|
|
string value;
|
|
Status s;
|
|
|
|
db->Put(write_options, "AAA", "bar");
|
|
db->Put(write_options, "BBB", "bar");
|
|
db->Put(write_options, "CCC", "bar");
|
|
|
|
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
db->Put(write_options, "AAA", "bar1");
|
|
|
|
// Read and write without a snapshot
|
|
txn->GetForUpdate(read_options, "AAA", &value);
|
|
ASSERT_EQ(value, "bar1");
|
|
txn->Put("AAA", "bar2");
|
|
|
|
// Modify BBB before snapshot is taken
|
|
db->Put(write_options, "BBB", "bar1");
|
|
|
|
txn->SetSnapshot();
|
|
snapshot_read_options.snapshot = txn->GetSnapshot();
|
|
|
|
// Read and write with snapshot
|
|
txn->GetForUpdate(snapshot_read_options, "BBB", &value);
|
|
ASSERT_EQ(value, "bar1");
|
|
txn->Put("BBB", "bar2");
|
|
|
|
db->Put(write_options, "CCC", "bar1");
|
|
|
|
// Set a new snapshot
|
|
txn->SetSnapshot();
|
|
snapshot_read_options.snapshot = txn->GetSnapshot();
|
|
|
|
// Read and write with snapshot
|
|
txn->GetForUpdate(snapshot_read_options, "CCC", &value);
|
|
ASSERT_EQ(value, "bar1");
|
|
txn->Put("CCC", "bar2");
|
|
|
|
s = txn->GetForUpdate(read_options, "AAA", &value);
|
|
ASSERT_OK(s);
|
|
ASSERT_EQ(value, "bar2");
|
|
s = txn->GetForUpdate(read_options, "BBB", &value);
|
|
ASSERT_OK(s);
|
|
ASSERT_EQ(value, "bar2");
|
|
s = txn->GetForUpdate(read_options, "CCC", &value);
|
|
ASSERT_OK(s);
|
|
ASSERT_EQ(value, "bar2");
|
|
|
|
s = db->Get(read_options, "AAA", &value);
|
|
ASSERT_OK(s);
|
|
ASSERT_EQ(value, "bar1");
|
|
s = db->Get(read_options, "BBB", &value);
|
|
ASSERT_OK(s);
|
|
ASSERT_EQ(value, "bar1");
|
|
s = db->Get(read_options, "CCC", &value);
|
|
ASSERT_OK(s);
|
|
ASSERT_EQ(value, "bar1");
|
|
|
|
s = txn->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
s = db->Get(read_options, "AAA", &value);
|
|
ASSERT_OK(s);
|
|
ASSERT_EQ(value, "bar2");
|
|
s = db->Get(read_options, "BBB", &value);
|
|
ASSERT_OK(s);
|
|
ASSERT_EQ(value, "bar2");
|
|
s = db->Get(read_options, "CCC", &value);
|
|
ASSERT_OK(s);
|
|
ASSERT_EQ(value, "bar2");
|
|
|
|
// verify that we track multiple writes to the same key at different snapshots
|
|
delete txn;
|
|
txn = txn_db->BeginTransaction(write_options);
|
|
|
|
// Potentially conflicting writes
|
|
db->Put(write_options, "ZZZ", "zzz");
|
|
db->Put(write_options, "XXX", "xxx");
|
|
|
|
txn->SetSnapshot();
|
|
|
|
OptimisticTransactionOptions txn_options;
|
|
txn_options.set_snapshot = true;
|
|
OptimisticTransaction* txn2 =
|
|
txn_db->BeginTransaction(write_options, txn_options);
|
|
txn2->SetSnapshot();
|
|
|
|
// This should not conflict in txn since the snapshot is later than the
|
|
// previous write (spoiler alert: it will later conflict with txn2).
|
|
txn->Put("ZZZ", "zzzz");
|
|
s = txn->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
delete txn;
|
|
|
|
// This will conflict since the snapshot is earlier than another write to ZZZ
|
|
txn2->Put("ZZZ", "xxxxx");
|
|
|
|
s = txn2->Commit();
|
|
ASSERT_NOK(s);
|
|
|
|
delete txn2;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, ColumnFamiliesTest) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options, snapshot_read_options;
|
|
OptimisticTransactionOptions txn_options;
|
|
string value;
|
|
Status s;
|
|
|
|
ColumnFamilyHandle *cfa, *cfb;
|
|
ColumnFamilyOptions cf_options;
|
|
|
|
// Create 2 new column families
|
|
s = db->CreateColumnFamily(cf_options, "CFA", &cfa);
|
|
ASSERT_OK(s);
|
|
s = db->CreateColumnFamily(cf_options, "CFB", &cfb);
|
|
ASSERT_OK(s);
|
|
|
|
delete cfa;
|
|
delete cfb;
|
|
delete txn_db;
|
|
|
|
// open DB with three column families
|
|
std::vector<ColumnFamilyDescriptor> column_families;
|
|
// have to open default column family
|
|
column_families.push_back(
|
|
ColumnFamilyDescriptor(kDefaultColumnFamilyName, ColumnFamilyOptions()));
|
|
// open the new column families
|
|
column_families.push_back(
|
|
ColumnFamilyDescriptor("CFA", ColumnFamilyOptions()));
|
|
column_families.push_back(
|
|
ColumnFamilyDescriptor("CFB", ColumnFamilyOptions()));
|
|
std::vector<ColumnFamilyHandle*> handles;
|
|
s = OptimisticTransactionDB::Open(options, dbname, column_families, &handles,
|
|
&txn_db);
|
|
ASSERT_OK(s);
|
|
db = txn_db->GetBaseDB();
|
|
|
|
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
txn->SetSnapshot();
|
|
snapshot_read_options.snapshot = txn->GetSnapshot();
|
|
|
|
txn_options.set_snapshot = true;
|
|
OptimisticTransaction* txn2 =
|
|
txn_db->BeginTransaction(write_options, txn_options);
|
|
ASSERT_TRUE(txn2);
|
|
|
|
// Write some data to the db
|
|
WriteBatch batch;
|
|
batch.Put("foo", "foo");
|
|
batch.Put(handles[1], "AAA", "bar");
|
|
batch.Put(handles[1], "AAAZZZ", "bar");
|
|
s = db->Write(write_options, &batch);
|
|
ASSERT_OK(s);
|
|
db->Delete(write_options, handles[1], "AAAZZZ");
|
|
|
|
// These keys do no conflict with existing writes since they're in
|
|
// different column families
|
|
txn->Delete("AAA");
|
|
txn->GetForUpdate(snapshot_read_options, handles[1], "foo", &value);
|
|
Slice key_slice("AAAZZZ");
|
|
Slice value_slices[2] = {Slice("bar"), Slice("bar")};
|
|
txn->Put(handles[2], SliceParts(&key_slice, 1), SliceParts(value_slices, 2));
|
|
|
|
// Txn should commit
|
|
s = txn->Commit();
|
|
ASSERT_OK(s);
|
|
s = db->Get(read_options, "AAA", &value);
|
|
ASSERT_TRUE(s.IsNotFound());
|
|
s = db->Get(read_options, handles[2], "AAAZZZ", &value);
|
|
ASSERT_EQ(value, "barbar");
|
|
|
|
Slice key_slices[3] = {Slice("AAA"), Slice("ZZ"), Slice("Z")};
|
|
Slice value_slice("barbarbar");
|
|
// This write will cause a conflict with the earlier batch write
|
|
txn2->Put(handles[1], SliceParts(key_slices, 3), SliceParts(&value_slice, 1));
|
|
|
|
txn2->Delete(handles[2], "XXX");
|
|
txn2->Delete(handles[1], "XXX");
|
|
s = txn2->GetForUpdate(snapshot_read_options, handles[1], "AAA", &value);
|
|
ASSERT_TRUE(s.IsNotFound());
|
|
|
|
// Verify txn did not commit
|
|
s = txn2->Commit();
|
|
ASSERT_NOK(s);
|
|
s = db->Get(read_options, handles[1], "AAAZZZ", &value);
|
|
ASSERT_EQ(value, "barbar");
|
|
|
|
delete txn;
|
|
delete txn2;
|
|
|
|
txn = txn_db->BeginTransaction(write_options, txn_options);
|
|
snapshot_read_options.snapshot = txn->GetSnapshot();
|
|
|
|
txn2 = txn_db->BeginTransaction(write_options, txn_options);
|
|
ASSERT_TRUE(txn);
|
|
|
|
std::vector<ColumnFamilyHandle*> multiget_cfh = {handles[1], handles[2],
|
|
handles[0], handles[2]};
|
|
std::vector<Slice> multiget_keys = {"AAA", "AAAZZZ", "foo", "foo"};
|
|
std::vector<std::string> values(4);
|
|
|
|
std::vector<Status> results = txn->MultiGetForUpdate(
|
|
snapshot_read_options, multiget_cfh, multiget_keys, &values);
|
|
ASSERT_OK(results[0]);
|
|
ASSERT_OK(results[1]);
|
|
ASSERT_OK(results[2]);
|
|
ASSERT_TRUE(results[3].IsNotFound());
|
|
ASSERT_EQ(values[0], "bar");
|
|
ASSERT_EQ(values[1], "barbar");
|
|
ASSERT_EQ(values[2], "foo");
|
|
|
|
txn->Delete(handles[2], "ZZZ");
|
|
txn->Put(handles[2], "ZZZ", "YYY");
|
|
txn->Put(handles[2], "ZZZ", "YYYY");
|
|
txn->Delete(handles[2], "ZZZ");
|
|
txn->Put(handles[2], "AAAZZZ", "barbarbar");
|
|
|
|
// Txn should commit
|
|
s = txn->Commit();
|
|
ASSERT_OK(s);
|
|
s = db->Get(read_options, handles[2], "ZZZ", &value);
|
|
ASSERT_TRUE(s.IsNotFound());
|
|
|
|
// Put a key which will conflict with the next txn using the previous snapshot
|
|
db->Put(write_options, handles[2], "foo", "000");
|
|
|
|
results = txn2->MultiGetForUpdate(snapshot_read_options, multiget_cfh,
|
|
multiget_keys, &values);
|
|
ASSERT_OK(results[0]);
|
|
ASSERT_OK(results[1]);
|
|
ASSERT_OK(results[2]);
|
|
ASSERT_TRUE(results[3].IsNotFound());
|
|
ASSERT_EQ(values[0], "bar");
|
|
ASSERT_EQ(values[1], "barbar");
|
|
ASSERT_EQ(values[2], "foo");
|
|
|
|
// Verify Txn Did not Commit
|
|
s = txn2->Commit();
|
|
ASSERT_NOK(s);
|
|
|
|
s = db->DropColumnFamily(handles[1]);
|
|
ASSERT_OK(s);
|
|
s = db->DropColumnFamily(handles[2]);
|
|
ASSERT_OK(s);
|
|
|
|
delete txn;
|
|
delete txn2;
|
|
|
|
for (auto handle : handles) {
|
|
delete handle;
|
|
}
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, EmptyTest) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options;
|
|
string value;
|
|
Status s;
|
|
|
|
s = db->Put(write_options, "aaa", "aaa");
|
|
ASSERT_OK(s);
|
|
|
|
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
|
s = txn->Commit();
|
|
ASSERT_OK(s);
|
|
delete txn;
|
|
|
|
txn = txn_db->BeginTransaction(write_options);
|
|
txn->Rollback();
|
|
delete txn;
|
|
|
|
txn = txn_db->BeginTransaction(write_options);
|
|
s = txn->GetForUpdate(read_options, "aaa", &value);
|
|
ASSERT_EQ(value, "aaa");
|
|
|
|
s = txn->Commit();
|
|
ASSERT_OK(s);
|
|
delete txn;
|
|
|
|
txn = txn_db->BeginTransaction(write_options);
|
|
txn->SetSnapshot();
|
|
s = txn->GetForUpdate(read_options, "aaa", &value);
|
|
ASSERT_EQ(value, "aaa");
|
|
|
|
s = db->Put(write_options, "aaa", "xxx");
|
|
s = txn->Commit();
|
|
ASSERT_NOK(s);
|
|
delete txn;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, PredicateManyPreceders) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options1, read_options2;
|
|
OptimisticTransactionOptions txn_options;
|
|
string value;
|
|
Status s;
|
|
|
|
txn_options.set_snapshot = true;
|
|
OptimisticTransaction* txn1 =
|
|
txn_db->BeginTransaction(write_options, txn_options);
|
|
read_options1.snapshot = txn1->GetSnapshot();
|
|
|
|
OptimisticTransaction* txn2 = txn_db->BeginTransaction(write_options);
|
|
txn2->SetSnapshot();
|
|
read_options2.snapshot = txn2->GetSnapshot();
|
|
|
|
std::vector<Slice> multiget_keys = {"1", "2", "3"};
|
|
std::vector<std::string> multiget_values;
|
|
|
|
std::vector<Status> results =
|
|
txn1->MultiGetForUpdate(read_options1, multiget_keys, &multiget_values);
|
|
ASSERT_TRUE(results[1].IsNotFound());
|
|
|
|
txn2->Put("2", "x");
|
|
|
|
s = txn2->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
multiget_values.clear();
|
|
results =
|
|
txn1->MultiGetForUpdate(read_options1, multiget_keys, &multiget_values);
|
|
ASSERT_TRUE(results[1].IsNotFound());
|
|
|
|
// should not commit since txn2 wrote a key txn has read
|
|
s = txn1->Commit();
|
|
ASSERT_NOK(s);
|
|
|
|
delete txn1;
|
|
delete txn2;
|
|
|
|
txn1 = txn_db->BeginTransaction(write_options, txn_options);
|
|
read_options1.snapshot = txn1->GetSnapshot();
|
|
|
|
txn2 = txn_db->BeginTransaction(write_options, txn_options);
|
|
read_options2.snapshot = txn2->GetSnapshot();
|
|
|
|
txn1->Put("4", "x");
|
|
|
|
txn2->Delete("4");
|
|
|
|
// txn1 can commit since txn2's delete hasn't happened yet (it's just batched)
|
|
s = txn1->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
s = txn2->GetForUpdate(read_options2, "4", &value);
|
|
ASSERT_TRUE(s.IsNotFound());
|
|
|
|
// txn2 cannot commit since txn1 changed "4"
|
|
s = txn2->Commit();
|
|
ASSERT_NOK(s);
|
|
|
|
delete txn1;
|
|
delete txn2;
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, LostUpdate) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options, read_options1, read_options2;
|
|
OptimisticTransactionOptions txn_options;
|
|
string value;
|
|
Status s;
|
|
|
|
// Test 2 transactions writing to the same key in multiple orders and
|
|
// with/without snapshots
|
|
|
|
OptimisticTransaction* txn1 = txn_db->BeginTransaction(write_options);
|
|
OptimisticTransaction* txn2 = txn_db->BeginTransaction(write_options);
|
|
|
|
txn1->Put("1", "1");
|
|
txn2->Put("1", "2");
|
|
|
|
s = txn1->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
s = txn2->Commit();
|
|
ASSERT_NOK(s);
|
|
|
|
delete txn1;
|
|
delete txn2;
|
|
|
|
txn_options.set_snapshot = true;
|
|
txn1 = txn_db->BeginTransaction(write_options, txn_options);
|
|
read_options1.snapshot = txn1->GetSnapshot();
|
|
|
|
txn2 = txn_db->BeginTransaction(write_options, txn_options);
|
|
read_options2.snapshot = txn2->GetSnapshot();
|
|
|
|
txn1->Put("1", "3");
|
|
txn2->Put("1", "4");
|
|
|
|
s = txn1->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
s = txn2->Commit();
|
|
ASSERT_NOK(s);
|
|
|
|
delete txn1;
|
|
delete txn2;
|
|
|
|
txn1 = txn_db->BeginTransaction(write_options, txn_options);
|
|
read_options1.snapshot = txn1->GetSnapshot();
|
|
|
|
txn2 = txn_db->BeginTransaction(write_options, txn_options);
|
|
read_options2.snapshot = txn2->GetSnapshot();
|
|
|
|
txn1->Put("1", "5");
|
|
s = txn1->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
txn2->Put("1", "6");
|
|
s = txn2->Commit();
|
|
ASSERT_NOK(s);
|
|
|
|
delete txn1;
|
|
delete txn2;
|
|
|
|
txn1 = txn_db->BeginTransaction(write_options, txn_options);
|
|
read_options1.snapshot = txn1->GetSnapshot();
|
|
|
|
txn2 = txn_db->BeginTransaction(write_options, txn_options);
|
|
read_options2.snapshot = txn2->GetSnapshot();
|
|
|
|
txn1->Put("1", "5");
|
|
s = txn1->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
txn2->SetSnapshot();
|
|
txn2->Put("1", "6");
|
|
s = txn2->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
delete txn1;
|
|
delete txn2;
|
|
|
|
txn1 = txn_db->BeginTransaction(write_options);
|
|
txn2 = txn_db->BeginTransaction(write_options);
|
|
|
|
txn1->Put("1", "7");
|
|
s = txn1->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
txn2->Put("1", "8");
|
|
s = txn2->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
delete txn1;
|
|
delete txn2;
|
|
|
|
s = db->Get(read_options, "1", &value);
|
|
ASSERT_OK(s);
|
|
ASSERT_EQ(value, "8");
|
|
}
|
|
|
|
TEST_F(OptimisticTransactionTest, UntrackedWrites) {
|
|
WriteOptions write_options;
|
|
ReadOptions read_options;
|
|
string value;
|
|
Status s;
|
|
|
|
// Verify transaction rollback works for untracked keys.
|
|
OptimisticTransaction* txn = txn_db->BeginTransaction(write_options);
|
|
txn->PutUntracked("untracked", "0");
|
|
txn->Rollback();
|
|
s = db->Get(read_options, "untracked", &value);
|
|
ASSERT_TRUE(s.IsNotFound());
|
|
|
|
delete txn;
|
|
txn = txn_db->BeginTransaction(write_options);
|
|
|
|
txn->Put("tracked", "1");
|
|
txn->PutUntracked("untracked", "1");
|
|
txn->MergeUntracked("untracked", "2");
|
|
txn->DeleteUntracked("untracked");
|
|
|
|
// Write to the untracked key outside of the transaction and verify
|
|
// it doesn't prevent the transaction from committing.
|
|
s = db->Put(write_options, "untracked", "x");
|
|
ASSERT_OK(s);
|
|
|
|
s = txn->Commit();
|
|
ASSERT_OK(s);
|
|
|
|
s = db->Get(read_options, "untracked", &value);
|
|
ASSERT_TRUE(s.IsNotFound());
|
|
|
|
delete txn;
|
|
txn = txn_db->BeginTransaction(write_options);
|
|
|
|
txn->Put("tracked", "10");
|
|
txn->PutUntracked("untracked", "A");
|
|
|
|
// Write to tracked key outside of the transaction and verify that the
|
|
// untracked keys are not written when the commit fails.
|
|
s = db->Delete(write_options, "tracked");
|
|
|
|
s = txn->Commit();
|
|
ASSERT_NOK(s);
|
|
|
|
s = db->Get(read_options, "untracked", &value);
|
|
ASSERT_TRUE(s.IsNotFound());
|
|
|
|
delete txn;
|
|
}
|
|
|
|
} // namespace rocksdb
|
|
|
|
int main(int argc, char** argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|
|
|
|
#endif // ROCKSDB_LITE
|