Bug 703154: Simplify the schema migration code. r=Unfocused

This commit is contained in:
Dave Townsend 2011-11-21 12:09:26 -08:00
parent a065316e1a
commit 7dbfd1ac65
4 changed files with 188 additions and 62 deletions

View File

@ -159,6 +159,13 @@ const DB_BOOL_METADATA = ["visible", "active", "userDisabled", "appDisabled",
"softDisabled", "foreignInstall",
"hasBinaryComponents", "strictCompatibility"];
// Properties that should be migrated where possible from an old database. These
// shouldn't include properties that can be read directly from install.rdf files
// or calculated
const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled",
"sourceURI", "applyBackgroundUpdates",
"releaseNotesURI", "foreignInstall", "syncGUID"];
const BOOTSTRAP_REASONS = {
APP_STARTUP : 1,
APP_SHUTDOWN : 2,
@ -2643,24 +2650,17 @@ var XPIProvider = {
// If there is migration data then apply it.
if (aMigrateData) {
LOG("Migrating data from old database");
// A theme's disabled state is determined by the selected theme
// preference which is read in loadManifestFromRDF
if (newAddon.type != "theme")
newAddon.userDisabled = aMigrateData.userDisabled;
if ("syncGUID" in aMigrateData)
newAddon.syncGUID = aMigrateData.syncGUID;
if ("installDate" in aMigrateData)
newAddon.installDate = aMigrateData.installDate;
if ("softDisabled" in aMigrateData)
newAddon.softDisabled = aMigrateData.softDisabled;
if ("applyBackgroundUpdates" in aMigrateData)
newAddon.applyBackgroundUpdates = aMigrateData.applyBackgroundUpdates;
if ("sourceURI" in aMigrateData)
newAddon.sourceURI = aMigrateData.sourceURI;
if ("releaseNotesURI" in aMigrateData)
newAddon.releaseNotesURI = aMigrateData.releaseNotesURI;
if ("foreignInstall" in aMigrateData)
newAddon.foreignInstall = aMigrateData.foreignInstall;
DB_MIGRATE_METADATA.forEach(function(aProp) {
// A theme's disabled state is determined by the selected theme
// preference which is read in loadManifestFromRDF
if (aProp == "userDisabled" && newAddon.type == "theme")
return;
LOG("Migrating " + aProp);
if (aProp in aMigrateData)
newAddon[aProp] = aMigrateData[aProp];
});
// Some properties should only be migrated if the add-on hasn't changed.
// The version property isn't a perfect check for this but covers the
@ -4476,61 +4476,47 @@ var XPIDatabase = {
// Attempt to migrate data from a different (even future!) version of the
// database
try {
// Build a list of sql statements that might recover useful data from this
// and future versions of the schema
var sql = [];
sql.push("SELECT internal_id, id, syncGUID, location, userDisabled, " +
"softDisabled, installDate, version, applyBackgroundUpdates, " +
"sourceURI, releaseNotesURI, foreignInstall FROM addon");
sql.push("SELECT internal_id, id, location, userDisabled, " +
"softDisabled, installDate, version, applyBackgroundUpdates, " +
"sourceURI, releaseNotesURI, foreignInstall FROM addon");
sql.push("SELECT internal_id, id, location, userDisabled, " +
"softDisabled, installDate, version, applyBackgroundUpdates, " +
"sourceURI, releaseNotesURI FROM addon");
sql.push("SELECT internal_id, id, location, userDisabled, " +
"installDate, version, applyBackgroundUpdates, " +
"sourceURI, releaseNotesURI FROM addon");
sql.push("SELECT internal_id, id, location, userDisabled, installDate, " +
"version FROM addon");
var stmt = this.connection.createStatement("PRAGMA table_info(addon)");
var stmt = null;
if (!sql.some(function(aSql) {
try {
stmt = this.connection.createStatement(aSql);
return true;
const REQUIRED = ["internal_id", "id", "location", "userDisabled",
"installDate", "version"];
let reqCount = 0;
let props = [];
for (let row in resultRows(stmt)) {
if (REQUIRED.indexOf(row.name) != -1) {
reqCount++;
props.push(row.name);
}
catch (e) {
return false;
else if (DB_METADATA.indexOf(row.name) != -1) {
props.push(row.name);
}
}, this)) {
else if (DB_BOOL_METADATA.indexOf(row.name) != -1) {
props.push(row.name);
}
}
if (reqCount < REQUIRED.length) {
ERROR("Unable to read anything useful from the database");
return migrateData;
}
stmt.finalize();
stmt = this.connection.createStatement("SELECT " + props.join(",") + " FROM addon");
for (let row in resultRows(stmt)) {
if (!(row.location in migrateData))
migrateData[row.location] = {};
migrateData[row.location][row.id] = {
internal_id: row.internal_id,
version: row.version,
installDate: row.installDate,
userDisabled: row.userDisabled == 1,
let addonData = {
targetApplications: []
};
}
migrateData[row.location][row.id] = addonData;
if ("syncGUID" in row)
migrateData[row.location][row.id].syncGUID = row.syncGUID;
if ("softDisabled" in row)
migrateData[row.location][row.id].softDisabled = row.softDisabled == 1;
if ("applyBackgroundUpdates" in row)
migrateData[row.location][row.id].applyBackgroundUpdates = row.applyBackgroundUpdates == 1;
if ("sourceURI" in row)
migrateData[row.location][row.id].sourceURI = row.sourceURI;
if ("releaseNotesURI" in row)
migrateData[row.location][row.id].releaseNotesURI = row.releaseNotesURI;
if ("foreignInstall" in row)
migrateData[row.location][row.id].foreignInstall = row.foreignInstall;
props.forEach(function(aProp) {
if (DB_BOOL_METADATA.indexOf(aProp) != -1)
addonData[aProp] = row[aProp] == 1;
else
addonData[aProp] = row[aProp];
})
}
var taStmt = this.connection.createStatement("SELECT id, minVersion, " +

View File

@ -135,7 +135,7 @@ function run_test() {
stmt.execute();
stmt.finalize();
db.schemaVersion = 100;
db.schemaVersion = 10000;
Services.prefs.setIntPref("extensions.databaseSchema", 100);
db.close();

View File

@ -0,0 +1,139 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Checks that we fail to migrate but still start up ok when there is a database
// with no useful data in it.
const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
var addon1 = {
id: "addon1@tests.mozilla.org",
version: "1.0",
name: "Test 1",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "1"
}]
};
var addon2 = {
id: "addon2@tests.mozilla.org",
version: "2.0",
name: "Test 5",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "0",
maxVersion: "0"
}]
};
var defaultTheme = {
id: "default@tests.mozilla.org",
version: "2.0",
name: "Default theme",
internalName: "classic/1.0",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "1"
}]
};
var theme1 = {
id: "theme1@tests.mozilla.org",
version: "2.0",
name: "Test theme",
internalName: "theme1/1.0",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "1"
}]
};
const profileDir = gProfD.clone();
profileDir.append("extensions");
function run_test() {
do_test_pending();
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
writeInstallRDFForExtension(addon1, profileDir);
writeInstallRDFForExtension(addon2, profileDir);
writeInstallRDFForExtension(defaultTheme, profileDir);
writeInstallRDFForExtension(theme1, profileDir);
Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, "theme1/1.0");
// Write out a broken database (no userDisabled field)
let dbfile = gProfD.clone();
dbfile.append("extensions.sqlite");
let db = AM_Cc["@mozilla.org/storage/service;1"].
getService(AM_Ci.mozIStorageService).
openDatabase(dbfile);
db.createTable("addon", "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"id TEXT, location TEXT, version TEXT, active INTEGER, " +
"installDate INTEGER");
db.createTable("targetApplication", "addon_internal_id INTEGER, " +
"id TEXT, minVersion TEXT, maxVersion TEXT");
let stmt = db.createStatement("INSERT INTO addon VALUES (NULL, :id, :location, " +
":version, :active, :installDate)");
let internal_ids = {};
[["addon1@tests.mozilla.org", "app-profile", "1.0", "1", "0"],
["addon2@tests.mozilla.org", "app-profile", "2.0", "0", "0"],
["default@tests.mozilla.org", "app-profile", "2.0", "1", "0"],
["theme1@tests.mozilla.org", "app-profile", "2.0", "0", "0"]].forEach(function(a) {
stmt.params.id = a[0];
stmt.params.location = a[1];
stmt.params.version = a[2];
stmt.params.active = a[3];
stmt.params.installDate = a[4];
stmt.execute();
internal_ids[a[0]] = db.lastInsertRowID;
});
stmt.finalize();
db.schemaVersion = 100;
Services.prefs.setIntPref("extensions.databaseSchema", 100);
db.close();
startupManager();
check_startup_changes("installed", []);
check_startup_changes("updated", []);
check_startup_changes("uninstalled", []);
check_startup_changes("disabled", []);
check_startup_changes("enabled", []);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"default@tests.mozilla.org",
"theme1@tests.mozilla.org"],
function([a1, a2, d, t1]) {
do_check_neq(a1, null);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_true(a1.isActive);
do_check_neq(a2, null);
do_check_false(a2.userDisabled);
do_check_true(a2.appDisabled);
do_check_false(a2.isActive);
// Should have enabled the selected theme
do_check_neq(t1, null);
do_check_false(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_true(t1.isActive);
do_check_neq(d, null);
do_check_true(d.userDisabled);
do_check_false(d.appDisabled);
do_check_false(d.isActive);
do_test_finished();
});
}

View File

@ -173,6 +173,7 @@ skip-if = os == "android"
[test_migrate2.js]
[test_migrate3.js]
[test_migrate4.js]
[test_migrate5.js]
[test_migrateAddonRepository.js]
[test_permissions.js]
[test_plugins.js]