Skip to content

Migrate FSTLevelDBMigrations to C++ LevelDbMigrations #1714

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@

#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
#import "Firestore/Source/Local/FSTLevelDB.h"
#import "Firestore/Source/Local/FSTLevelDBMigrations.h"
#import "Firestore/Source/Local/FSTLevelDBMutationQueue.h"
#import "Firestore/Source/Local/FSTLevelDBQueryCache.h"

#include "Firestore/core/src/firebase/firestore/local/leveldb_key.h"
#include "Firestore/core/src/firebase/firestore/local/leveldb_migrations.h"
#include "Firestore/core/src/firebase/firestore/util/ordered_code.h"
#include "Firestore/core/src/firebase/firestore/util/status.h"
#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
Expand All @@ -39,6 +39,7 @@

using firebase::firestore::FirestoreErrorCode;
using firebase::firestore::local::LevelDbDocumentTargetKey;
using firebase::firestore::local::LevelDbMigrations;
using firebase::firestore::local::LevelDbMutationKey;
using firebase::firestore::local::LevelDbMutationQueueKey;
using firebase::firestore::local::LevelDbQueryTargetKey;
Expand All @@ -54,6 +55,8 @@
using leveldb::Options;
using leveldb::Status;

using SchemaVersion = LevelDbMigrations::SchemaVersion;

@interface FSTLevelDBMigrationsTests : XCTestCase
@end

