Skip to content

Commit

Permalink
Merge pull request #2021 from dimagi/ssdFix
Browse files Browse the repository at this point in the history
Fixes Crash due to SSD records getting detached in FormProvider Migration
  • Loading branch information
shubham1g5 authored Jul 17, 2018
2 parents 38d734b + e79a211 commit fb6bcbc
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public int getFormRecordId() {
return formRecordId;
}

public void setFormRecordId(int formRecordId) {
this.formRecordId = formRecordId;
}

public SessionStateDescriptor reMapFormRecordId(int idForNewRecord) {
SessionStateDescriptor copy = new SessionStateDescriptor();
copy.formRecordId = idForNewRecord;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ public class DatabaseUserOpenHelper extends SQLiteOpenHelper {
* V.22 - Add column for appId in entity_cache table
* V.23 - Merges InstanceProvider to FormRecord (delete instanceUri, add displayName, filePath and canEditWhenComplete)
* v.24 - Adds and indexes column for Case external_id
* v.25 - No DB changes, validates SessionStateDescriptor records corrupted due to an earlier bug in v23 migration (In 2.44 and 2.44.1)
*/

private static final int USER_DB_VERSION = 24;
private static final int USER_DB_VERSION = 25;

private static final String USER_DB_LOCATOR = "database_sandbox_";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.commcare.android.database.user.models.ACasePreV24Model;
import org.commcare.android.database.user.models.FormRecordV2;
import org.commcare.android.database.user.models.FormRecordV3;
import org.commcare.android.database.user.models.SessionStateDescriptor;
import org.commcare.android.logging.ForceCloseLogEntry;
import org.commcare.android.javarosa.AndroidLogEntry;
import org.commcare.cases.model.Case;
Expand All @@ -36,6 +37,7 @@
import org.javarosa.core.services.Logger;
import org.javarosa.core.services.storage.Persistable;

import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Vector;

Expand All @@ -62,6 +64,8 @@ public void upgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// longer than usual in order to make sure the upgrade has time to finish
CommCareApplication.instance().setCustomServiceBindTimeout(5 * 60 * 1000);

int startVersion = oldVersion;

if (oldVersion == 1) {
if (upgradeOneTwo(db)) {
oldVersion = 2;
Expand Down Expand Up @@ -191,6 +195,18 @@ public void upgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
oldVersion = 24;
}
}

if (oldVersion == 24) {
// We are doing migration to v25 because of a bug in migration to v23 earlier, so
// we only want to actually trigger this for apps already on v23 or greater
if (startVersion > 22) {
if (upgradeTwentyFourTwentyFive(db)) {
oldVersion = 25;
}
} else {
oldVersion = 25;
}
}
}

private boolean upgradeOneTwo(final SQLiteDatabase db) {
Expand Down Expand Up @@ -648,6 +664,49 @@ private boolean upgradeTwentyThreeTwentyFour(SQLiteDatabase db) {
}
}

// check for integrity of SSD records and wipes the whole table in case of any discrepancy
private boolean upgradeTwentyFourTwentyFive(SQLiteDatabase db) {
db.beginTransaction();
try {
boolean strandedRecordObserved = false;
SqlStorage<FormRecord> formRecordStorage = UserDbUpgradeUtils.getFormRecordStorage(c, db, FormRecord.class);
SqlStorage<SessionStateDescriptor> ssdStorage = new SqlStorage<>(
SessionStateDescriptor.STORAGE_KEY,
SessionStateDescriptor.class,
new ConcreteAndroidDbHelper(c, db));
for (SessionStateDescriptor ssd : ssdStorage) {
// we are in invalid state if formRecord with corresponding ssd form id
// either doesn't exist or has status unstarted
try {
FormRecord formRecord = formRecordStorage.read(ssd.getFormRecordId());
if (formRecord.getStatus().contentEquals(FormRecord.STATUS_UNSTARTED)) {
strandedRecordObserved = true;
break;
}
} catch (NoSuchElementException e) {
strandedRecordObserved = true;
break;
}
}

if (strandedRecordObserved) {
SqlStorage.wipeTable(db, SessionStateDescriptor.STORAGE_KEY);

// Since we have wiped out SSD records, we won't be able to resume
// incomplete forms with their earlier session state. Therfore we are
// going to delete all incomplete form records as well
Vector<FormRecord> incompleteRecords = formRecordStorage.getRecordsForValue(FormRecord.META_STATUS, FormRecord.STATUS_INCOMPLETE);
for (FormRecord incompleteRecord : incompleteRecords) {
formRecordStorage.remove(incompleteRecord);
}
}
db.setTransactionSuccessful();
return true;
} finally {
db.endTransaction();
}
}

private void migrateV2FormRecordsForSingleApp(String appId,
SqlStorage<FormRecordV2> oldStorage,
Vector<FormRecordV3> upgradedRecords) {
Expand Down
28 changes: 24 additions & 4 deletions app/src/org/commcare/models/database/user/UserDbUpgradeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import org.javarosa.core.services.storage.Persistable;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
Expand Down Expand Up @@ -198,7 +197,8 @@ protected static void migrateFormRecordsToV3(Context c, SQLiteDatabase db) {

/**
* Migrated form records to include data from corresponding Instance from InstanceProvider
* @param c Context
*
* @param c Context
* @param db User DB we are migrating
* @return a Vector containing Instance Uris corresponding to InstanceProvider entries that got migrated successfully
*/
Expand Down Expand Up @@ -232,6 +232,7 @@ protected static Vector<Uri> migrateV4FormRecords(Context c, SQLiteDatabase db)
cursor.close();
}
}
newRecord.setID(oldRecord.getID());
newRecords.add(new Pair<>(newRecord, instanceUri));
}

Expand All @@ -242,15 +243,34 @@ protected static Vector<Uri> migrateV4FormRecords(Context c, SQLiteDatabase db)

// Write to the new table
SqlStorage<FormRecord> newStorage = getFormRecordStorage(c, db, FormRecord.class);
SqlStorage<SessionStateDescriptor> ssdStorage = new SqlStorage<>(
SessionStateDescriptor.STORAGE_KEY,
SessionStateDescriptor.class,
new ConcreteAndroidDbHelper(c, db));
for (Pair entry : newRecords) {
newStorage.write(((FormRecord)entry.first));
FormRecord newRecord = ((FormRecord)entry.first);
int oldId = newRecord.getID();

// Since we are writing in new table, reset the id before write
newRecord.setID(-1);
newStorage.write(newRecord);

// Migrate SSD
try {
SessionStateDescriptor ssd = ssdStorage.getRecordForValue(SessionStateDescriptor.META_FORM_RECORD_ID, oldId);
ssd.setFormRecordId(newRecord.getID());
ssdStorage.write(ssd);
} catch (Exception e) {
// Ignore failures in SSD Migration
}

migratedInstances.add(((Uri)entry.second));
}

return migratedInstances;
}

private static SqlStorage getFormRecordStorage(Context c, SQLiteDatabase db, Class formRecordClass) {
public static SqlStorage getFormRecordStorage(Context c, SQLiteDatabase db, Class formRecordClass) {
return new SqlStorage<>(
FormRecord.STORAGE_KEY,
formRecordClass,
Expand Down

0 comments on commit fb6bcbc

Please sign in to comment.