Skip to content

Commit 227b6aa

Browse files
author
hideki
committed
Issue: Crash during 1.1 to 1.2 migration causes permanent loss of data Changes: - Instead of migrate to actual new name, create temporary database and migrate to temporary db, then rename temporary name to actual name. - If temporary database exists, rollback it to old name before continue to migration. Rollback actually rollback attachment files. - Because of calling backOut() for rollback, importData() method do move file attachment instead of copy file. <- No change.
1 parent 9bc2313 commit 227b6aa

1 file changed

Lines changed: 68 additions & 14 deletions

File tree

src/main/java/com/couchbase/lite/Manager.java

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,8 @@ public boolean accept(File file, String name) {
795795
String filename = file.getName();
796796
String name = nameOfDatabaseAtPath(filename);
797797
String oldDbPath = new File(directory, filename).getAbsolutePath();
798-
upgradeDatabase(name, oldDbPath, true);
798+
if (!upgradeDatabase(name, oldDbPath, true))
799+
throw new RuntimeException("Database upgrade failed for: " + name);
799800
}
800801
}
801802

@@ -804,28 +805,81 @@ public boolean accept(File file, String name) {
804805
* - (BOOL) upgradeDatabaseNamed: (NSString*)name
805806
* atPath: (NSString*)dbPath
806807
* error: (NSError**)outError
808+
*
809+
* NOTE: upgradeDatabase() method is called if the old database exists.
807810
*/
808811
private boolean upgradeDatabase(String name, String dbPath, boolean close) {
809-
Log.v(Log.TAG_DATABASE, "CouchbaseLite: Upgrading database at %s ...", dbPath);
810-
if (!name.equals("_replicator")) {
811-
// Create and open new CBLDatabase:
812-
Database db = getDatabase(name, false);
813-
if (db == null) {
814-
Log.w(Log.TAG_DATABASE, "Upgrade failed: Creating new db failed");
812+
Log.v(Log.TAG_DATABASE, "CouchbaseLite: Upgrading database (%s) at %s ...", name, dbPath);
813+
814+
// if db with name already exists, not need to migrate. Simply remove old database
815+
Database db = getDatabase(name, false);
816+
if (!db.exists() && !name.equals("_replicator")) {
817+
818+
// temporary database name for new db schema
819+
String tempName = name + ".tmp";
820+
821+
// Create and open new CBLDatabase with temporary name:
822+
Database tmpDB = getDatabase(tempName, false);
823+
if (tmpDB == null) {
824+
Log.w(Log.TAG_DATABASE, "Upgrade failed: Creating new db failed: %s", tempName);
815825
return false;
816826
}
817-
if (!db.exists()) {
818-
// Upgrade the old database into the new one:
819-
DatabaseUpgrade upgrader = new DatabaseUpgrade(this, db, dbPath);
820-
if (!upgrader.importData()) {
821-
upgrader.backOut();
827+
828+
// upgradeDatabase() is called only if dbPath (old db version) exists. So presence of
829+
// temporary db indicates that previous upgrade crashed midway.
830+
if (tmpDB.exists()) {
831+
Log.v(Log.TAG_DATABASE, "Previous upgrade probably crashed midway. dbPath: " + dbPath);
832+
833+
// rollback from temporary database to old database
834+
// NOTE: Deleting temporary db is not enough because `DatabaseUpgrade.importData()`
835+
// move attachment files instead of copy files.
836+
DatabaseUpgrade upgrader = new DatabaseUpgrade(this, tmpDB, dbPath);
837+
upgrader.backOut();
838+
839+
// temporary db is deleted by previous operation, reopen the temporary db.
840+
tmpDB = getDatabase(tempName, false);
841+
if (tmpDB == null) {
842+
Log.w(Log.TAG_DATABASE, "Upgrade failed: Creating new db failed: %s", tempName);
822843
return false;
823844
}
824845
}
825-
if (close)
826-
db.close();
846+
847+
if (tmpDB.exists()) {
848+
// the temporary db should not exist. Just double check
849+
Log.w(Log.TAG_DATABASE, "Upgrade failed: Failed to delete already existing db: %s", tempName);
850+
return false;
851+
}
852+
853+
// Upgrade the old database into the new one:
854+
DatabaseUpgrade upgrader = new DatabaseUpgrade(this, tmpDB, dbPath);
855+
if (!upgrader.importData()) {
856+
upgrader.backOut();
857+
return false;
858+
}
859+
860+
// close temporary database
861+
tmpDB.close();
862+
863+
// rename temporary database name to new name
864+
File tmpPath = new File(pathForDatabaseNamed(tempName));
865+
File newPath = new File(pathForDatabaseNamed(name));
866+
if (!tmpPath.renameTo(newPath)) {
867+
Log.w(Log.TAG_DATABASE, "Upgrade failed: Failed to rename db folder from temporary name: %s -> %s", tmpPath, newPath);
868+
return false;
869+
}
870+
871+
// reopen db with name
872+
db = getDatabase(name, false);
873+
if (!db.exists()) {
874+
Log.w(Log.TAG_DATABASE, "Upgrade failed: Failed to open the database after migrate: %s", name);
875+
return false;
876+
}
827877
}
828878

879+
// close db if necessary
880+
if (close)
881+
db.close();
882+
829883
// Remove old database file and its SQLite side files:
830884
moveSQLiteDbFiles(dbPath, null);
831885

0 commit comments

Comments
 (0)