Expand All @@ -80,7 +83,7 @@ - (void)tearDown {
- (void)testAddsTargetGlobal {
FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()];
XCTAssertNil(metadata, @"Not expecting metadata yet, we should have an empty db");
[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get()];
LevelDbMigrations::RunMigrations(_db.get());

metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()];
XCTAssertNotNil(metadata, @"Migrations should have added the metadata");
Expand All @@ -89,18 +92,16 @@ - (void)testAddsTargetGlobal {
- (void)testSetsVersionNumber {
{
LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber before");
FSTLevelDBSchemaVersion initial =
[FSTLevelDBMigrations schemaVersionWithTransaction:&transaction];
SchemaVersion initial = LevelDbMigrations::ReadSchemaVersion(&transaction);
XCTAssertEqual(0, initial, "No version should be equivalent to 0");
}

{
// Pick an arbitrary high migration number and migrate to it.
[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get()];
LevelDbMigrations::RunMigrations(_db.get());

LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber after");
FSTLevelDBSchemaVersion actual =
[FSTLevelDBMigrations schemaVersionWithTransaction:&transaction];
SchemaVersion actual = LevelDbMigrations::ReadSchemaVersion(&transaction);
XCTAssertGreaterThan(actual, 0, @"Expected to migrate to a schema version > 0");
}
}
Expand Down Expand Up @@ -143,7 +144,7 @@ - (void)testDropsTheQueryCache {
LevelDbMutationKey::Key(userID, batchID),
};

[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:2];
LevelDbMigrations::RunMigrations(_db.get(), 2);
{
// Setup some targets to be counted in the migration.
LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCache setup");
Expand All @@ -156,7 +157,7 @@ - (void)testDropsTheQueryCache {
transaction.Commit();
}

[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:3];
LevelDbMigrations::RunMigrations(_db.get(), 3);
{
LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCache");
for (const std::string &key : targetKeys) {
Expand All @@ -173,7 +174,7 @@ - (void)testDropsTheQueryCache {
}

- (void)testDropsTheQueryCacheWithThousandsOfEntries {
[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:2];
LevelDbMigrations::RunMigrations(_db.get(), 2);
{
// Setup some targets to be destroyed.
LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCacheWithThousandsOfEntries setup");
Expand All @@ -183,7 +184,7 @@ - (void)testDropsTheQueryCacheWithThousandsOfEntries {
transaction.Commit();
}

[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:3];
LevelDbMigrations::RunMigrations(_db.get(), 3);
{
LevelDbTransaction transaction(_db.get(), "Verify");
std::string prefix = LevelDbTargetKey::KeyPrefix();
Expand Down
5 changes: 3 additions & 2 deletions Firestore/Source/Local/FSTLevelDB.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
#import "FIRFirestoreErrors.h"
#import "Firestore/Source/Core/FSTListenSequence.h"
#import "Firestore/Source/Local/FSTLRUGarbageCollector.h"
#import "Firestore/Source/Local/FSTLevelDBMigrations.h"
#import "Firestore/Source/Local/FSTLevelDBMutationQueue.h"
#import "Firestore/Source/Local/FSTLevelDBQueryCache.h"
#import "Firestore/Source/Local/FSTLevelDBRemoteDocumentCache.h"
Expand All @@ -33,6 +32,7 @@
#include "Firestore/core/src/firebase/firestore/auth/user.h"
#include "Firestore/core/src/firebase/firestore/core/database_info.h"
#include "Firestore/core/src/firebase/firestore/local/leveldb_key.h"
#include "Firestore/core/src/firebase/firestore/local/leveldb_migrations.h"
#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
#include "Firestore/core/src/firebase/firestore/local/leveldb_util.h"
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
Expand All @@ -58,6 +58,7 @@
using firebase::firestore::local::ConvertStatus;
using firebase::firestore::local::LevelDbDocumentMutationKey;
using firebase::firestore::local::LevelDbDocumentTargetKey;
using firebase::firestore::local::LevelDbMigrations;
using firebase::firestore::local::LevelDbMutationKey;
using firebase::firestore::local::LevelDbTransaction;
using firebase::firestore::model::DatabaseId;
Expand Down Expand Up @@ -352,7 +353,7 @@ - (Status)start {
}
_ptr = std::move(database).ValueOrDie();

[FSTLevelDBMigrations runMigrationsWithDatabase:_ptr.get()];
LevelDbMigrations::RunMigrations(_ptr.get());
LevelDbTransaction transaction(_ptr.get(), "Start LevelDB");
_users = [FSTLevelDB collectUserSet:&transaction];
transaction.Commit();
Expand Down
49 changes: 0 additions & 49 deletions Firestore/Source/Local/FSTLevelDBMigrations.h

This file was deleted.

13 changes: 10 additions & 3 deletions Firestore/core/src/firebase/firestore/local/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,23 @@ if(HAVE_LEVELDB)
cc_library(
firebase_firestore_local_persistence_leveldb
SOURCES
leveldb_key.h
leveldb_key.cc
leveldb_transaction.h
leveldb_key.h
leveldb_migrations.cc
leveldb_migrations.h
leveldb_transaction.cc
leveldb_util.h
leveldb_transaction.h
leveldb_util.cc
leveldb_util.h
DEPENDS
# TODO(b/111328563) Force nanopb first to work around ODR violations
protobuf-nanopb

LevelDB::LevelDB
absl_strings
firebase_firestore_model
firebase_firestore_nanopb
firebase_firestore_protos_nanopb
firebase_firestore_util
EXCLUDE_FROM_ALL
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,69 +14,69 @@
* limitations under the License.
*/

#import "Firestore/Source/Local/FSTLevelDBMigrations.h"
#include "Firestore/core/src/firebase/firestore/local/leveldb_migrations.h"

#include <string>
#include <utility>

#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
#import "Firestore/Source/Local/FSTLevelDBQueryCache.h"

#include "Firestore/Protos/nanopb/firestore/local/target.nanopb.h"
#include "Firestore/core/src/firebase/firestore/local/leveldb_key.h"
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
#include "absl/base/macros.h"
#include "absl/memory/memory.h"
#include "Firestore/core/src/firebase/firestore/nanopb/writer.h"
#include "absl/strings/match.h"
#include "leveldb/write_batch.h"

NS_ASSUME_NONNULL_BEGIN
namespace firebase {
namespace firestore {
namespace local {

using leveldb::Iterator;
using leveldb::Slice;
using leveldb::Status;
using leveldb::WriteOptions;
using nanopb::Writer;

namespace {

/**
* Schema version for the iOS client.
*
* Note that tables aren't a concept in LevelDB. They exist in our schema as just prefixes on keys.
* This means tables don't need to be created but they also can't easily be dropped and re-created.
* Note that tables aren't a concept in LevelDB. They exist in our schema as
* just prefixes on keys. This means tables don't need to be created but they
* also can't easily be dropped and re-created.
*
* Migrations:
* * Migration 1 used to ensure the target_global row existed, without clearing it. No longer
* required because migration 3 unconditionally clears it.
* * Migration 2 used to ensure that the target_global row had a correct count of targets. No
* longer required because migration 3 deletes them all.
* * Migration 3 deletes the entire query cache to deal with cache corruption related to
* limbo resolution. Addresses https://github.com/firebase/firebase-ios-sdk/issues/1548.
* * Migration 1 used to ensure the target_global row existed, without
* clearing it. No longer required because migration 3 unconditionally
* clears it.
* * Migration 2 used to ensure that the target_global row had a correct count
* of targets. No longer required because migration 3 deletes them all.
* * Migration 3 deletes the entire query cache to deal with cache corruption
* related to limbo resolution. Addresses
* https://github.com/firebase/firebase-ios-sdk/issues/1548.
*/
static FSTLevelDBSchemaVersion kSchemaVersion = 3;

using firebase::firestore::local::LevelDbDocumentTargetKey;
using firebase::firestore::local::LevelDbQueryTargetKey;
using firebase::firestore::local::LevelDbTargetDocumentKey;
using firebase::firestore::local::LevelDbTargetGlobalKey;
using firebase::firestore::local::LevelDbTargetKey;
using firebase::firestore::local::LevelDbTransaction;
using firebase::firestore::local::LevelDbVersionKey;
using leveldb::Iterator;
using leveldb::Status;
using leveldb::Slice;
using leveldb::WriteOptions;
const LevelDbMigrations::SchemaVersion kSchemaVersion = 3;

/**
* Save the given version number as the current version of the schema of the database.
* Save the given version number as the current version of the schema of the
* database.
* @param version The version to save
* @param transaction The transaction in which to save the new version number
*/
static void SaveVersion(FSTLevelDBSchemaVersion version, LevelDbTransaction *transaction) {
void SaveVersion(LevelDbMigrations::SchemaVersion version,
LevelDbTransaction* transaction) {
std::string key = LevelDbVersionKey::Key();
std::string version_string = std::to_string(version);
transaction->Put(key, version_string);
}

static void DeleteEverythingWithPrefix(const std::string &prefix, leveldb::DB *db) {
void DeleteEverythingWithPrefix(const std::string& prefix, leveldb::DB* db) {
bool more_deletes = true;
while (more_deletes) {
LevelDbTransaction transaction(db, "Delete everything with prefix");
auto it = transaction.NewIterator();

more_deletes = false;
for (it->Seek(prefix); it->Valid() && absl::StartsWith(it->key(), prefix); it->Next()) {
for (it->Seek(prefix); it->Valid() && absl::StartsWith(it->key(), prefix);
it->Next()) {
if (transaction.changed_keys() >= 1000) {
more_deletes = true;
break;
Expand All @@ -89,7 +89,7 @@ static void DeleteEverythingWithPrefix(const std::string &prefix, leveldb::DB *d
}

/** Migration 3. */
static void ClearQueryCache(leveldb::DB *db) {
void ClearQueryCache(leveldb::DB* db) {
DeleteEverythingWithPrefix(LevelDbTargetKey::KeyPrefix(), db);
DeleteEverythingWithPrefix(LevelDbDocumentTargetKey::KeyPrefix(), db);
DeleteEverythingWithPrefix(LevelDbTargetDocumentKey::KeyPrefix(), db);
Expand All @@ -98,16 +98,22 @@ static void ClearQueryCache(leveldb::DB *db) {
LevelDbTransaction transaction(db, "Drop query cache");

// Reset the target global entry too (to reset the target count).
transaction.Put(LevelDbTargetGlobalKey::Key(), [FSTPBTargetGlobal message]);
firestore_client_TargetGlobal target_global{};

std::string bytes;
Writer writer = Writer::Wrap(&bytes);
writer.WriteNanopbMessage(firestore_client_TargetGlobal_fields,
&target_global);
transaction.Put(LevelDbTargetGlobalKey::Key(), std::move(bytes));

SaveVersion(3, &transaction);
transaction.Commit();
}

@implementation FSTLevelDBMigrations
} // namespace

+ (FSTLevelDBSchemaVersion)schemaVersionWithTransaction:
(firebase::firestore::local::LevelDbTransaction *)transaction {
LevelDbMigrations::SchemaVersion LevelDbMigrations::ReadSchemaVersion(
LevelDbTransaction* transaction) {
std::string key = LevelDbVersionKey::Key();
std::string version_string;
Status status = transaction->Get(key, &version_string);
Expand All @@ -118,22 +124,23 @@ + (FSTLevelDBSchemaVersion)schemaVersionWithTransaction:
}
}

+ (void)runMigrationsWithDatabase:(leveldb::DB *)database {
[self runMigrationsWithDatabase:database upToVersion:kSchemaVersion];
void LevelDbMigrations::RunMigrations(leveldb::DB* db) {
RunMigrations(db, kSchemaVersion);
}

+ (void)runMigrationsWithDatabase:(leveldb::DB *)database
upToVersion:(FSTLevelDBSchemaVersion)toVersion {
LevelDbTransaction transaction{database, "Read schema version"};
FSTLevelDBSchemaVersion fromVersion = [self schemaVersionWithTransaction:&transaction];
void LevelDbMigrations::RunMigrations(leveldb::DB* db,
SchemaVersion to_version) {
LevelDbTransaction transaction{db, "Read schema version"};
SchemaVersion from_version = ReadSchemaVersion(&transaction);

// This must run unconditionally because schema migrations were added to iOS after the first
// release. There may be clients that have never run any migrations that have existing targets.
if (fromVersion < 3 && toVersion >= 3) {
ClearQueryCache(database);
// This must run unconditionally because schema migrations were added to iOS
// after the first release. There may be clients that have never run any
// migrations that have existing targets.
if (from_version < 3 && to_version >= 3) {
ClearQueryCache(db);
}
}

@end

NS_ASSUME_NONNULL_END
} // namespace local
} // namespace firestore
} // namespace firebase
Loading