aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/Example
diff options
context:
space:
mode:
authorGravatar Greg Soltis <gsoltis@google.com>2018-03-15 16:52:13 -0700
committerGravatar GitHub <noreply@github.com>2018-03-15 16:52:13 -0700
commit9060078609232082a989c7906e8d53b6c3b490b4 (patch)
tree15aa01c3858f12ac24257be0e298079658222ab7 /Firestore/Example
parent52334d251b167c150a1d0352f92f926f9a80f392 (diff)
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
Diffstat (limited to 'Firestore/Example')
-rw-r--r--Firestore/Example/Firestore.xcodeproj/project.pbxproj6
-rw-r--r--Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm274
2 files changed, 280 insertions, 0 deletions
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 = "<group>"; };
12F4357299652983A615F886 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
+ 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBTransactionTests.mm; sourceTree = "<group>"; };
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 = "<group>"; };
@@ -541,6 +543,8 @@
6003F58C195388D20070C39A /* Frameworks */,
6003F58B195388D20070C39A /* Products */,
A47A1BF74A48BCAEAFBCBF1E /* Pods */,
+ 132E3B6B902D5CBD779D901A,
+ 132E37F450D23EF90B2352D9,
);
sourceTree = "<group>";
};
@@ -744,6 +748,7 @@
5492E0902021552B00B64F25 /* FSTRemoteDocumentChangeBufferTests.mm */,
5492E09B2021552C00B64F25 /* FSTWriteGroupTests.mm */,
5492E0932021552B00B64F25 /* StringViewTests.mm */,
+ 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */,
);
path = Local;
sourceTree = "<group>";
@@ -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 <XCTest/XCTest.h>
+#include <absl/strings/string_view.h>
+#include <leveldb/db.h>
+#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> _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<LevelDbTransaction::Iterator> 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<LevelDbTransaction::Iterator> 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<LevelDbTransaction::Iterator> 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<LevelDbTransaction::Iterator> 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