summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/tests/migration
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places/tests/migration')
-rw-r--r--toolkit/components/places/tests/migration/favicons_v41.sqlitebin0 -> 229376 bytes
-rw-r--r--toolkit/components/places/tests/migration/head_migration.js47
-rw-r--r--toolkit/components/places/tests/migration/places_outdated.sqlitebin0 -> 155648 bytes
-rw-r--r--toolkit/components/places/tests/migration/places_v43.sqlitebin0 -> 1146880 bytes
-rw-r--r--toolkit/components/places/tests/migration/places_v54.sqlitebin0 -> 1212416 bytes
-rw-r--r--toolkit/components/places/tests/migration/places_v66.sqlitebin0 -> 1703936 bytes
-rw-r--r--toolkit/components/places/tests/migration/places_v68.sqlitebin0 -> 1703936 bytes
-rw-r--r--toolkit/components/places/tests/migration/places_v69.sqlitebin0 -> 1703936 bytes
-rw-r--r--toolkit/components/places/tests/migration/places_v70.sqlitebin0 -> 1703936 bytes
-rw-r--r--toolkit/components/places/tests/migration/places_v72.sqlitebin0 -> 1409024 bytes
-rw-r--r--toolkit/components/places/tests/migration/places_v74.sqlitebin0 -> 1441792 bytes
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_downgraded.js29
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_outdated.js47
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v43.js253
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v45.js100
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v46.js52
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v47.js128
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v48.js190
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v50.js209
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v53.js23
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v54.js58
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v66.js53
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v68.js35
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v69.js84
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v70.js96
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v72.js29
-rw-r--r--toolkit/components/places/tests/migration/xpcshell.ini33
27 files changed, 1466 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/migration/favicons_v41.sqlite b/toolkit/components/places/tests/migration/favicons_v41.sqlite
new file mode 100644
index 0000000000..a59d9d286f
--- /dev/null
+++ b/toolkit/components/places/tests/migration/favicons_v41.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/head_migration.js b/toolkit/components/places/tests/migration/head_migration.js
new file mode 100644
index 0000000000..27fb06a3ef
--- /dev/null
+++ b/toolkit/components/places/tests/migration/head_migration.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Import common head.
+{
+ /* import-globals-from ../head_common.js */
+ let commonFile = do_get_file("../head_common.js", false);
+ let uri = Services.io.newFileURI(commonFile);
+ Services.scriptloader.loadSubScript(uri.spec, this);
+}
+
+// Put any other stuff relative to this test folder below.
+
+const CURRENT_SCHEMA_VERSION = 74;
+const FIRST_UPGRADABLE_SCHEMA_VERSION = 43;
+
+async function assertAnnotationsRemoved(db, expectedAnnos) {
+ for (let anno of expectedAnnos) {
+ let rows = await db.execute(
+ `
+ SELECT id FROM moz_anno_attributes
+ WHERE name = :anno
+ `,
+ { anno }
+ );
+
+ Assert.equal(rows.length, 0, `${anno} should not exist in the database`);
+ }
+}
+
+async function assertNoOrphanAnnotations(db) {
+ let rows = await db.execute(`
+ SELECT item_id FROM moz_items_annos
+ WHERE item_id NOT IN (SELECT id from moz_bookmarks)
+ `);
+
+ Assert.equal(rows.length, 0, `Should have no orphan annotations.`);
+
+ rows = await db.execute(`
+ SELECT id FROM moz_anno_attributes
+ WHERE id NOT IN (SELECT id from moz_items_annos)
+ `);
+
+ Assert.equal(rows.length, 0, `Should have no orphan annotation attributes.`);
+}
diff --git a/toolkit/components/places/tests/migration/places_outdated.sqlite b/toolkit/components/places/tests/migration/places_outdated.sqlite
new file mode 100644
index 0000000000..2852a4cf97
--- /dev/null
+++ b/toolkit/components/places/tests/migration/places_outdated.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/places_v43.sqlite b/toolkit/components/places/tests/migration/places_v43.sqlite
new file mode 100644
index 0000000000..9210f215fa
--- /dev/null
+++ b/toolkit/components/places/tests/migration/places_v43.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/places_v54.sqlite b/toolkit/components/places/tests/migration/places_v54.sqlite
new file mode 100644
index 0000000000..a203b28c10
--- /dev/null
+++ b/toolkit/components/places/tests/migration/places_v54.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/places_v66.sqlite b/toolkit/components/places/tests/migration/places_v66.sqlite
new file mode 100644
index 0000000000..9578ee11e6
--- /dev/null
+++ b/toolkit/components/places/tests/migration/places_v66.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/places_v68.sqlite b/toolkit/components/places/tests/migration/places_v68.sqlite
new file mode 100644
index 0000000000..414fa170ec
--- /dev/null
+++ b/toolkit/components/places/tests/migration/places_v68.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/places_v69.sqlite b/toolkit/components/places/tests/migration/places_v69.sqlite
new file mode 100644
index 0000000000..bc3053c18e
--- /dev/null
+++ b/toolkit/components/places/tests/migration/places_v69.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/places_v70.sqlite b/toolkit/components/places/tests/migration/places_v70.sqlite
new file mode 100644
index 0000000000..907e7f5046
--- /dev/null
+++ b/toolkit/components/places/tests/migration/places_v70.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/places_v72.sqlite b/toolkit/components/places/tests/migration/places_v72.sqlite
new file mode 100644
index 0000000000..59d0d8fdab
--- /dev/null
+++ b/toolkit/components/places/tests/migration/places_v72.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/places_v74.sqlite b/toolkit/components/places/tests/migration/places_v74.sqlite
new file mode 100644
index 0000000000..e7078a054f
--- /dev/null
+++ b/toolkit/components/places/tests/migration/places_v74.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/test_current_from_downgraded.js b/toolkit/components/places/tests/migration/test_current_from_downgraded.js
new file mode 100644
index 0000000000..5daec14e2f
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_downgraded.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test ensures we can pass twice through migration methods without
+// failing, that is what happens in case of a downgrade followed by an upgrade.
+
+add_task(async function setup() {
+ let dbFile = PathUtils.join(
+ do_get_cwd().path,
+ `places_v${CURRENT_SCHEMA_VERSION}.sqlite`
+ );
+ Assert.ok(await IOUtils.exists(dbFile));
+ await setupPlacesDatabase(`places_v${CURRENT_SCHEMA_VERSION}.sqlite`);
+ // Downgrade the schema version to the first supported one.
+ let path = PathUtils.join(PathUtils.profileDir, DB_FILENAME);
+ let db = await Sqlite.openConnection({ path });
+ await db.setSchemaVersion(FIRST_UPGRADABLE_SCHEMA_VERSION);
+ await db.close();
+});
+
+add_task(async function database_is_valid() {
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_outdated.js b/toolkit/components/places/tests/migration/test_current_from_outdated.js
new file mode 100644
index 0000000000..e7fad5b3a4
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_outdated.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests migration from a preliminary schema version 6 that
+ * lacks frecency column and moz_inputhistory table.
+ */
+
+add_task(async function setup() {
+ await setupPlacesDatabase("places_outdated.sqlite");
+});
+
+add_task(async function corrupt_database_not_exists() {
+ let corruptPath = PathUtils.join(
+ PathUtils.profileDir,
+ "places.sqlite.corrupt"
+ );
+ Assert.ok(
+ !(await IOUtils.exists(corruptPath)),
+ "Corrupt file should not exist"
+ );
+});
+
+add_task(async function database_is_valid() {
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CORRUPT
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(async function check_columns() {
+ // Check the database has been replaced, these would throw otherwise.
+ let db = await PlacesUtils.promiseDBConnection();
+ await db.execute("SELECT frecency from moz_places");
+ await db.execute("SELECT 1 from moz_inputhistory");
+});
+
+add_task(async function corrupt_database_exists() {
+ let corruptPath = PathUtils.join(
+ PathUtils.profileDir,
+ "places.sqlite.corrupt"
+ );
+ Assert.ok(await IOUtils.exists(corruptPath), "Corrupt file should exist");
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v43.js b/toolkit/components/places/tests/migration/test_current_from_v43.js
new file mode 100644
index 0000000000..70a383bb2e
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v43.js
@@ -0,0 +1,253 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const EXPECTED_REMAINING_ROOTS = [
+ ...PlacesUtils.bookmarks.userContentRoots,
+ PlacesUtils.bookmarks.tagsGuid,
+];
+
+const EXPECTED_REMOVED_BOOKMARK_GUIDS = [
+ // These first ones are the old left-pane folder queries
+ "SNLmwJH6GtW9", // Root Query
+ "r0dY_2_y4mlx", // History
+ "xGGhZK3b6GnW", // Downloads
+ "EJG6I1nKkQFQ", // Tags
+ "gSyHo5oNSUJV", // All Bookmarks
+ // These are simulated add-on injections that we expect to be removed.
+ "exaddon_____",
+ "exaddon1____",
+ "exaddon2____",
+ "exaddon3____",
+ "test________",
+];
+
+const EXPECTED_REMOVED_ANNOTATIONS = [
+ "PlacesOrganizer/OrganizerFolder",
+ "PlacesOrganizer/OrganizerQuery",
+];
+
+const EXPECTED_REMOVED_PLACES_ENTRIES = ["exaddonh____", "exaddonh3___"];
+const EXPECTED_KEPT_PLACES_ENTRY = "exaddonh2___";
+const EXPECTED_REMOVED_KEYWORDS = ["exaddon", "exaddon2"];
+
+async function assertItemIn(db, table, field, expectedItems) {
+ let rows = await db.execute(`SELECT ${field} from ${table}`);
+
+ Assert.ok(
+ rows.length >= expectedItems.length,
+ "Should be at least the number of annotations we expect to be removed."
+ );
+
+ let fieldValues = rows.map(row => row.getResultByName(field));
+
+ for (let item of expectedItems) {
+ Assert.ok(
+ fieldValues.includes(item),
+ `${table} should have ${expectedItems}`
+ );
+ }
+}
+
+add_task(async function setup() {
+ await setupPlacesDatabase("places_v43.sqlite");
+
+ // Setup database contents to be migrated.
+ let path = PathUtils.join(PathUtils.profileDir, DB_FILENAME);
+ let db = await Sqlite.openConnection({ path });
+
+ let rows = await db.execute(`SELECT * FROM moz_bookmarks_deleted`);
+ Assert.equal(rows.length, 0, "Should be nothing in moz_bookmarks_deleted");
+
+ // Break roots parenting, to test for Bug 1472127.
+ await db.execute(`INSERT INTO moz_bookmarks (title, parent, position, guid)
+ VALUES ("test", 1, 0, "test________")`);
+ await db.execute(`UPDATE moz_bookmarks
+ SET parent = (SELECT id FROM moz_bookmarks WHERE guid = "test________")
+ WHERE guid = "menu________"`);
+
+ await assertItemIn(
+ db,
+ "moz_anno_attributes",
+ "name",
+ EXPECTED_REMOVED_ANNOTATIONS
+ );
+ await assertItemIn(
+ db,
+ "moz_bookmarks",
+ "guid",
+ EXPECTED_REMOVED_BOOKMARK_GUIDS
+ );
+ await assertItemIn(db, "moz_keywords", "keyword", EXPECTED_REMOVED_KEYWORDS);
+ await assertItemIn(db, "moz_places", "guid", EXPECTED_REMOVED_PLACES_ENTRIES);
+
+ await db.close();
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(async function test_roots_removed() {
+ let db = await PlacesUtils.promiseDBConnection();
+ let rows = await db.execute(
+ `
+ SELECT id FROM moz_bookmarks
+ WHERE guid = :guid
+ `,
+ { guid: PlacesUtils.bookmarks.rootGuid }
+ );
+ Assert.equal(rows.length, 1, "Should have exactly one root row.");
+ let rootId = rows[0].getResultByName("id");
+
+ rows = await db.execute(
+ `
+ SELECT guid FROM moz_bookmarks
+ WHERE parent = :rootId`,
+ { rootId }
+ );
+
+ Assert.equal(
+ rows.length,
+ EXPECTED_REMAINING_ROOTS.length,
+ "Should only have the built-in folder roots."
+ );
+
+ for (let row of rows) {
+ let guid = row.getResultByName("guid");
+ Assert.ok(
+ EXPECTED_REMAINING_ROOTS.includes(guid),
+ `Should have only the expected guids remaining, unexpected guid: ${guid}`
+ );
+ }
+
+ // Check the reparented menu now.
+ rows = await db.execute(
+ `
+ SELECT id, parent FROM moz_bookmarks
+ WHERE guid = :guid
+ `,
+ { guid: PlacesUtils.bookmarks.menuGuid }
+ );
+ Assert.equal(rows.length, 1, "Should have found the menu root.");
+ Assert.equal(
+ rows[0].getResultByName("parent"),
+ await PlacesUtils.promiseItemId(PlacesUtils.bookmarks.rootGuid),
+ "Should have moved the menu back to the Places root."
+ );
+});
+
+add_task(async function test_tombstones_added() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ let rows = await db.execute(`
+ SELECT guid FROM moz_bookmarks_deleted
+ `);
+
+ for (let row of rows) {
+ let guid = row.getResultByName("guid");
+ Assert.ok(
+ EXPECTED_REMOVED_BOOKMARK_GUIDS.includes(guid),
+ `Should have tombstoned the expected guids, unexpected guid: ${guid}`
+ );
+ }
+
+ Assert.equal(
+ rows.length,
+ EXPECTED_REMOVED_BOOKMARK_GUIDS.length,
+ "Should have removed all the expected bookmarks."
+ );
+});
+
+add_task(async function test_annotations_removed() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ await assertAnnotationsRemoved(db, EXPECTED_REMOVED_ANNOTATIONS);
+});
+
+add_task(async function test_check_history_entries() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ for (let entry of EXPECTED_REMOVED_PLACES_ENTRIES) {
+ let rows = await db.execute(`
+ SELECT id FROM moz_places
+ WHERE guid = '${entry}'`);
+
+ Assert.equal(
+ rows.length,
+ 0,
+ `Should have removed an orphaned history entry ${EXPECTED_REMOVED_PLACES_ENTRIES}.`
+ );
+ }
+
+ let rows = await db.execute(
+ `
+ SELECT foreign_count FROM moz_places
+ WHERE guid = :guid
+ `,
+ { guid: EXPECTED_KEPT_PLACES_ENTRY }
+ );
+
+ Assert.equal(
+ rows.length,
+ 1,
+ `Should have kept visited history entry ${EXPECTED_KEPT_PLACES_ENTRY}`
+ );
+
+ let foreignCount = rows[0].getResultByName("foreign_count");
+ Assert.equal(
+ foreignCount,
+ 0,
+ `Should have updated the foreign_count for ${EXPECTED_KEPT_PLACES_ENTRY}`
+ );
+});
+
+add_task(async function test_check_keyword_removed() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ for (let keyword of EXPECTED_REMOVED_KEYWORDS) {
+ let rows = await db.execute(
+ `
+ SELECT keyword FROM moz_keywords
+ WHERE keyword = :keyword
+ `,
+ { keyword }
+ );
+
+ Assert.equal(
+ rows.length,
+ 0,
+ `Should have removed the expected keyword: ${keyword}.`
+ );
+ }
+});
+
+add_task(async function test_no_orphan_annotations() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ await assertNoOrphanAnnotations(db);
+});
+
+add_task(async function test_no_orphan_keywords() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ let rows = await db.execute(`
+ SELECT place_id FROM moz_keywords
+ WHERE place_id NOT IN (SELECT id from moz_places)
+ `);
+
+ Assert.equal(rows.length, 0, `Should have no orphan keywords.`);
+});
+
+add_task(async function test_meta_exists() {
+ let db = await PlacesUtils.promiseDBConnection();
+ await db.execute(`SELECT 1 FROM moz_meta`);
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v45.js b/toolkit/components/places/tests/migration/test_current_from_v45.js
new file mode 100644
index 0000000000..af940d75d4
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v45.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let gTags = [
+ {
+ folder: 123456,
+ url: "place:folder=123456&type=7&queryType=1",
+ title: "tag1",
+ hash: "268505532566465",
+ },
+ {
+ folder: 234567,
+ url: "place:folder=234567&type=7&queryType=1&somethingelse",
+ title: "tag2",
+ hash: "268506675127932",
+ },
+ {
+ folder: 345678,
+ url: "place:type=7&folder=345678&queryType=1",
+ title: "tag3",
+ hash: "268506471927988",
+ },
+ // This will point to an invalid folder id.
+ {
+ folder: 456789,
+ url: "place:type=7&folder=456789&queryType=1",
+ expectedUrl:
+ "place:type=7&invalidOldParentId=456789&queryType=1&excludeItems=1",
+ title: "invalid",
+ hash: "268505972797836",
+ },
+];
+gTags.forEach(t => (t.guid = t.title.padEnd(12, "_")));
+
+add_task(async function setup() {
+ await setupPlacesDatabase("places_v43.sqlite");
+
+ // Setup database contents to be migrated.
+ let path = PathUtils.join(PathUtils.profileDir, DB_FILENAME);
+ let db = await Sqlite.openConnection({ path });
+
+ for (let tag of gTags) {
+ // We can reuse the same guid, it doesn't matter for this test.
+ await db.execute(
+ `INSERT INTO moz_places (url, guid, url_hash)
+ VALUES (:url, :guid, :hash)
+ `,
+ { url: tag.url, guid: tag.guid, hash: tag.hash }
+ );
+ if (tag.title != "invalid") {
+ await db.execute(
+ `INSERT INTO moz_bookmarks (id, fk, guid, title)
+ VALUES (:id, (SELECT id FROM moz_places WHERE guid = :guid), :guid, :title)
+ `,
+ { id: tag.folder, guid: tag.guid, title: tag.title }
+ );
+ }
+ }
+
+ await db.close();
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(async function test_queries_converted() {
+ for (let tag of gTags) {
+ let url =
+ tag.title == "invalid" ? tag.expectedUrl : "place:tag=" + tag.title;
+ let page = await PlacesUtils.history.fetch(tag.guid);
+ Assert.equal(page.url.href, url);
+ }
+});
+
+add_task(async function test_sync_fields() {
+ let db = await PlacesUtils.promiseDBConnection();
+ for (let tag of gTags) {
+ if (tag.title != "invalid") {
+ let rows = await db.execute(
+ `
+ SELECT syncChangeCounter
+ FROM moz_bookmarks
+ WHERE guid = :guid
+ `,
+ { guid: tag.guid }
+ );
+ Assert.equal(rows[0].getResultByIndex(0), 2);
+ }
+ }
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v46.js b/toolkit/components/places/tests/migration/test_current_from_v46.js
new file mode 100644
index 0000000000..a613a3027e
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v46.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let guid = "null".padEnd(12, "_");
+
+add_task(async function setup() {
+ await setupPlacesDatabase("places_v43.sqlite");
+
+ // Setup database contents to be migrated.
+ let path = PathUtils.join(PathUtils.profileDir, DB_FILENAME);
+ let db = await Sqlite.openConnection({ path });
+ // We can reuse the same guid, it doesn't matter for this test.
+
+ await db.execute(
+ `INSERT INTO moz_places (url, guid, url_hash)
+ VALUES (NULL, :guid, "123456")`,
+ { guid }
+ );
+ await db.execute(
+ `INSERT INTO moz_bookmarks (fk, guid)
+ VALUES ((SELECT id FROM moz_places WHERE guid = :guid), :guid)
+ `,
+ { guid }
+ );
+ await db.close();
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+
+ let page = await PlacesUtils.history.fetch(guid);
+ Assert.equal(page.url.href, "place:excludeItems=1");
+
+ let rows = await db.execute(
+ `
+ SELECT syncChangeCounter
+ FROM moz_bookmarks
+ WHERE guid = :guid
+ `,
+ { guid }
+ );
+ Assert.equal(rows[0].getResultByIndex(0), 2);
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v47.js b/toolkit/components/places/tests/migration/test_current_from_v47.js
new file mode 100644
index 0000000000..b3d5f47211
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v47.js
@@ -0,0 +1,128 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function setup() {
+ await setupPlacesDatabase("places_v43.sqlite");
+});
+
+// Accessing the database for the first time should trigger migration, and the
+// schema version should be updated.
+add_task(async function database_is_valid() {
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+
+ // Now wait for moz_origins.frecency to be populated before continuing with
+ // other test tasks.
+ await TestUtils.waitForCondition(
+ () => {
+ return !Services.prefs.getBoolPref(
+ "places.database.migrateV52OriginFrecencies",
+ false
+ );
+ },
+ "Waiting for v52 origin frecencies to be migrated",
+ 100,
+ 3000
+ );
+});
+
+// moz_origins should be populated.
+add_task(async function test_origins() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ // Collect origins.
+ let rows = await db.execute(`
+ SELECT id, prefix, host, frecency
+ FROM moz_origins
+ ORDER BY id ASC;
+ `);
+ Assert.notEqual(rows.length, 0);
+ let origins = rows.map(r => ({
+ id: r.getResultByName("id"),
+ prefix: r.getResultByName("prefix"),
+ host: r.getResultByName("host"),
+ frecency: r.getResultByName("frecency"),
+ }));
+
+ // Get moz_places.
+ rows = await db.execute(`
+ SELECT get_prefix(url) AS prefix, get_host_and_port(url) AS host,
+ origin_id, frecency
+ FROM moz_places;
+ `);
+ Assert.notEqual(rows.length, 0);
+
+ let seenOriginIDs = [];
+ let frecenciesByOriginID = {};
+
+ // Make sure moz_places.origin_id refers to the right origins.
+ for (let row of rows) {
+ let originID = row.getResultByName("origin_id");
+ let origin = origins.find(o => o.id == originID);
+ Assert.ok(origin);
+ Assert.equal(origin.prefix, row.getResultByName("prefix"));
+ Assert.equal(origin.host, row.getResultByName("host"));
+
+ seenOriginIDs.push(originID);
+
+ let frecency = row.getResultByName("frecency");
+ frecenciesByOriginID[originID] = frecenciesByOriginID[originID] || 0;
+ frecenciesByOriginID[originID] += frecency;
+ }
+
+ for (let origin of origins) {
+ // Make sure each origin corresponds to at least one moz_place.
+ Assert.ok(seenOriginIDs.includes(origin.id));
+
+ // moz_origins.frecency should be the sum of frecencies of all moz_places
+ // with the origin.
+ Assert.equal(origin.frecency, frecenciesByOriginID[origin.id]);
+ }
+
+ // Make sure moz_hosts was emptied.
+ rows = await db.execute(`
+ SELECT *
+ FROM moz_hosts;
+ `);
+ Assert.equal(rows.length, 0);
+});
+
+// Frecency stats should have been collected.
+add_task(async function test_frecency_stats() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ // Collect positive frecencies from moz_origins.
+ let rows = await db.execute(`
+ SELECT frecency FROM moz_origins WHERE frecency > 0
+ `);
+ Assert.notEqual(rows.length, 0);
+ let frecencies = rows.map(r => r.getResultByName("frecency"));
+
+ // Collect stats.
+ rows = await db.execute(`
+ SELECT
+ (SELECT value FROM moz_meta WHERE key = "origin_frecency_count"),
+ (SELECT value FROM moz_meta WHERE key = "origin_frecency_sum"),
+ (SELECT value FROM moz_meta WHERE key = "origin_frecency_sum_of_squares")
+ `);
+ let count = rows[0].getResultByIndex(0);
+ let sum = rows[0].getResultByIndex(1);
+ let squares = rows[0].getResultByIndex(2);
+
+ Assert.equal(count, frecencies.length);
+ Assert.equal(
+ sum,
+ frecencies.reduce((memo, f) => memo + f, 0)
+ );
+ Assert.equal(
+ squares,
+ frecencies.reduce((memo, f) => memo + f * f, 0)
+ );
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v48.js b/toolkit/components/places/tests/migration/test_current_from_v48.js
new file mode 100644
index 0000000000..f2c7c683ed
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v48.js
@@ -0,0 +1,190 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const gCreatedParentGuid = "m47___FOLDER";
+
+const gTestItems = [
+ {
+ // Folder shortcuts to built-in folders.
+ guid: "m47_____ROOT",
+ url: "place:folder=PLACES_ROOT",
+ targetParentGuid: "rootGuid",
+ },
+ {
+ guid: "m47_____MENU",
+ url: "place:folder=BOOKMARKS_MENU",
+ targetParentGuid: "menuGuid",
+ },
+ {
+ guid: "m47_____TAGS",
+ url: "place:folder=TAGS",
+ targetParentGuid: "tagsGuid",
+ },
+ {
+ guid: "m47____OTHER",
+ url: "place:folder=UNFILED_BOOKMARKS",
+ targetParentGuid: "unfiledGuid",
+ },
+ {
+ guid: "m47__TOOLBAR",
+ url: "place:folder=TOOLBAR",
+ targetParentGuid: "toolbarGuid",
+ },
+ {
+ guid: "m47___MOBILE",
+ url: "place:folder=MOBILE_BOOKMARKS",
+ targetParentGuid: "mobileGuid",
+ },
+ {
+ // Folder shortcut to using id.
+ guid: "m47_______ID",
+ url: "place:folder=%id%",
+ expectedUrl: "place:parent=%guid%",
+ },
+ {
+ // Folder shortcut to multiple folders.
+ guid: "m47____MULTI",
+ url: "place:folder=TOOLBAR&folder=%id%&sort=1",
+ expectedUrl: "place:parent=%toolbarGuid%&parent=%guid%&sort=1",
+ },
+ {
+ // Folder shortcut to non-existent folder.
+ guid: "m47______NON",
+ url: "place:folder=454554545",
+ expectedUrl: "place:invalidOldParentId=454554545&excludeItems=1",
+ },
+];
+
+add_task(async function setup() {
+ await setupPlacesDatabase("places_v43.sqlite");
+
+ // Setup database contents to be migrated.
+ let path = PathUtils.join(PathUtils.profileDir, DB_FILENAME);
+ let db = await Sqlite.openConnection({ path });
+
+ let rows = await db.execute(
+ `SELECT id FROM moz_bookmarks
+ WHERE guid = :guid`,
+ { guid: PlacesUtils.bookmarks.unfiledGuid }
+ );
+
+ let unfiledId = rows[0].getResultByName("id");
+
+ // Insert a test folder.
+ await db.execute(
+ `INSERT INTO moz_bookmarks (guid, title, parent)
+ VALUES (:guid, "Folder", :parent)`,
+ { guid: gCreatedParentGuid, parent: unfiledId }
+ );
+
+ rows = await db.execute(
+ `SELECT id FROM moz_bookmarks
+ WHERE guid = :guid`,
+ { guid: gCreatedParentGuid }
+ );
+
+ let createdFolderId = rows[0].getResultByName("id");
+
+ for (let item of gTestItems) {
+ item.url = item.url.replace("%id%", createdFolderId);
+
+ // We can reuse the same guid, it doesn't matter for this test.
+ await db.execute(
+ `INSERT INTO moz_places (url, guid, url_hash)
+ VALUES (:url, :guid, :hash)
+ `,
+ {
+ url: item.url,
+ guid: item.guid,
+ hash: PlacesUtils.history.hashURL(item.url),
+ }
+ );
+ await db.execute(
+ `INSERT INTO moz_bookmarks (id, fk, guid, title, parent)
+ VALUES (:id, (SELECT id FROM moz_places WHERE guid = :guid),
+ :guid, :title,
+ (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid))
+ `,
+ {
+ id: item.folder,
+ guid: item.guid,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: item.guid,
+ }
+ );
+ }
+
+ await db.close();
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(async function test_correct_folder_queries() {
+ for (let item of gTestItems) {
+ let bm = await PlacesUtils.bookmarks.fetch(item.guid);
+
+ if (item.targetParentGuid) {
+ Assert.equal(
+ bm.url,
+ `place:parent=${PlacesUtils.bookmarks[item.targetParentGuid]}`,
+ `Should have updated the URL for ${item.guid}`
+ );
+ } else {
+ let expected = item.expectedUrl
+ .replace("%guid%", gCreatedParentGuid)
+ .replace("%toolbarGuid%", PlacesUtils.bookmarks.toolbarGuid);
+
+ Assert.equal(
+ bm.url,
+ expected,
+ `Should have updated the URL for ${item.guid}`
+ );
+ }
+ }
+});
+
+add_task(async function test_hashes_valid() {
+ let db = await PlacesUtils.promiseDBConnection();
+ // Ensure all the hashes in moz_places are valid.
+ let rows = await db.execute(`SELECT url, url_hash FROM moz_places`);
+
+ for (let row of rows) {
+ let url = row.getResultByName("url");
+ let url_hash = row.getResultByName("url_hash");
+ Assert.equal(
+ url_hash,
+ PlacesUtils.history.hashURL(url),
+ `url hash should be correct for ${url}`
+ );
+ }
+});
+
+add_task(async function test_sync_counters_updated() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ for (let test of gTestItems) {
+ let rows = await db.execute(
+ `SELECT syncChangeCounter FROM moz_bookmarks
+ WHERE guid = :guid`,
+ { guid: test.guid }
+ );
+
+ Assert.equal(rows.length, 1, `Should only be one record for ${test.guid}`);
+ Assert.equal(
+ rows[0].getResultByName("syncChangeCounter"),
+ 2,
+ `Should have bumped the syncChangeCounter for ${test.guid}`
+ );
+ }
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v50.js b/toolkit/components/places/tests/migration/test_current_from_v50.js
new file mode 100644
index 0000000000..af181091c0
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v50.js
@@ -0,0 +1,209 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BASE_GUID = "null".padEnd(11, "_");
+const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
+const LAST_USED_META_DATA = "places/bookmarks/edit/lastusedfolder";
+
+let expectedGuids = [];
+
+async function adjustIndices(db, itemGuid) {
+ await db.execute(
+ `
+ UPDATE moz_bookmarks SET
+ position = position - 1
+ WHERE parent = (SELECT parent FROM moz_bookmarks
+ WHERE guid = :itemGuid) AND
+ position >= (SELECT position FROM moz_bookmarks
+ WHERE guid = :itemGuid)`,
+ { itemGuid }
+ );
+}
+
+async function fetchChildInfos(db, parentGuid) {
+ let rows = await db.execute(
+ `
+ SELECT b.guid, b.position, b.syncChangeCounter
+ FROM moz_bookmarks b
+ JOIN moz_bookmarks p ON p.id = b.parent
+ WHERE p.guid = :parentGuid
+ ORDER BY b.position`,
+ { parentGuid }
+ );
+ return rows.map(row => ({
+ guid: row.getResultByName("guid"),
+ position: row.getResultByName("position"),
+ syncChangeCounter: row.getResultByName("syncChangeCounter"),
+ }));
+}
+
+add_task(async function setup() {
+ await setupPlacesDatabase("places_v43.sqlite");
+
+ // Setup database contents to be migrated.
+ let path = PathUtils.join(PathUtils.profileDir, DB_FILENAME);
+ let db = await Sqlite.openConnection({ path });
+ // We can reuse the same guid, it doesn't matter for this test.
+ await db.execute(
+ `INSERT INTO moz_anno_attributes (name)
+ VALUES (:last_used_anno)`,
+ { last_used_anno: LAST_USED_ANNO }
+ );
+
+ for (let i = 0; i < 3; i++) {
+ let guid = `${BASE_GUID}${i}`;
+ await db.execute(
+ `INSERT INTO moz_bookmarks (guid, type)
+ VALUES (:guid, :type)
+ `,
+ { guid, type: PlacesUtils.bookmarks.TYPE_FOLDER }
+ );
+ await db.execute(
+ `INSERT INTO moz_items_annos (item_id, anno_attribute_id, content)
+ VALUES ((SELECT id FROM moz_bookmarks WHERE guid = :guid),
+ (SELECT id FROM moz_anno_attributes WHERE name = :last_used_anno),
+ :content)`,
+ {
+ guid,
+ content: new Date(1517318477569) - (3 - i) * 60 * 60 * 1000,
+ last_used_anno: LAST_USED_ANNO,
+ }
+ );
+ expectedGuids.unshift(guid);
+ }
+
+ info("Move menu into unfiled");
+ await adjustIndices(db, "menu________");
+ await db.execute(
+ `
+ UPDATE moz_bookmarks SET
+ parent = (SELECT id FROM moz_bookmarks WHERE guid = :newParentGuid),
+ position = IFNULL((SELECT MAX(position) + 1 FROM moz_bookmarks
+ WHERE guid = :newParentGuid), 0)
+ WHERE guid = :itemGuid`,
+ { newParentGuid: "unfiled_____", itemGuid: "menu________" }
+ );
+
+ info("Move toolbar into mobile");
+ let mobileChildren = [
+ "bookmarkAAAA",
+ "bookmarkBBBB",
+ "toolbar_____",
+ "bookmarkCCCC",
+ "bookmarkDDDD",
+ ];
+ await adjustIndices(db, "toolbar_____");
+ for (let position = 0; position < mobileChildren.length; position++) {
+ await db.execute(
+ `
+ INSERT INTO moz_bookmarks(guid, parent, position)
+ VALUES(:guid, (SELECT id FROM moz_bookmarks WHERE guid = 'mobile______'),
+ :position)
+ ON CONFLICT(guid) DO UPDATE SET
+ parent = excluded.parent,
+ position = excluded.position`,
+ { guid: mobileChildren[position], position }
+ );
+ }
+
+ info("Reset Sync change counters");
+ await db.execute(`UPDATE moz_bookmarks SET syncChangeCounter = 0`);
+
+ await db.close();
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(async function test_folders_migrated() {
+ let metaData = await PlacesUtils.metadata.get(LAST_USED_META_DATA);
+
+ Assert.deepEqual(JSON.parse(metaData), expectedGuids);
+});
+
+add_task(async function test_annotations_removed() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ await assertAnnotationsRemoved(db, [LAST_USED_ANNO]);
+});
+
+add_task(async function test_no_orphan_annotations() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ await assertNoOrphanAnnotations(db);
+});
+
+add_task(async function test_roots_fixed() {
+ let db = await PlacesUtils.promiseDBConnection();
+
+ let expectedRootInfos = [
+ {
+ guid: PlacesUtils.bookmarks.tagsGuid,
+ position: 0,
+ syncChangeCounter: 0,
+ },
+ {
+ guid: PlacesUtils.bookmarks.unfiledGuid,
+ position: 1,
+ syncChangeCounter: 1,
+ },
+ {
+ guid: PlacesUtils.bookmarks.mobileGuid,
+ position: 2,
+ syncChangeCounter: 1,
+ },
+ {
+ guid: PlacesUtils.bookmarks.menuGuid,
+ position: 3,
+ syncChangeCounter: 1,
+ },
+ {
+ guid: PlacesUtils.bookmarks.toolbarGuid,
+ position: 4,
+ syncChangeCounter: 1,
+ },
+ ];
+ Assert.deepEqual(
+ expectedRootInfos,
+ await fetchChildInfos(db, PlacesUtils.bookmarks.rootGuid),
+ "All roots should be reparented to the Places root"
+ );
+
+ let expectedMobileInfos = [
+ {
+ guid: "bookmarkAAAA",
+ position: 0,
+ syncChangeCounter: 0,
+ },
+ {
+ guid: "bookmarkBBBB",
+ position: 1,
+ syncChangeCounter: 0,
+ },
+ {
+ guid: "bookmarkCCCC",
+ position: 2,
+ syncChangeCounter: 0,
+ },
+ {
+ guid: "bookmarkDDDD",
+ position: 3,
+ syncChangeCounter: 0,
+ },
+ ];
+ Assert.deepEqual(
+ expectedMobileInfos,
+ await fetchChildInfos(db, PlacesUtils.bookmarks.mobileGuid),
+ "Should fix misparented root sibling positions"
+ );
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v53.js b/toolkit/components/places/tests/migration/test_current_from_v53.js
new file mode 100644
index 0000000000..f872dea5d5
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v53.js
@@ -0,0 +1,23 @@
+add_task(async function setup() {
+ // Since this migration doesn't affect places.sqlite, we can reuse v43.
+ await setupPlacesDatabase("places_v43.sqlite");
+ await setupPlacesDatabase("favicons_v41.sqlite", "favicons.sqlite");
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+
+ let count = (
+ await db.execute(
+ `SELECT count(*) FROM moz_icons_to_pages WHERE expire_ms = 0`
+ )
+ )[0].getResultByIndex(0);
+ Assert.equal(count, 0, "All the expirations should be set");
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v54.js b/toolkit/components/places/tests/migration/test_current_from_v54.js
new file mode 100644
index 0000000000..94c8a26474
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v54.js
@@ -0,0 +1,58 @@
+add_task(async function setup() {
+ // Since this migration doesn't affect places.sqlite, we can reuse v43.
+ await setupPlacesDatabase("places_v54.sqlite");
+ await setupPlacesDatabase("favicons_v41.sqlite", "favicons.sqlite");
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+
+ for (let table of [
+ "moz_places_metadata",
+ "moz_places_metadata_search_queries",
+ ]) {
+ let count = (
+ await db.execute(`SELECT count(*) FROM ${table}`)
+ )[0].getResultByIndex(0);
+ Assert.equal(count, 0, `Empty table ${table}`);
+ }
+
+ for (let table of [
+ "moz_places_metadata_snapshots",
+ "moz_places_metadata_snapshots_extra",
+ "moz_places_metadata_snapshots_groups",
+ "moz_places_metadata_groups_to_snapshots",
+ "moz_session_metadata",
+ "moz_session_to_places",
+ ]) {
+ await Assert.rejects(
+ db.execute(`SELECT count(*) FROM ${table}`),
+ /no such table/,
+ `Table ${table} should not exist`
+ );
+ }
+});
+
+add_task(async function scrolling_fields_in_database() {
+ let db = await PlacesUtils.promiseDBConnection();
+ await db.execute(
+ `SELECT scrolling_time,scrolling_distance FROM moz_places_metadata`
+ );
+});
+
+add_task(async function site_name_field_in_database() {
+ let db = await PlacesUtils.promiseDBConnection();
+ await db.execute(`SELECT site_name FROM moz_places`);
+});
+
+add_task(async function previews_tombstones_in_database() {
+ let db = await PlacesUtils.promiseDBConnection();
+ await db.execute(`SELECT hash FROM moz_previews_tombstones`);
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v66.js b/toolkit/components/places/tests/migration/test_current_from_v66.js
new file mode 100644
index 0000000000..5ea14f3b9d
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v66.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function setup() {
+ const path = await setupPlacesDatabase("places_v66.sqlite");
+
+ const db = await Sqlite.openConnection({ path });
+ await db.execute(`
+ INSERT INTO moz_inputhistory (input, use_count, place_id)
+ VALUES
+ ('abc', 1, 1),
+ ('aBc', 0.9, 1),
+ ('ABC', 5, 1),
+ ('ABC', 1, 2),
+ ('DEF', 1, 3)
+ `);
+ await db.close();
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ let db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(async function moz_inputhistory() {
+ await PlacesUtils.withConnectionWrapper("test_sqlite_migration", async db => {
+ const rows = await db.execute(
+ "SELECT * FROM moz_inputhistory ORDER BY place_id"
+ );
+
+ Assert.equal(rows.length, 3);
+
+ Assert.equal(rows[0].getResultByName("place_id"), 1);
+ Assert.equal(rows[0].getResultByName("input"), "abc");
+ Assert.equal(rows[0].getResultByName("use_count"), 5);
+
+ Assert.equal(rows[1].getResultByName("place_id"), 2);
+ Assert.equal(rows[1].getResultByName("input"), "abc");
+ Assert.equal(rows[1].getResultByName("use_count"), 1);
+
+ Assert.equal(rows[2].getResultByName("place_id"), 3);
+ Assert.equal(rows[2].getResultByName("input"), "def");
+ Assert.equal(rows[2].getResultByName("use_count"), 1);
+ });
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v68.js b/toolkit/components/places/tests/migration/test_current_from_v68.js
new file mode 100644
index 0000000000..689fcbfd40
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v68.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function setup() {
+ const path = await setupPlacesDatabase("places_v68.sqlite");
+
+ const db = await Sqlite.openConnection({ path });
+ await db.execute("INSERT INTO moz_historyvisits (from_visit) VALUES (-1)");
+ await db.close();
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ const db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(async function moz_historyvisits() {
+ await PlacesUtils.withConnectionWrapper("test_sqlite_migration", async db => {
+ const rows = await db.execute(
+ "SELECT * FROM moz_historyvisits WHERE from_visit=-1"
+ );
+
+ Assert.equal(rows.length, 1);
+ Assert.equal(rows[0].getResultByName("source"), 0);
+ Assert.equal(rows[0].getResultByName("triggeringPlaceId"), null);
+ });
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v69.js b/toolkit/components/places/tests/migration/test_current_from_v69.js
new file mode 100644
index 0000000000..09c66fb66e
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v69.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function setup() {
+ const path = await setupPlacesDatabase("places_v69.sqlite");
+
+ const db = await Sqlite.openConnection({ path });
+ await db.execute(`
+ INSERT INTO moz_places (url, guid, url_hash, origin_id, frecency)
+ VALUES
+ ('https://test1.com', '___________1', '123456', 100, 0),
+ ('https://test2.com', '___________2', '123456', 101, -1),
+ ('https://test3.com', '___________3', '123456', 102, -1234)
+ `);
+ await db.execute(`
+ INSERT INTO moz_origins (id, prefix, host, frecency)
+ VALUES
+ (100, 'https://', 'test1.com', 0),
+ (101, 'https://', 'test2.com', 0),
+ (102, 'https://', 'test3.com', 0)
+ `);
+ await db.close();
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ const db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(async function moz_historyvisits() {
+ await PlacesUtils.withConnectionWrapper("test_sqlite_migration", async db => {
+ function expectedFrecency(guid) {
+ switch (guid) {
+ case "___________1":
+ return 0;
+ case "___________2":
+ return -1;
+ case "___________3":
+ return 1234;
+ default:
+ throw new Error("Unknown guid");
+ }
+ }
+ const rows = await db.execute(
+ "SELECT guid, frecency FROM moz_places WHERE url_hash = '123456'"
+ );
+ for (let row of rows) {
+ Assert.equal(
+ row.getResultByName("frecency"),
+ expectedFrecency(row.getResultByName("guid")),
+ "Check expected frecency"
+ );
+ }
+ const origins = new Map(
+ (await db.execute("SELECT host, frecency FROM moz_origins")).map(r => [
+ r.getResultByName("host"),
+ r.getResultByName("frecency"),
+ ])
+ );
+ Assert.equal(origins.get("test1.com"), 0);
+ Assert.equal(origins.get("test2.com"), 0);
+ Assert.equal(origins.get("test3.com"), 1234);
+
+ const statSum = (
+ await db.execute(
+ "SELECT value FROM moz_meta WHERE key = 'origin_frecency_sum'"
+ )
+ )[0].getResultByName("value");
+ const sum = (
+ await db.execute(
+ "SELECT SUM(frecency) AS sum from moz_origins WHERE frecency > 0"
+ )
+ )[0].getResultByName("sum");
+ Assert.equal(sum, statSum, "Check stats were updated");
+ });
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v70.js b/toolkit/components/places/tests/migration/test_current_from_v70.js
new file mode 100644
index 0000000000..e5e41852e3
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v70.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function setup() {
+ let path = await setupPlacesDatabase("places_v70.sqlite");
+
+ let db = await Sqlite.openConnection({ path });
+ await db.execute(`
+ INSERT INTO moz_places (url, guid, url_hash, origin_id, frecency, foreign_count)
+ VALUES
+ ('https://test1.com', '___________1', '123456', 100, 0, 2),
+ ('https://test2.com', '___________2', '123456', 101, -1, 2),
+ ('https://test3.com', '___________3', '123456', 102, -1234, 1)
+ `);
+ await db.execute(`
+ INSERT INTO moz_origins (id, prefix, host, frecency)
+ VALUES
+ (100, 'https://', 'test1.com', 0),
+ (101, 'https://', 'test2.com', 0),
+ (102, 'https://', 'test3.com', 0)
+ `);
+ await db.execute(
+ `INSERT INTO moz_session_metadata
+ (id, guid)
+ VALUES (0, "0")
+ `
+ );
+
+ await db.execute(
+ `INSERT INTO moz_places_metadata_snapshots
+ (place_id, created_at, first_interaction_at, last_interaction_at)
+ VALUES ((SELECT id FROM moz_places WHERE guid = :guid), 0, 0, 0)
+ `,
+ { guid: "___________1" }
+ );
+ await db.execute(
+ `INSERT INTO moz_bookmarks
+ (fk, guid)
+ VALUES ((SELECT id FROM moz_places WHERE guid = :guid), :guid)
+ `,
+ { guid: "___________1" }
+ );
+
+ await db.execute(
+ `INSERT INTO moz_places_metadata_snapshots
+ (place_id, created_at, first_interaction_at, last_interaction_at)
+ VALUES ((SELECT id FROM moz_places WHERE guid = :guid), 0, 0, 0)
+ `,
+ { guid: "___________2" }
+ );
+ await db.execute(
+ `INSERT INTO moz_session_to_places
+ (session_id, place_id)
+ VALUES (0, (SELECT id FROM moz_places WHERE guid = :guid))
+ `,
+ { guid: "___________2" }
+ );
+
+ await db.execute(
+ `INSERT INTO moz_session_to_places
+ (session_id, place_id)
+ VALUES (0, (SELECT id FROM moz_places WHERE guid = :guid))
+ `,
+ { guid: "___________3" }
+ );
+
+ await db.close();
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ const db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+
+ let rows = await db.execute("SELECT guid, foreign_count FROM moz_places");
+ for (let row of rows) {
+ let guid = row.getResultByName("guid");
+ let count = row.getResultByName("foreign_count");
+ if (guid == "___________1") {
+ Assert.equal(count, 1, "test1 should have the correct foreign_count");
+ }
+ if (guid == "___________2") {
+ Assert.equal(count, 0, "test2 should have the correct foreign_count");
+ }
+ if (guid == "___________3") {
+ Assert.equal(count, 0, "test3 should have the correct foreign_count");
+ }
+ }
+});
diff --git a/toolkit/components/places/tests/migration/test_current_from_v72.js b/toolkit/components/places/tests/migration/test_current_from_v72.js
new file mode 100644
index 0000000000..626279fce4
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v72.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function setup() {
+ await setupPlacesDatabase("places_v72.sqlite");
+});
+
+add_task(async function database_is_valid() {
+ // Accessing the database for the first time triggers migration.
+ Assert.equal(
+ PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_UPGRADED
+ );
+
+ const db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+
+ await db.execute(
+ "SELECT recalc_frecency, alt_frecency, recalc_alt_frecency FROM moz_origins"
+ );
+
+ await db.execute("SELECT alt_frecency, recalc_alt_frecency FROM moz_places");
+ Assert.ok(
+ await db.indexExists("moz_places_altfrecencyindex"),
+ "Should have created an index"
+ );
+});
diff --git a/toolkit/components/places/tests/migration/xpcshell.ini b/toolkit/components/places/tests/migration/xpcshell.ini
new file mode 100644
index 0000000000..6f864171fc
--- /dev/null
+++ b/toolkit/components/places/tests/migration/xpcshell.ini
@@ -0,0 +1,33 @@
+[DEFAULT]
+head = head_migration.js
+tags = condprof
+
+support-files =
+ favicons_v41.sqlite
+ places_outdated.sqlite
+ places_v43.sqlite
+ places_v54.sqlite
+ places_v66.sqlite
+ places_v68.sqlite
+ places_v69.sqlite
+ places_v70.sqlite
+ places_v72.sqlite
+ places_v74.sqlite
+
+[test_current_from_downgraded.js]
+[test_current_from_outdated.js]
+[test_current_from_v43.js]
+[test_current_from_v45.js]
+[test_current_from_v46.js]
+[test_current_from_v47.js]
+[test_current_from_v48.js]
+[test_current_from_v50.js]
+[test_current_from_v53.js]
+skip-if = condprof # Bug 1769154 - not supported
+[test_current_from_v54.js]
+skip-if = condprof # Bug 1769154 - not supported
+[test_current_from_v66.js]
+[test_current_from_v68.js]
+[test_current_from_v69.js]
+[test_current_from_v70.js]
+[test_current_from_v72.js]