FSTLevelDBMigrations.mm 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. /*
  2. * Copyright 2018 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "Firestore/Source/Local/FSTLevelDBMigrations.h"
  17. #include <string>
  18. #import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
  19. #import "Firestore/Source/Local/FSTLevelDBKey.h"
  20. #import "Firestore/Source/Local/FSTLevelDBQueryCache.h"
  21. #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
  22. #include "absl/base/macros.h"
  23. #include "absl/memory/memory.h"
  24. #include "absl/strings/match.h"
  25. #include "leveldb/write_batch.h"
  26. NS_ASSUME_NONNULL_BEGIN
  27. /**
  28. * Schema version for the iOS client.
  29. *
  30. * Note that tables aren't a concept in LevelDB. They exist in our schema as just prefixes on keys.
  31. * This means tables don't need to be created but they also can't easily be dropped and re-created.
  32. *
  33. * Migrations:
  34. * * Migration 1 used to ensure the target_global row existed, without clearing it. No longer
  35. * required because migration 3 unconditionally clears it.
  36. * * Migration 2 used to ensure that the target_global row had a correct count of targets. No
  37. * longer required because migration 3 deletes them all.
  38. * * Migration 3 deletes the entire query cache to deal with cache corruption related to
  39. * limbo resolution. Addresses https://github.com/firebase/firebase-ios-sdk/issues/1548.
  40. */
  41. static FSTLevelDBSchemaVersion kSchemaVersion = 3;
  42. using firebase::firestore::local::LevelDbTransaction;
  43. using leveldb::Iterator;
  44. using leveldb::Status;
  45. using leveldb::Slice;
  46. using leveldb::WriteOptions;
  47. /**
  48. * Save the given version number as the current version of the schema of the database.
  49. * @param version The version to save
  50. * @param transaction The transaction in which to save the new version number
  51. */
  52. static void SaveVersion(FSTLevelDBSchemaVersion version, LevelDbTransaction *transaction) {
  53. std::string key = [FSTLevelDBVersionKey key];
  54. std::string version_string = std::to_string(version);
  55. transaction->Put(key, version_string);
  56. }
  57. static void DeleteEverythingWithPrefix(const std::string &prefix, leveldb::DB *db) {
  58. bool more_deletes = true;
  59. while (more_deletes) {
  60. LevelDbTransaction transaction(db, "Delete everything with prefix");
  61. auto it = transaction.NewIterator();
  62. more_deletes = false;
  63. for (it->Seek(prefix); it->Valid() && absl::StartsWith(it->key(), prefix); it->Next()) {
  64. if (transaction.changed_keys() >= 1000) {
  65. more_deletes = true;
  66. break;
  67. }
  68. transaction.Delete(it->key());
  69. }
  70. transaction.Commit();
  71. }
  72. }
  73. /** Migration 3. */
  74. static void ClearQueryCache(leveldb::DB *db) {
  75. DeleteEverythingWithPrefix([FSTLevelDBTargetKey keyPrefix], db);
  76. DeleteEverythingWithPrefix([FSTLevelDBDocumentTargetKey keyPrefix], db);
  77. DeleteEverythingWithPrefix([FSTLevelDBTargetDocumentKey keyPrefix], db);
  78. DeleteEverythingWithPrefix([FSTLevelDBQueryTargetKey keyPrefix], db);
  79. LevelDbTransaction transaction(db, "Drop query cache");
  80. // Reset the target global entry too (to reset the target count).
  81. transaction.Put([FSTLevelDBTargetGlobalKey key], [FSTPBTargetGlobal message]);
  82. SaveVersion(3, &transaction);
  83. transaction.Commit();
  84. }
  85. @implementation FSTLevelDBMigrations
  86. + (FSTLevelDBSchemaVersion)schemaVersionWithTransaction:
  87. (firebase::firestore::local::LevelDbTransaction *)transaction {
  88. std::string key = [FSTLevelDBVersionKey key];
  89. std::string version_string;
  90. Status status = transaction->Get(key, &version_string);
  91. if (status.IsNotFound()) {
  92. return 0;
  93. } else {
  94. return stoi(version_string);
  95. }
  96. }
  97. + (void)runMigrationsWithDatabase:(leveldb::DB *)database {
  98. [self runMigrationsWithDatabase:database upToVersion:kSchemaVersion];
  99. }
  100. + (void)runMigrationsWithDatabase:(leveldb::DB *)database
  101. upToVersion:(FSTLevelDBSchemaVersion)toVersion {
  102. LevelDbTransaction transaction{database, "Read schema version"};
  103. FSTLevelDBSchemaVersion fromVersion = [self schemaVersionWithTransaction:&transaction];
  104. // This must run unconditionally because schema migrations were added to iOS after the first
  105. // release. There may be clients that have never run any migrations that have existing targets.
  106. if (fromVersion < 3 && toVersion >= 3) {
  107. ClearQueryCache(database);
  108. }
  109. }
  110. @end
  111. NS_ASSUME_NONNULL_END