From 9060078609232082a989c7906e8d53b6c3b490b4 Mon Sep 17 00:00:00 2001 From: Greg Soltis Date: Thu, 15 Mar 2018 16:52:13 -0700 Subject: Implements transactions for leveldb (#881) * Start work on leveldb transactions * Style * Working API. Not plumbed in yet * Move files into correct place * Wrangling file locations and associations * Tests pass * Add some comments * style * Fix copyright * Rewrite iterator internals to handle deletion-while-iterating. Also add tests for same * Switch to strings instead of slices * Style * More style fixes * Response to feedback before updating docs * Style * Add comment * Initialize version_ * Satisfy the linter * Fix style * snake_case * More snakes * LevelDBTransaction -> LevelDbTransaction --- .../Example/Firestore.xcodeproj/project.pbxproj | 6 + .../Tests/Local/FSTLevelDBTransactionTests.mm | 274 +++++++++++++++++++++ .../firestore/local/leveldb_transaction.cc | 211 ++++++++++++++++ .../firebase/firestore/local/leveldb_transaction.h | 204 +++++++++++++++ 4 files changed, 695 insertions(+) create mode 100644 Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm create mode 100644 Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc create mode 100644 Firestore/core/src/firebase/firestore/local/leveldb_transaction.h diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 3b1c328..f80bd8c 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 132E3E53179DE287D875F3F2 /* FSTLevelDBTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */; }; 3B843E4C1F3A182900548890 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; }; 5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5436F32320008FAD006E51E3 /* string_printf_test.cc */; }; 5467FB01203E5717009C9584 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; @@ -229,6 +230,7 @@ /* Begin PBXFileReference section */ 04DF37A117F88A9891379ED6 /* Pods-Firestore_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.release.xcconfig"; sourceTree = ""; }; 12F4357299652983A615F886 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBTransactionTests.mm; sourceTree = ""; }; 245812330F6A31632BB4B623 /* Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftBuildTest.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = remote_store_spec_test.json; sourceTree = ""; }; @@ -541,6 +543,8 @@ 6003F58C195388D20070C39A /* Frameworks */, 6003F58B195388D20070C39A /* Products */, A47A1BF74A48BCAEAFBCBF1E /* Pods */, + 132E3B6B902D5CBD779D901A, + 132E37F450D23EF90B2352D9, ); sourceTree = ""; }; @@ -744,6 +748,7 @@ 5492E0902021552B00B64F25 /* FSTRemoteDocumentChangeBufferTests.mm */, 5492E09B2021552C00B64F25 /* FSTWriteGroupTests.mm */, 5492E0932021552B00B64F25 /* StringViewTests.mm */, + 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */, ); path = Local; sourceTree = ""; @@ -1540,6 +1545,7 @@ 5492E057202154AB00B64F25 /* FIRSnapshotMetadataTests.mm in Sources */, 54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */, 5492E0BE2021555100B64F25 /* FSTMutationTests.mm in Sources */, + 132E3E53179DE287D875F3F2 /* FSTLevelDBTransactionTests.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm new file mode 100644 index 0000000..704f8c6 --- /dev/null +++ b/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm @@ -0,0 +1,274 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h" + +#import +#include +#include +#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h" +#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h" + +NS_ASSUME_NONNULL_BEGIN + +using leveldb::DB; +using leveldb::Options; +using leveldb::ReadOptions; +using leveldb::WriteOptions; +using leveldb::Status; +using firebase::firestore::local::LevelDbTransaction; + +@interface FSTLevelDBTransactionTests : XCTestCase +@end + +@implementation FSTLevelDBTransactionTests { + std::shared_ptr _db; +} + +- (void)setUp { + Options options; + options.error_if_exists = true; + options.create_if_missing = true; + + NSString *dir = [FSTPersistenceTestHelpers levelDBDir]; + DB *db; + Status status = DB::Open(options, [dir UTF8String], &db); + XCTAssert(status.ok(), @"Failed to create db: %s", status.ToString().c_str()); + _db.reset(db); +} + +- (void)tearDown { + _db.reset(); +} + +- (void)testCreateTransaction { + LevelDbTransaction transaction(_db.get()); + std::string key = "key1"; + + transaction.Put(key, "value"); + std::unique_ptr iter(transaction.NewIterator()); + iter->Seek(key); + XCTAssertEqual(key, iter->key()); + iter->Next(); + XCTAssertFalse(iter->Valid()); +} + +- (void)testCanReadCommittedAndMutations { + const std::string committed_key1 = "c_key1"; + const std::string committed_value1 = "c_value1"; + const WriteOptions &writeOptions = LevelDbTransaction::DefaultWriteOptions(); + // add two things committed, mutate one, add another mutation + // verify you can get the original committed, the mutation, and the addition + Status status = _db->Put(writeOptions, committed_key1, committed_value1); + XCTAssertTrue(status.ok()); + + const std::string committed_key2 = "c_key2"; + const std::string committed_value2 = "c_value2"; + status = _db->Put(writeOptions, committed_key2, committed_value2); + XCTAssertTrue(status.ok()); + + LevelDbTransaction transaction(_db.get()); + const std::string mutation_key1 = "m_key1"; + const std::string mutation_value1 = "m_value1"; + transaction.Put(mutation_key1, mutation_value1); + + const std::string mutation_key2 = committed_key2; + const std::string mutation_value2 = "m_value2"; + transaction.Put(mutation_key2, mutation_value2); + + std::string value; + status = transaction.Get(committed_key1, &value); + XCTAssertTrue(status.ok()); + XCTAssertEqual(value, committed_value1); + + status = transaction.Get(mutation_key1, &value); + XCTAssertTrue(status.ok()); + XCTAssertEqual(value, mutation_value1); + + status = transaction.Get(committed_key2, &value); + XCTAssertTrue(status.ok()); + XCTAssertEqual(value, mutation_value2); +} + +- (void)testDeleteCommitted { + // add something committed, delete it, verify you can't read it + for (int i = 0; i < 3; ++i) { + Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i), + "value_" + std::to_string(i)); + XCTAssertTrue(status.ok()); + } + LevelDbTransaction transaction(_db.get()); + transaction.Put("key_1", "new_value"); + std::string value; + Status status = transaction.Get("key_1", &value); + XCTAssertTrue(status.ok()); + XCTAssertEqual(value, "new_value"); + + transaction.Delete("key_1"); + status = transaction.Get("key_1", &value); + XCTAssertTrue(status.IsNotFound()); + + LevelDbTransaction::Iterator iter(&transaction); + iter.Seek(""); + XCTAssertEqual(iter.key(), "key_0"); + iter.Next(); + XCTAssertEqual(iter.key(), "key_2"); + iter.Next(); + XCTAssertFalse(iter.Valid()); +} + +- (void)testMutateDeleted { + // delete something, then mutate it, then read it. + // Also include an actual deletion + for (int i = 0; i < 4; ++i) { + Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i), + "value_" + std::to_string(i)); + XCTAssertTrue(status.ok()); + } + std::string value; + LevelDbTransaction transaction(_db.get()); + transaction.Delete("key_1"); + Status status = transaction.Get("key_1", &value); + XCTAssertTrue(status.IsNotFound()); + + transaction.Put("key_1", "new_value"); + status = transaction.Get("key_1", &value); + XCTAssertTrue(status.ok()); + XCTAssertEqual(value, "new_value"); + + transaction.Delete("key_3"); + + LevelDbTransaction::Iterator iter(&transaction); + iter.Seek(""); + XCTAssertEqual(iter.key(), "key_0"); + iter.Next(); + XCTAssertEqual(iter.key(), "key_1"); + XCTAssertEqual(iter.value(), "new_value"); + iter.Next(); + XCTAssertEqual(iter.key(), "key_2"); + iter.Next(); + XCTAssertFalse(iter.Valid()); + + // Commit, then check underlying db. + transaction.Commit(); + + const ReadOptions &readOptions = LevelDbTransaction::DefaultReadOptions(); + status = _db->Get(readOptions, "key_0", &value); + XCTAssertTrue(status.ok()); + XCTAssertEqual("value_0", value); + + status = _db->Get(readOptions, "key_1", &value); + XCTAssertTrue(status.ok()); + XCTAssertEqual("new_value", value); + + status = _db->Get(readOptions, "key_2", &value); + XCTAssertTrue(status.ok()); + XCTAssertEqual("value_2", value); + + status = _db->Get(readOptions, "key_3", &value); + XCTAssertTrue(status.IsNotFound()); +} + +- (void)testProtobufSupport { + LevelDbTransaction transaction(_db.get()); + + FSTPBTarget *target = [FSTPBTarget message]; + target.targetId = 1; + target.lastListenSequenceNumber = 2; + + std::string key("theKey"); + transaction.Put(key, target); + + std::string value; + Status status = transaction.Get("theKey", &value); + NSData *result = + [[NSData alloc] initWithBytesNoCopy:(void *)value.data() length:value.size() freeWhenDone:NO]; + NSError *error; + FSTPBTarget *parsed = [FSTPBTarget parseFromData:result error:&error]; + XCTAssertNil(error); + XCTAssertTrue([target isEqual:parsed]); +} + +- (void)testCanIterateAndDelete { + LevelDbTransaction transaction(_db.get()); + + for (int i = 0; i < 4; ++i) { + transaction.Put("key_" + std::to_string(i), "value_" + std::to_string(i)); + } + + std::unique_ptr it(transaction.NewIterator()); + it->Seek("key_0"); + for (int i = 0; i < 4; ++i) { + XCTAssertTrue(it->Valid()); + const absl::string_view &key = it->key(); + std::string expected = "key_" + std::to_string(i); + XCTAssertEqual(expected, key); + transaction.Delete(key); + it->Next(); + } +} + +- (void)testCanIterateFromDeletionToCommitted { + // Write keys key_0 and key_1 + for (int i = 0; i < 2; ++i) { + Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i), + "value_" + std::to_string(i)); + XCTAssertTrue(status.ok()); + } + + // Create a transaction, iterate, deleting key_0. Verify we still iterate key_1. + LevelDbTransaction transaction(_db.get()); + std::unique_ptr it(transaction.NewIterator()); + it->Seek("key_0"); + XCTAssertTrue(it->Valid()); + XCTAssertEqual("key_0", it->key()); + transaction.Delete("key_0"); + it->Next(); + XCTAssertTrue(it->Valid()); + XCTAssertEqual("key_1", it->key()); + it->Next(); + XCTAssertFalse(it->Valid()); +} + +- (void)testDeletingAheadOfAnIterator { + // Write keys + for (int i = 0; i < 4; ++i) { + Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i), + "value_" + std::to_string(i)); + XCTAssertTrue(status.ok()); + } + + // Create a transaction, iterate to key_1, delete key_2. Verify we still iterate key_3. + LevelDbTransaction transaction(_db.get()); + std::unique_ptr it(transaction.NewIterator()); + it->Seek("key_0"); + XCTAssertTrue(it->Valid()); + XCTAssertEqual("key_0", it->key()); + it->Next(); + XCTAssertTrue(it->Valid()); + XCTAssertEqual("key_1", it->key()); + transaction.Delete("key_2"); + it->Next(); + XCTAssertTrue(it->Valid()); + XCTAssertEqual("key_3", it->key()); + XCTAssertTrue(it->Valid()); + it->Next(); + XCTAssertFalse(it->Valid()); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc new file mode 100644 index 0000000..af72716 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.cc @@ -0,0 +1,211 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h" + +#include + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +using leveldb::DB; +using leveldb::ReadOptions; +using leveldb::Slice; +using leveldb::Status; +using leveldb::WriteBatch; +using leveldb::WriteOptions; + +namespace firebase { +namespace firestore { +namespace local { + +LevelDbTransaction::Iterator::Iterator(LevelDbTransaction* txn) + : db_iter_(txn->db_->NewIterator(txn->read_options_)), + last_version_(txn->version_), + txn_(txn), + mutations_iter_(txn->mutations_.begin()), + current_(), + is_mutation_(false), + // Iterator doesn't really point to anything yet, so is + // invalid + is_valid_(false) { +} + +void LevelDbTransaction::Iterator::UpdateCurrent() { + bool mutation_is_valid = mutations_iter_ != txn_->mutations_.end(); + is_valid_ = mutation_is_valid || db_iter_->Valid(); + + if (is_valid_) { + if (!mutation_is_valid) { + is_mutation_ = false; + } else if (!db_iter_->Valid()) { + is_mutation_ = true; + } else { + // Both iterators are valid. If the leveldb key is equal to or greater + // than the current mutation key, we are looking at a mutation next. It's + // either sooner in the iteration or directly shadowing the underlying + // committed value in leveldb. + is_mutation_ = db_iter_->key().compare(mutations_iter_->first) >= 0; + } + if (is_mutation_) { + current_ = *mutations_iter_; + } else { + current_ = {db_iter_->key().ToString(), db_iter_->value().ToString()}; + } + } +} + +void LevelDbTransaction::Iterator::Seek(const std::string& key) { + db_iter_->Seek(key); + for (; db_iter_->Valid() && IsDeleted(db_iter_->key()); db_iter_->Next()) { + } + mutations_iter_ = txn_->mutations_.lower_bound(key); + UpdateCurrent(); + last_version_ = txn_->version_; +} + +absl::string_view LevelDbTransaction::Iterator::key() { + FIREBASE_ASSERT_MESSAGE(Valid(), "key() called on invalid iterator"); + return current_.first; +} + +absl::string_view LevelDbTransaction::Iterator::value() { + FIREBASE_ASSERT_MESSAGE(Valid(), "value() called on invalid iterator"); + return current_.second; +} + +bool LevelDbTransaction::Iterator::IsDeleted(leveldb::Slice slice) { + return txn_->deletions_.find(slice.ToString()) != txn_->deletions_.end(); +} + +bool LevelDbTransaction::Iterator::SyncToTransaction() { + if (last_version_ < txn_->version_) { + // Intentionally copying here since Seek() may update current_. We need the + // copy to do the comparison below. + const std::string current_key = current_.first; + Seek(current_key); + // If we advanced, we don't need to advance again. + return is_valid_ && current_.first > current_key; + } else { + return false; + } +} + +void LevelDbTransaction::Iterator::AdvanceLDB() { + do { + db_iter_->Next(); + } while (db_iter_->Valid() && IsDeleted(db_iter_->key())); +} + +void LevelDbTransaction::Iterator::Next() { + FIREBASE_ASSERT_MESSAGE(Valid(), "Next() called on invalid iterator"); + bool advanced = SyncToTransaction(); + if (!advanced) { + if (is_mutation_) { + // A mutation might be shadowing leveldb. If so, advance both. + if (db_iter_->Valid() && db_iter_->key() == mutations_iter_->first) { + AdvanceLDB(); + } + ++mutations_iter_; + } else { + AdvanceLDB(); + } + UpdateCurrent(); + } +} + +bool LevelDbTransaction::Iterator::Valid() { + return is_valid_; +} + +LevelDbTransaction::LevelDbTransaction(DB* db, + const ReadOptions& read_options, + const WriteOptions& write_options) + : db_(db), + mutations_(), + deletions_(), + read_options_(read_options), + write_options_(write_options), + version_(0) { +} + +const ReadOptions& LevelDbTransaction::DefaultReadOptions() { + static ReadOptions options = ([]() { + ReadOptions read_options; + read_options.verify_checksums = true; + return read_options; + })(); + return options; +} + +const WriteOptions& LevelDbTransaction::DefaultWriteOptions() { + static WriteOptions options; + return options; +} + +void LevelDbTransaction::Put(const absl::string_view& key, + const absl::string_view& value) { + std::string key_string(key); + std::string value_string(value); + mutations_[key_string] = value_string; + deletions_.erase(key_string); + version_++; +} + +LevelDbTransaction::Iterator* LevelDbTransaction::NewIterator() { + return new LevelDbTransaction::Iterator(this); +} + +Status LevelDbTransaction::Get(const absl::string_view& key, + std::string* value) { + std::string key_string(key); + if (deletions_.find(key_string) != deletions_.end()) { + return Status::NotFound(key_string + " is not present in the transaction"); + } else { + Mutations::iterator iter(mutations_.find(key_string)); + if (iter != mutations_.end()) { + *value = iter->second; + return Status::OK(); + } else { + return db_->Get(read_options_, key_string, value); + } + } +} + +void LevelDbTransaction::Delete(const absl::string_view& key) { + std::string to_delete(key); + deletions_.insert(to_delete); + mutations_.erase(to_delete); + version_++; +} + +void LevelDbTransaction::Commit() { + WriteBatch batch; + for (auto it = deletions_.begin(); it != deletions_.end(); it++) { + batch.Delete(*it); + } + + for (auto it = mutations_.begin(); it != mutations_.end(); it++) { + batch.Put(it->first, it->second); + } + + Status status = db_->Write(write_options_, &batch); + FIREBASE_ASSERT_MESSAGE(status.ok(), "Failed to commit transaction: %s", + status.ToString().c_str()); +} + +} // namespace local +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h new file mode 100644 index 0000000..015a6cb --- /dev/null +++ b/Firestore/core/src/firebase/firestore/local/leveldb_transaction.h @@ -0,0 +1,204 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_TRANSACTION_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_TRANSACTION_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#if __OBJC__ +#import +#endif + +namespace firebase { +namespace firestore { +namespace local { + +/** + * LevelDBTransaction tracks pending changes to entries in leveldb, including + * deletions. It also provides an Iterator to traverse a merged view of pending + * changes and committed values. + */ +class LevelDbTransaction { + using Deletions = std::set; + using Mutations = std::map; + + public: + /** + * Iterator iterates over a merged view of pending changes from the + * transaction and any unchanged values in the underlying leveldb instance. + */ + class Iterator { + public: + explicit Iterator(LevelDbTransaction* txn); + + /** + * Returns true if this iterator points to an entry + */ + bool Valid(); + + /** + * Seeks this iterator to the first key equal to or greater than the given + * key + */ + void Seek(const std::string& key); + + /** + * Advances the iterator to the next entry + */ + void Next(); + + /** + * Returns the key of the current entry + */ + absl::string_view key(); + + /** + * Returns the value of the current entry + */ + absl::string_view value(); + + private: + /** + * Advances to the next non-deleted key in leveldb. + */ + void AdvanceLDB(); + + /** + * Returns true if the given slice matches a key present in the deletions_ + * set. + */ + bool IsDeleted(leveldb::Slice slice); + + /** + * Syncs with the underlying transaction. If the transaction has been + * updated, the mutation iterator may need to be reset. Returns true if this + * resulted in moving to a new underlying entry (i.e. the entry represented + * by current_ was deleted). + */ + bool SyncToTransaction(); + + /** + * Given the current state of the internal iterators, set is_valid_, + * is_mutation_, and current_. + */ + void UpdateCurrent(); + + std::unique_ptr db_iter_; + + // The last observed version of the underlying transaction + int32_t last_version_; + // The underlying transaction. + LevelDbTransaction* txn_; + Mutations::iterator mutations_iter_; + // We save the current key and value so that once an iterator is Valid(), it + // remains so at least until the next call to Seek() or Next(), even if the + // underlying data is deleted. + std::pair current_; + // True if current_ represents an entry in the mutations_ map, rather than + // committed data. + bool is_mutation_; + // True if the iterator pointed to a valid entry the last time Next() or + // Seek() was called. + bool is_valid_; + }; + + explicit LevelDbTransaction( + leveldb::DB* db, + const leveldb::ReadOptions& read_options = DefaultReadOptions(), + const leveldb::WriteOptions& write_options = DefaultWriteOptions()); + + LevelDbTransaction(const LevelDbTransaction& other) = delete; + + LevelDbTransaction& operator=(const LevelDbTransaction& other) = delete; + + /** + * Returns a default set of ReadOptions + */ + static const leveldb::ReadOptions& DefaultReadOptions(); + + /** + * Returns a default set of WriteOptions + */ + static const leveldb::WriteOptions& DefaultWriteOptions(); + + /** + * Remove the database entry (if any) for "key". It is not an error if "key" + * did not exist in the database. + */ + void Delete(const absl::string_view& key); + +#if __OBJC__ + /** + * Schedules the row identified by `key` to be set to the given protocol + * buffer message when this transaction commits. + */ + void Put(const absl::string_view& key, GPBMessage* message) { + NSData* data = [message data]; + std::string key_string(key); + mutations_[key_string] = std::string((const char*)data.bytes, data.length); + version_++; + } +#endif + + /** + * Schedules the row identified by `key` to be set to `value` when this + * transaction commits. + */ + void Put(const absl::string_view& key, const absl::string_view& value); + + /** + * Sets the contents of `value` to the latest known value for the given key, + * including any pending mutations and `Status::OK` is returned. If the key + * doesn't exist in leveldb, or it is scheduled for deletion in this + * transaction, `Status::NotFound` is returned. + */ + leveldb::Status Get(const absl::string_view& key, std::string* value); + + /** + * Returns a new Iterator over the pending changes in this transaction, merged + * with the existing values already in leveldb. + */ + Iterator* NewIterator(); + + /** + * Commits the transaction. All pending changes are written. The transaction + * should not be used after calling this method. + */ + void Commit(); + + private: + leveldb::DB* db_; + Mutations mutations_; + Deletions deletions_; + leveldb::ReadOptions read_options_; + leveldb::WriteOptions write_options_; + int32_t version_; +}; + +} // namespace local +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_TRANSACTION_H_ -- cgit v1.2.3