toolkit/components/places/PlacesDBUtils.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 * vim: sw=2 ts=2 sts=2 expandtab filetype=javascript
michael@0 3 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 const Cc = Components.classes;
michael@0 8 const Ci = Components.interfaces;
michael@0 9 const Cr = Components.results;
michael@0 10 const Cu = Components.utils;
michael@0 11
michael@0 12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 13 Cu.import("resource://gre/modules/Services.jsm");
michael@0 14 Cu.import("resource://gre/modules/PlacesUtils.jsm");
michael@0 15
michael@0 16 this.EXPORTED_SYMBOLS = [ "PlacesDBUtils" ];
michael@0 17
michael@0 18 ////////////////////////////////////////////////////////////////////////////////
michael@0 19 //// Constants
michael@0 20
michael@0 21 const FINISHED_MAINTENANCE_TOPIC = "places-maintenance-finished";
michael@0 22
michael@0 23 const BYTES_PER_MEBIBYTE = 1048576;
michael@0 24
michael@0 25 ////////////////////////////////////////////////////////////////////////////////
michael@0 26 //// Smart getters
michael@0 27
michael@0 28 XPCOMUtils.defineLazyGetter(this, "DBConn", function() {
michael@0 29 return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
michael@0 30 });
michael@0 31
michael@0 32 ////////////////////////////////////////////////////////////////////////////////
michael@0 33 //// PlacesDBUtils
michael@0 34
michael@0 35 this.PlacesDBUtils = {
michael@0 36 /**
michael@0 37 * Executes a list of maintenance tasks.
michael@0 38 * Once finished it will pass a array log to the callback attached to tasks.
michael@0 39 * FINISHED_MAINTENANCE_TOPIC is notified through observer service on finish.
michael@0 40 *
michael@0 41 * @param aTasks
michael@0 42 * Tasks object to execute.
michael@0 43 */
michael@0 44 _executeTasks: function PDBU__executeTasks(aTasks)
michael@0 45 {
michael@0 46 if (PlacesDBUtils._isShuttingDown) {
michael@0 47 aTasks.log("- We are shutting down. Will not schedule the tasks.");
michael@0 48 aTasks.clear();
michael@0 49 }
michael@0 50
michael@0 51 let task = aTasks.pop();
michael@0 52 if (task) {
michael@0 53 task.call(PlacesDBUtils, aTasks);
michael@0 54 }
michael@0 55 else {
michael@0 56 // All tasks have been completed.
michael@0 57 // Telemetry the time it took for maintenance, if a start time exists.
michael@0 58 if (aTasks._telemetryStart) {
michael@0 59 Services.telemetry.getHistogramById("PLACES_IDLE_MAINTENANCE_TIME_MS")
michael@0 60 .add(Date.now() - aTasks._telemetryStart);
michael@0 61 aTasks._telemetryStart = 0;
michael@0 62 }
michael@0 63
michael@0 64 if (aTasks.callback) {
michael@0 65 let scope = aTasks.scope || Cu.getGlobalForObject(aTasks.callback);
michael@0 66 aTasks.callback.call(scope, aTasks.messages);
michael@0 67 }
michael@0 68
michael@0 69 // Notify observers that maintenance finished.
michael@0 70 Services.prefs.setIntPref("places.database.lastMaintenance", parseInt(Date.now() / 1000));
michael@0 71 Services.obs.notifyObservers(null, FINISHED_MAINTENANCE_TOPIC, null);
michael@0 72 }
michael@0 73 },
michael@0 74
michael@0 75 _isShuttingDown : false,
michael@0 76 shutdown: function PDBU_shutdown() {
michael@0 77 PlacesDBUtils._isShuttingDown = true;
michael@0 78 },
michael@0 79
michael@0 80 /**
michael@0 81 * Executes integrity check and common maintenance tasks.
michael@0 82 *
michael@0 83 * @param [optional] aCallback
michael@0 84 * Callback to be invoked when done. The callback will get a array
michael@0 85 * of log messages.
michael@0 86 * @param [optional] aScope
michael@0 87 * Scope for the callback.
michael@0 88 */
michael@0 89 maintenanceOnIdle: function PDBU_maintenanceOnIdle(aCallback, aScope)
michael@0 90 {
michael@0 91 let tasks = new Tasks([
michael@0 92 this.checkIntegrity
michael@0 93 , this.checkCoherence
michael@0 94 , this._refreshUI
michael@0 95 ]);
michael@0 96 tasks._telemetryStart = Date.now();
michael@0 97 tasks.callback = aCallback;
michael@0 98 tasks.scope = aScope;
michael@0 99 this._executeTasks(tasks);
michael@0 100 },
michael@0 101
michael@0 102 /**
michael@0 103 * Executes integrity check, common and advanced maintenance tasks (like
michael@0 104 * expiration and vacuum). Will also collect statistics on the database.
michael@0 105 *
michael@0 106 * @param [optional] aCallback
michael@0 107 * Callback to be invoked when done. The callback will get a array
michael@0 108 * of log messages.
michael@0 109 * @param [optional] aScope
michael@0 110 * Scope for the callback.
michael@0 111 */
michael@0 112 checkAndFixDatabase: function PDBU_checkAndFixDatabase(aCallback, aScope)
michael@0 113 {
michael@0 114 let tasks = new Tasks([
michael@0 115 this.checkIntegrity
michael@0 116 , this.checkCoherence
michael@0 117 , this.expire
michael@0 118 , this.vacuum
michael@0 119 , this.stats
michael@0 120 , this._refreshUI
michael@0 121 ]);
michael@0 122 tasks.callback = aCallback;
michael@0 123 tasks.scope = aScope;
michael@0 124 this._executeTasks(tasks);
michael@0 125 },
michael@0 126
michael@0 127 /**
michael@0 128 * Forces a full refresh of Places views.
michael@0 129 *
michael@0 130 * @param [optional] aTasks
michael@0 131 * Tasks object to execute.
michael@0 132 */
michael@0 133 _refreshUI: function PDBU__refreshUI(aTasks)
michael@0 134 {
michael@0 135 let tasks = new Tasks(aTasks);
michael@0 136
michael@0 137 // Send batch update notifications to update the UI.
michael@0 138 PlacesUtils.history.runInBatchMode({
michael@0 139 runBatched: function (aUserData) {}
michael@0 140 }, null);
michael@0 141 PlacesDBUtils._executeTasks(tasks);
michael@0 142 },
michael@0 143
michael@0 144 _handleError: function PDBU__handleError(aError)
michael@0 145 {
michael@0 146 Cu.reportError("Async statement execution returned with '" +
michael@0 147 aError.result + "', '" + aError.message + "'");
michael@0 148 },
michael@0 149
michael@0 150 /**
michael@0 151 * Tries to execute a REINDEX on the database.
michael@0 152 *
michael@0 153 * @param [optional] aTasks
michael@0 154 * Tasks object to execute.
michael@0 155 */
michael@0 156 reindex: function PDBU_reindex(aTasks)
michael@0 157 {
michael@0 158 let tasks = new Tasks(aTasks);
michael@0 159 tasks.log("> Reindex");
michael@0 160
michael@0 161 let stmt = DBConn.createAsyncStatement("REINDEX");
michael@0 162 stmt.executeAsync({
michael@0 163 handleError: PlacesDBUtils._handleError,
michael@0 164 handleResult: function () {},
michael@0 165
michael@0 166 handleCompletion: function (aReason)
michael@0 167 {
michael@0 168 if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
michael@0 169 tasks.log("+ The database has been reindexed");
michael@0 170 }
michael@0 171 else {
michael@0 172 tasks.log("- Unable to reindex database");
michael@0 173 }
michael@0 174
michael@0 175 PlacesDBUtils._executeTasks(tasks);
michael@0 176 }
michael@0 177 });
michael@0 178 stmt.finalize();
michael@0 179 },
michael@0 180
michael@0 181 /**
michael@0 182 * Checks integrity but does not try to fix the database through a reindex.
michael@0 183 *
michael@0 184 * @param [optional] aTasks
michael@0 185 * Tasks object to execute.
michael@0 186 */
michael@0 187 _checkIntegritySkipReindex: function PDBU__checkIntegritySkipReindex(aTasks)
michael@0 188 this.checkIntegrity(aTasks, true),
michael@0 189
michael@0 190 /**
michael@0 191 * Checks integrity and tries to fix the database through a reindex.
michael@0 192 *
michael@0 193 * @param [optional] aTasks
michael@0 194 * Tasks object to execute.
michael@0 195 * @param [optional] aSkipdReindex
michael@0 196 * Whether to try to reindex database or not.
michael@0 197 */
michael@0 198 checkIntegrity: function PDBU_checkIntegrity(aTasks, aSkipReindex)
michael@0 199 {
michael@0 200 let tasks = new Tasks(aTasks);
michael@0 201 tasks.log("> Integrity check");
michael@0 202
michael@0 203 // Run a integrity check, but stop at the first error.
michael@0 204 let stmt = DBConn.createAsyncStatement("PRAGMA integrity_check(1)");
michael@0 205 stmt.executeAsync({
michael@0 206 handleError: PlacesDBUtils._handleError,
michael@0 207
michael@0 208 _corrupt: false,
michael@0 209 handleResult: function (aResultSet)
michael@0 210 {
michael@0 211 let row = aResultSet.getNextRow();
michael@0 212 this._corrupt = row.getResultByIndex(0) != "ok";
michael@0 213 },
michael@0 214
michael@0 215 handleCompletion: function (aReason)
michael@0 216 {
michael@0 217 if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
michael@0 218 if (this._corrupt) {
michael@0 219 tasks.log("- The database is corrupt");
michael@0 220 if (aSkipReindex) {
michael@0 221 tasks.log("- Unable to fix corruption, database will be replaced on next startup");
michael@0 222 Services.prefs.setBoolPref("places.database.replaceOnStartup", true);
michael@0 223 tasks.clear();
michael@0 224 }
michael@0 225 else {
michael@0 226 // Try to reindex, this often fixed simple indices corruption.
michael@0 227 // We insert from the top of the queue, they will run inverse.
michael@0 228 tasks.push(PlacesDBUtils._checkIntegritySkipReindex);
michael@0 229 tasks.push(PlacesDBUtils.reindex);
michael@0 230 }
michael@0 231 }
michael@0 232 else {
michael@0 233 tasks.log("+ The database is sane");
michael@0 234 }
michael@0 235 }
michael@0 236 else {
michael@0 237 tasks.log("- Unable to check database status");
michael@0 238 tasks.clear();
michael@0 239 }
michael@0 240
michael@0 241 PlacesDBUtils._executeTasks(tasks);
michael@0 242 }
michael@0 243 });
michael@0 244 stmt.finalize();
michael@0 245 },
michael@0 246
michael@0 247 /**
michael@0 248 * Checks data coherence and tries to fix most common errors.
michael@0 249 *
michael@0 250 * @param [optional] aTasks
michael@0 251 * Tasks object to execute.
michael@0 252 */
michael@0 253 checkCoherence: function PDBU_checkCoherence(aTasks)
michael@0 254 {
michael@0 255 let tasks = new Tasks(aTasks);
michael@0 256 tasks.log("> Coherence check");
michael@0 257
michael@0 258 let stmts = PlacesDBUtils._getBoundCoherenceStatements();
michael@0 259 DBConn.executeAsync(stmts, stmts.length, {
michael@0 260 handleError: PlacesDBUtils._handleError,
michael@0 261 handleResult: function () {},
michael@0 262
michael@0 263 handleCompletion: function (aReason)
michael@0 264 {
michael@0 265 if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
michael@0 266 tasks.log("+ The database is coherent");
michael@0 267 }
michael@0 268 else {
michael@0 269 tasks.log("- Unable to check database coherence");
michael@0 270 tasks.clear();
michael@0 271 }
michael@0 272
michael@0 273 PlacesDBUtils._executeTasks(tasks);
michael@0 274 }
michael@0 275 });
michael@0 276 stmts.forEach(function (aStmt) aStmt.finalize());
michael@0 277 },
michael@0 278
michael@0 279 _getBoundCoherenceStatements: function PDBU__getBoundCoherenceStatements()
michael@0 280 {
michael@0 281 let cleanupStatements = [];
michael@0 282
michael@0 283 // MOZ_ANNO_ATTRIBUTES
michael@0 284 // A.1 remove obsolete annotations from moz_annos.
michael@0 285 // The 'weave0' idiom exploits character ordering (0 follows /) to
michael@0 286 // efficiently select all annos with a 'weave/' prefix.
michael@0 287 let deleteObsoleteAnnos = DBConn.createAsyncStatement(
michael@0 288 "DELETE FROM moz_annos " +
michael@0 289 "WHERE anno_attribute_id IN ( " +
michael@0 290 " SELECT id FROM moz_anno_attributes " +
michael@0 291 " WHERE name BETWEEN 'weave/' AND 'weave0' " +
michael@0 292 ")");
michael@0 293 cleanupStatements.push(deleteObsoleteAnnos);
michael@0 294
michael@0 295 // A.2 remove obsolete annotations from moz_items_annos.
michael@0 296 let deleteObsoleteItemsAnnos = DBConn.createAsyncStatement(
michael@0 297 "DELETE FROM moz_items_annos " +
michael@0 298 "WHERE anno_attribute_id IN ( " +
michael@0 299 " SELECT id FROM moz_anno_attributes " +
michael@0 300 " WHERE name = 'sync/children' " +
michael@0 301 " OR name = 'placesInternal/GUID' " +
michael@0 302 " OR name BETWEEN 'weave/' AND 'weave0' " +
michael@0 303 ")");
michael@0 304 cleanupStatements.push(deleteObsoleteItemsAnnos);
michael@0 305
michael@0 306 // A.3 remove unused attributes.
michael@0 307 let deleteUnusedAnnoAttributes = DBConn.createAsyncStatement(
michael@0 308 "DELETE FROM moz_anno_attributes WHERE id IN ( " +
michael@0 309 "SELECT id FROM moz_anno_attributes n " +
michael@0 310 "WHERE NOT EXISTS " +
michael@0 311 "(SELECT id FROM moz_annos WHERE anno_attribute_id = n.id LIMIT 1) " +
michael@0 312 "AND NOT EXISTS " +
michael@0 313 "(SELECT id FROM moz_items_annos WHERE anno_attribute_id = n.id LIMIT 1) " +
michael@0 314 ")");
michael@0 315 cleanupStatements.push(deleteUnusedAnnoAttributes);
michael@0 316
michael@0 317 // MOZ_ANNOS
michael@0 318 // B.1 remove annos with an invalid attribute
michael@0 319 let deleteInvalidAttributeAnnos = DBConn.createAsyncStatement(
michael@0 320 "DELETE FROM moz_annos WHERE id IN ( " +
michael@0 321 "SELECT id FROM moz_annos a " +
michael@0 322 "WHERE NOT EXISTS " +
michael@0 323 "(SELECT id FROM moz_anno_attributes " +
michael@0 324 "WHERE id = a.anno_attribute_id LIMIT 1) " +
michael@0 325 ")");
michael@0 326 cleanupStatements.push(deleteInvalidAttributeAnnos);
michael@0 327
michael@0 328 // B.2 remove orphan annos
michael@0 329 let deleteOrphanAnnos = DBConn.createAsyncStatement(
michael@0 330 "DELETE FROM moz_annos WHERE id IN ( " +
michael@0 331 "SELECT id FROM moz_annos a " +
michael@0 332 "WHERE NOT EXISTS " +
michael@0 333 "(SELECT id FROM moz_places WHERE id = a.place_id LIMIT 1) " +
michael@0 334 ")");
michael@0 335 cleanupStatements.push(deleteOrphanAnnos);
michael@0 336
michael@0 337 // MOZ_BOOKMARKS_ROOTS
michael@0 338 // C.1 fix missing Places root
michael@0 339 // Bug 477739 shows a case where the root could be wrongly removed
michael@0 340 // due to an endianness issue. We try to fix broken roots here.
michael@0 341 let selectPlacesRoot = DBConn.createStatement(
michael@0 342 "SELECT id FROM moz_bookmarks WHERE id = :places_root");
michael@0 343 selectPlacesRoot.params["places_root"] = PlacesUtils.placesRootId;
michael@0 344 if (!selectPlacesRoot.executeStep()) {
michael@0 345 // We are missing the root, try to recreate it.
michael@0 346 let createPlacesRoot = DBConn.createAsyncStatement(
michael@0 347 "INSERT INTO moz_bookmarks (id, type, fk, parent, position, title, "
michael@0 348 + "guid) "
michael@0 349 + "VALUES (:places_root, 2, NULL, 0, 0, :title, GENERATE_GUID())");
michael@0 350 createPlacesRoot.params["places_root"] = PlacesUtils.placesRootId;
michael@0 351 createPlacesRoot.params["title"] = "";
michael@0 352 cleanupStatements.push(createPlacesRoot);
michael@0 353
michael@0 354 // Now ensure that other roots are children of Places root.
michael@0 355 let fixPlacesRootChildren = DBConn.createAsyncStatement(
michael@0 356 "UPDATE moz_bookmarks SET parent = :places_root WHERE id IN " +
michael@0 357 "(SELECT folder_id FROM moz_bookmarks_roots " +
michael@0 358 "WHERE folder_id <> :places_root)");
michael@0 359 fixPlacesRootChildren.params["places_root"] = PlacesUtils.placesRootId;
michael@0 360 cleanupStatements.push(fixPlacesRootChildren);
michael@0 361 }
michael@0 362 selectPlacesRoot.finalize();
michael@0 363
michael@0 364 // C.2 fix roots titles
michael@0 365 // some alpha version has wrong roots title, and this also fixes them if
michael@0 366 // locale has changed.
michael@0 367 let updateRootTitleSql = "UPDATE moz_bookmarks SET title = :title " +
michael@0 368 "WHERE id = :root_id AND title <> :title";
michael@0 369 // root
michael@0 370 let fixPlacesRootTitle = DBConn.createAsyncStatement(updateRootTitleSql);
michael@0 371 fixPlacesRootTitle.params["root_id"] = PlacesUtils.placesRootId;
michael@0 372 fixPlacesRootTitle.params["title"] = "";
michael@0 373 cleanupStatements.push(fixPlacesRootTitle);
michael@0 374 // bookmarks menu
michael@0 375 let fixBookmarksMenuTitle = DBConn.createAsyncStatement(updateRootTitleSql);
michael@0 376 fixBookmarksMenuTitle.params["root_id"] = PlacesUtils.bookmarksMenuFolderId;
michael@0 377 fixBookmarksMenuTitle.params["title"] =
michael@0 378 PlacesUtils.getString("BookmarksMenuFolderTitle");
michael@0 379 cleanupStatements.push(fixBookmarksMenuTitle);
michael@0 380 // bookmarks toolbar
michael@0 381 let fixBookmarksToolbarTitle = DBConn.createAsyncStatement(updateRootTitleSql);
michael@0 382 fixBookmarksToolbarTitle.params["root_id"] = PlacesUtils.toolbarFolderId;
michael@0 383 fixBookmarksToolbarTitle.params["title"] =
michael@0 384 PlacesUtils.getString("BookmarksToolbarFolderTitle");
michael@0 385 cleanupStatements.push(fixBookmarksToolbarTitle);
michael@0 386 // unsorted bookmarks
michael@0 387 let fixUnsortedBookmarksTitle = DBConn.createAsyncStatement(updateRootTitleSql);
michael@0 388 fixUnsortedBookmarksTitle.params["root_id"] = PlacesUtils.unfiledBookmarksFolderId;
michael@0 389 fixUnsortedBookmarksTitle.params["title"] =
michael@0 390 PlacesUtils.getString("UnsortedBookmarksFolderTitle");
michael@0 391 cleanupStatements.push(fixUnsortedBookmarksTitle);
michael@0 392 // tags
michael@0 393 let fixTagsRootTitle = DBConn.createAsyncStatement(updateRootTitleSql);
michael@0 394 fixTagsRootTitle.params["root_id"] = PlacesUtils.tagsFolderId;
michael@0 395 fixTagsRootTitle.params["title"] =
michael@0 396 PlacesUtils.getString("TagsFolderTitle");
michael@0 397 cleanupStatements.push(fixTagsRootTitle);
michael@0 398
michael@0 399 // MOZ_BOOKMARKS
michael@0 400 // D.1 remove items without a valid place
michael@0 401 // if fk IS NULL we fix them in D.7
michael@0 402 let deleteNoPlaceItems = DBConn.createAsyncStatement(
michael@0 403 "DELETE FROM moz_bookmarks WHERE id NOT IN ( " +
michael@0 404 "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
michael@0 405 ") AND id IN (" +
michael@0 406 "SELECT b.id FROM moz_bookmarks b " +
michael@0 407 "WHERE fk NOT NULL AND b.type = :bookmark_type " +
michael@0 408 "AND NOT EXISTS (SELECT url FROM moz_places WHERE id = b.fk LIMIT 1) " +
michael@0 409 ")");
michael@0 410 deleteNoPlaceItems.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
michael@0 411 cleanupStatements.push(deleteNoPlaceItems);
michael@0 412
michael@0 413 // D.2 remove items that are not uri bookmarks from tag containers
michael@0 414 let deleteBogusTagChildren = DBConn.createAsyncStatement(
michael@0 415 "DELETE FROM moz_bookmarks WHERE id NOT IN ( " +
michael@0 416 "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
michael@0 417 ") AND id IN (" +
michael@0 418 "SELECT b.id FROM moz_bookmarks b " +
michael@0 419 "WHERE b.parent IN " +
michael@0 420 "(SELECT id FROM moz_bookmarks WHERE parent = :tags_folder) " +
michael@0 421 "AND b.type <> :bookmark_type " +
michael@0 422 ")");
michael@0 423 deleteBogusTagChildren.params["tags_folder"] = PlacesUtils.tagsFolderId;
michael@0 424 deleteBogusTagChildren.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
michael@0 425 cleanupStatements.push(deleteBogusTagChildren);
michael@0 426
michael@0 427 // D.3 remove empty tags
michael@0 428 let deleteEmptyTags = DBConn.createAsyncStatement(
michael@0 429 "DELETE FROM moz_bookmarks WHERE id NOT IN ( " +
michael@0 430 "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
michael@0 431 ") AND id IN (" +
michael@0 432 "SELECT b.id FROM moz_bookmarks b " +
michael@0 433 "WHERE b.id IN " +
michael@0 434 "(SELECT id FROM moz_bookmarks WHERE parent = :tags_folder) " +
michael@0 435 "AND NOT EXISTS " +
michael@0 436 "(SELECT id from moz_bookmarks WHERE parent = b.id LIMIT 1) " +
michael@0 437 ")");
michael@0 438 deleteEmptyTags.params["tags_folder"] = PlacesUtils.tagsFolderId;
michael@0 439 cleanupStatements.push(deleteEmptyTags);
michael@0 440
michael@0 441 // D.4 move orphan items to unsorted folder
michael@0 442 let fixOrphanItems = DBConn.createAsyncStatement(
michael@0 443 "UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE id NOT IN ( " +
michael@0 444 "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
michael@0 445 ") AND id IN (" +
michael@0 446 "SELECT b.id FROM moz_bookmarks b " +
michael@0 447 "WHERE b.parent <> 0 " + // exclude Places root
michael@0 448 "AND NOT EXISTS " +
michael@0 449 "(SELECT id FROM moz_bookmarks WHERE id = b.parent LIMIT 1) " +
michael@0 450 ")");
michael@0 451 fixOrphanItems.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId;
michael@0 452 cleanupStatements.push(fixOrphanItems);
michael@0 453
michael@0 454 // D.5 fix wrong keywords
michael@0 455 let fixInvalidKeywords = DBConn.createAsyncStatement(
michael@0 456 "UPDATE moz_bookmarks SET keyword_id = NULL WHERE id NOT IN ( " +
michael@0 457 "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
michael@0 458 ") AND id IN ( " +
michael@0 459 "SELECT id FROM moz_bookmarks b " +
michael@0 460 "WHERE keyword_id NOT NULL " +
michael@0 461 "AND NOT EXISTS " +
michael@0 462 "(SELECT id FROM moz_keywords WHERE id = b.keyword_id LIMIT 1) " +
michael@0 463 ")");
michael@0 464 cleanupStatements.push(fixInvalidKeywords);
michael@0 465
michael@0 466 // D.6 fix wrong item types
michael@0 467 // Folders and separators should not have an fk.
michael@0 468 // If they have a valid fk convert them to bookmarks. Later in D.9 we
michael@0 469 // will move eventual children to unsorted bookmarks.
michael@0 470 let fixBookmarksAsFolders = DBConn.createAsyncStatement(
michael@0 471 "UPDATE moz_bookmarks SET type = :bookmark_type WHERE id NOT IN ( " +
michael@0 472 "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
michael@0 473 ") AND id IN ( " +
michael@0 474 "SELECT id FROM moz_bookmarks b " +
michael@0 475 "WHERE type IN (:folder_type, :separator_type) " +
michael@0 476 "AND fk NOTNULL " +
michael@0 477 ")");
michael@0 478 fixBookmarksAsFolders.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
michael@0 479 fixBookmarksAsFolders.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
michael@0 480 fixBookmarksAsFolders.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR;
michael@0 481 cleanupStatements.push(fixBookmarksAsFolders);
michael@0 482
michael@0 483 // D.7 fix wrong item types
michael@0 484 // Bookmarks should have an fk, if they don't have any, convert them to
michael@0 485 // folders.
michael@0 486 let fixFoldersAsBookmarks = DBConn.createAsyncStatement(
michael@0 487 "UPDATE moz_bookmarks SET type = :folder_type WHERE id NOT IN ( " +
michael@0 488 "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
michael@0 489 ") AND id IN ( " +
michael@0 490 "SELECT id FROM moz_bookmarks b " +
michael@0 491 "WHERE type = :bookmark_type " +
michael@0 492 "AND fk IS NULL " +
michael@0 493 ")");
michael@0 494 fixFoldersAsBookmarks.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
michael@0 495 fixFoldersAsBookmarks.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
michael@0 496 cleanupStatements.push(fixFoldersAsBookmarks);
michael@0 497
michael@0 498 // D.9 fix wrong parents
michael@0 499 // Items cannot have separators or other bookmarks
michael@0 500 // as parent, if they have bad parent move them to unsorted bookmarks.
michael@0 501 let fixInvalidParents = DBConn.createAsyncStatement(
michael@0 502 "UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE id NOT IN ( " +
michael@0 503 "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
michael@0 504 ") AND id IN ( " +
michael@0 505 "SELECT id FROM moz_bookmarks b " +
michael@0 506 "WHERE EXISTS " +
michael@0 507 "(SELECT id FROM moz_bookmarks WHERE id = b.parent " +
michael@0 508 "AND type IN (:bookmark_type, :separator_type) " +
michael@0 509 "LIMIT 1) " +
michael@0 510 ")");
michael@0 511 fixInvalidParents.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId;
michael@0 512 fixInvalidParents.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
michael@0 513 fixInvalidParents.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR;
michael@0 514 cleanupStatements.push(fixInvalidParents);
michael@0 515
michael@0 516 // D.10 recalculate positions
michael@0 517 // This requires multiple related statements.
michael@0 518 // We can detect a folder with bad position values comparing the sum of
michael@0 519 // all distinct position values (+1 since position is 0-based) with the
michael@0 520 // triangular numbers obtained by the number of children (n).
michael@0 521 // SUM(DISTINCT position + 1) == (n * (n + 1) / 2).
michael@0 522 cleanupStatements.push(DBConn.createAsyncStatement(
michael@0 523 "CREATE TEMP TABLE IF NOT EXISTS moz_bm_reindex_temp ( " +
michael@0 524 " id INTEGER PRIMARY_KEY " +
michael@0 525 ", parent INTEGER " +
michael@0 526 ", position INTEGER " +
michael@0 527 ") "
michael@0 528 ));
michael@0 529 cleanupStatements.push(DBConn.createAsyncStatement(
michael@0 530 "INSERT INTO moz_bm_reindex_temp " +
michael@0 531 "SELECT id, parent, 0 " +
michael@0 532 "FROM moz_bookmarks b " +
michael@0 533 "WHERE parent IN ( " +
michael@0 534 "SELECT parent " +
michael@0 535 "FROM moz_bookmarks " +
michael@0 536 "GROUP BY parent " +
michael@0 537 "HAVING (SUM(DISTINCT position + 1) - (count(*) * (count(*) + 1) / 2)) <> 0 " +
michael@0 538 ") " +
michael@0 539 "ORDER BY parent ASC, position ASC, ROWID ASC "
michael@0 540 ));
michael@0 541 cleanupStatements.push(DBConn.createAsyncStatement(
michael@0 542 "CREATE INDEX IF NOT EXISTS moz_bm_reindex_temp_index " +
michael@0 543 "ON moz_bm_reindex_temp(parent)"
michael@0 544 ));
michael@0 545 cleanupStatements.push(DBConn.createAsyncStatement(
michael@0 546 "UPDATE moz_bm_reindex_temp SET position = ( " +
michael@0 547 "ROWID - (SELECT MIN(t.ROWID) FROM moz_bm_reindex_temp t " +
michael@0 548 "WHERE t.parent = moz_bm_reindex_temp.parent) " +
michael@0 549 ") "
michael@0 550 ));
michael@0 551 cleanupStatements.push(DBConn.createAsyncStatement(
michael@0 552 "CREATE TEMP TRIGGER IF NOT EXISTS moz_bm_reindex_temp_trigger " +
michael@0 553 "BEFORE DELETE ON moz_bm_reindex_temp " +
michael@0 554 "FOR EACH ROW " +
michael@0 555 "BEGIN " +
michael@0 556 "UPDATE moz_bookmarks SET position = OLD.position WHERE id = OLD.id; " +
michael@0 557 "END "
michael@0 558 ));
michael@0 559 cleanupStatements.push(DBConn.createAsyncStatement(
michael@0 560 "DELETE FROM moz_bm_reindex_temp "
michael@0 561 ));
michael@0 562 cleanupStatements.push(DBConn.createAsyncStatement(
michael@0 563 "DROP INDEX moz_bm_reindex_temp_index "
michael@0 564 ));
michael@0 565 cleanupStatements.push(DBConn.createAsyncStatement(
michael@0 566 "DROP TRIGGER moz_bm_reindex_temp_trigger "
michael@0 567 ));
michael@0 568 cleanupStatements.push(DBConn.createAsyncStatement(
michael@0 569 "DROP TABLE moz_bm_reindex_temp "
michael@0 570 ));
michael@0 571
michael@0 572 // D.12 Fix empty-named tags.
michael@0 573 // Tags were allowed to have empty names due to a UI bug. Fix them
michael@0 574 // replacing their title with "(notitle)".
michael@0 575 let fixEmptyNamedTags = DBConn.createAsyncStatement(
michael@0 576 "UPDATE moz_bookmarks SET title = :empty_title " +
michael@0 577 "WHERE length(title) = 0 AND type = :folder_type " +
michael@0 578 "AND parent = :tags_folder"
michael@0 579 );
michael@0 580 fixEmptyNamedTags.params["empty_title"] = "(notitle)";
michael@0 581 fixEmptyNamedTags.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
michael@0 582 fixEmptyNamedTags.params["tags_folder"] = PlacesUtils.tagsFolderId;
michael@0 583 cleanupStatements.push(fixEmptyNamedTags);
michael@0 584
michael@0 585 // MOZ_FAVICONS
michael@0 586 // E.1 remove orphan icons
michael@0 587 let deleteOrphanIcons = DBConn.createAsyncStatement(
michael@0 588 "DELETE FROM moz_favicons WHERE id IN (" +
michael@0 589 "SELECT id FROM moz_favicons f " +
michael@0 590 "WHERE NOT EXISTS " +
michael@0 591 "(SELECT id FROM moz_places WHERE favicon_id = f.id LIMIT 1) " +
michael@0 592 ")");
michael@0 593 cleanupStatements.push(deleteOrphanIcons);
michael@0 594
michael@0 595 // MOZ_HISTORYVISITS
michael@0 596 // F.1 remove orphan visits
michael@0 597 let deleteOrphanVisits = DBConn.createAsyncStatement(
michael@0 598 "DELETE FROM moz_historyvisits WHERE id IN (" +
michael@0 599 "SELECT id FROM moz_historyvisits v " +
michael@0 600 "WHERE NOT EXISTS " +
michael@0 601 "(SELECT id FROM moz_places WHERE id = v.place_id LIMIT 1) " +
michael@0 602 ")");
michael@0 603 cleanupStatements.push(deleteOrphanVisits);
michael@0 604
michael@0 605 // MOZ_INPUTHISTORY
michael@0 606 // G.1 remove orphan input history
michael@0 607 let deleteOrphanInputHistory = DBConn.createAsyncStatement(
michael@0 608 "DELETE FROM moz_inputhistory WHERE place_id IN (" +
michael@0 609 "SELECT place_id FROM moz_inputhistory i " +
michael@0 610 "WHERE NOT EXISTS " +
michael@0 611 "(SELECT id FROM moz_places WHERE id = i.place_id LIMIT 1) " +
michael@0 612 ")");
michael@0 613 cleanupStatements.push(deleteOrphanInputHistory);
michael@0 614
michael@0 615 // MOZ_ITEMS_ANNOS
michael@0 616 // H.1 remove item annos with an invalid attribute
michael@0 617 let deleteInvalidAttributeItemsAnnos = DBConn.createAsyncStatement(
michael@0 618 "DELETE FROM moz_items_annos WHERE id IN ( " +
michael@0 619 "SELECT id FROM moz_items_annos t " +
michael@0 620 "WHERE NOT EXISTS " +
michael@0 621 "(SELECT id FROM moz_anno_attributes " +
michael@0 622 "WHERE id = t.anno_attribute_id LIMIT 1) " +
michael@0 623 ")");
michael@0 624 cleanupStatements.push(deleteInvalidAttributeItemsAnnos);
michael@0 625
michael@0 626 // H.2 remove orphan item annos
michael@0 627 let deleteOrphanItemsAnnos = DBConn.createAsyncStatement(
michael@0 628 "DELETE FROM moz_items_annos WHERE id IN ( " +
michael@0 629 "SELECT id FROM moz_items_annos t " +
michael@0 630 "WHERE NOT EXISTS " +
michael@0 631 "(SELECT id FROM moz_bookmarks WHERE id = t.item_id LIMIT 1) " +
michael@0 632 ")");
michael@0 633 cleanupStatements.push(deleteOrphanItemsAnnos);
michael@0 634
michael@0 635 // MOZ_KEYWORDS
michael@0 636 // I.1 remove unused keywords
michael@0 637 let deleteUnusedKeywords = DBConn.createAsyncStatement(
michael@0 638 "DELETE FROM moz_keywords WHERE id IN ( " +
michael@0 639 "SELECT id FROM moz_keywords k " +
michael@0 640 "WHERE NOT EXISTS " +
michael@0 641 "(SELECT id FROM moz_bookmarks WHERE keyword_id = k.id LIMIT 1) " +
michael@0 642 ")");
michael@0 643 cleanupStatements.push(deleteUnusedKeywords);
michael@0 644
michael@0 645 // MOZ_PLACES
michael@0 646 // L.1 fix wrong favicon ids
michael@0 647 let fixInvalidFaviconIds = DBConn.createAsyncStatement(
michael@0 648 "UPDATE moz_places SET favicon_id = NULL WHERE id IN ( " +
michael@0 649 "SELECT id FROM moz_places h " +
michael@0 650 "WHERE favicon_id NOT NULL " +
michael@0 651 "AND NOT EXISTS " +
michael@0 652 "(SELECT id FROM moz_favicons WHERE id = h.favicon_id LIMIT 1) " +
michael@0 653 ")");
michael@0 654 cleanupStatements.push(fixInvalidFaviconIds);
michael@0 655
michael@0 656 // L.2 recalculate visit_count and last_visit_date
michael@0 657 let fixVisitStats = DBConn.createAsyncStatement(
michael@0 658 "UPDATE moz_places " +
michael@0 659 "SET visit_count = (SELECT count(*) FROM moz_historyvisits " +
michael@0 660 "WHERE place_id = moz_places.id AND visit_type NOT IN (0,4,7,8)), " +
michael@0 661 "last_visit_date = (SELECT MAX(visit_date) FROM moz_historyvisits " +
michael@0 662 "WHERE place_id = moz_places.id) " +
michael@0 663 "WHERE id IN ( " +
michael@0 664 "SELECT h.id FROM moz_places h " +
michael@0 665 "WHERE visit_count <> (SELECT count(*) FROM moz_historyvisits v " +
michael@0 666 "WHERE v.place_id = h.id AND visit_type NOT IN (0,4,7,8)) " +
michael@0 667 "OR last_visit_date <> (SELECT MAX(visit_date) FROM moz_historyvisits v " +
michael@0 668 "WHERE v.place_id = h.id) " +
michael@0 669 ")");
michael@0 670 cleanupStatements.push(fixVisitStats);
michael@0 671
michael@0 672 // L.3 recalculate hidden for redirects.
michael@0 673 let fixRedirectsHidden = DBConn.createAsyncStatement(
michael@0 674 "UPDATE moz_places " +
michael@0 675 "SET hidden = 1 " +
michael@0 676 "WHERE id IN ( " +
michael@0 677 "SELECT h.id FROM moz_places h " +
michael@0 678 "JOIN moz_historyvisits src ON src.place_id = h.id " +
michael@0 679 "JOIN moz_historyvisits dst ON dst.from_visit = src.id AND dst.visit_type IN (5,6) " +
michael@0 680 "LEFT JOIN moz_bookmarks on fk = h.id AND fk ISNULL " +
michael@0 681 "GROUP BY src.place_id HAVING count(*) = visit_count " +
michael@0 682 ")");
michael@0 683 cleanupStatements.push(fixRedirectsHidden);
michael@0 684
michael@0 685 // MAINTENANCE STATEMENTS SHOULD GO ABOVE THIS POINT!
michael@0 686
michael@0 687 return cleanupStatements;
michael@0 688 },
michael@0 689
michael@0 690 /**
michael@0 691 * Tries to vacuum the database.
michael@0 692 *
michael@0 693 * @param [optional] aTasks
michael@0 694 * Tasks object to execute.
michael@0 695 */
michael@0 696 vacuum: function PDBU_vacuum(aTasks)
michael@0 697 {
michael@0 698 let tasks = new Tasks(aTasks);
michael@0 699 tasks.log("> Vacuum");
michael@0 700
michael@0 701 let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
michael@0 702 DBFile.append("places.sqlite");
michael@0 703 tasks.log("Initial database size is " +
michael@0 704 parseInt(DBFile.fileSize / 1024) + " KiB");
michael@0 705
michael@0 706 let stmt = DBConn.createAsyncStatement("VACUUM");
michael@0 707 stmt.executeAsync({
michael@0 708 handleError: PlacesDBUtils._handleError,
michael@0 709 handleResult: function () {},
michael@0 710
michael@0 711 handleCompletion: function (aReason)
michael@0 712 {
michael@0 713 if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
michael@0 714 tasks.log("+ The database has been vacuumed");
michael@0 715 let vacuumedDBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
michael@0 716 vacuumedDBFile.append("places.sqlite");
michael@0 717 tasks.log("Final database size is " +
michael@0 718 parseInt(vacuumedDBFile.fileSize / 1024) + " KiB");
michael@0 719 }
michael@0 720 else {
michael@0 721 tasks.log("- Unable to vacuum database");
michael@0 722 tasks.clear();
michael@0 723 }
michael@0 724
michael@0 725 PlacesDBUtils._executeTasks(tasks);
michael@0 726 }
michael@0 727 });
michael@0 728 stmt.finalize();
michael@0 729 },
michael@0 730
michael@0 731 /**
michael@0 732 * Forces a full expiration on the database.
michael@0 733 *
michael@0 734 * @param [optional] aTasks
michael@0 735 * Tasks object to execute.
michael@0 736 */
michael@0 737 expire: function PDBU_expire(aTasks)
michael@0 738 {
michael@0 739 let tasks = new Tasks(aTasks);
michael@0 740 tasks.log("> Orphans expiration");
michael@0 741
michael@0 742 let expiration = Cc["@mozilla.org/places/expiration;1"].
michael@0 743 getService(Ci.nsIObserver);
michael@0 744
michael@0 745 Services.obs.addObserver(function (aSubject, aTopic, aData) {
michael@0 746 Services.obs.removeObserver(arguments.callee, aTopic);
michael@0 747 tasks.log("+ Database cleaned up");
michael@0 748 PlacesDBUtils._executeTasks(tasks);
michael@0 749 }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
michael@0 750
michael@0 751 // Force an orphans expiration step.
michael@0 752 expiration.observe(null, "places-debug-start-expiration", 0);
michael@0 753 },
michael@0 754
michael@0 755 /**
michael@0 756 * Collects statistical data on the database.
michael@0 757 *
michael@0 758 * @param [optional] aTasks
michael@0 759 * Tasks object to execute.
michael@0 760 */
michael@0 761 stats: function PDBU_stats(aTasks)
michael@0 762 {
michael@0 763 let tasks = new Tasks(aTasks);
michael@0 764 tasks.log("> Statistics");
michael@0 765
michael@0 766 let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
michael@0 767 DBFile.append("places.sqlite");
michael@0 768 tasks.log("Database size is " + parseInt(DBFile.fileSize / 1024) + " KiB");
michael@0 769
michael@0 770 [ "user_version"
michael@0 771 , "page_size"
michael@0 772 , "cache_size"
michael@0 773 , "journal_mode"
michael@0 774 , "synchronous"
michael@0 775 ].forEach(function (aPragma) {
michael@0 776 let stmt = DBConn.createStatement("PRAGMA " + aPragma);
michael@0 777 stmt.executeStep();
michael@0 778 tasks.log(aPragma + " is " + stmt.getString(0));
michael@0 779 stmt.finalize();
michael@0 780 });
michael@0 781
michael@0 782 // Get maximum number of unique URIs.
michael@0 783 try {
michael@0 784 let limitURIs = Services.prefs.getIntPref(
michael@0 785 "places.history.expiration.transient_current_max_pages");
michael@0 786 tasks.log("History can store a maximum of " + limitURIs + " unique pages");
michael@0 787 } catch(ex) {}
michael@0 788
michael@0 789 let stmt = DBConn.createStatement(
michael@0 790 "SELECT name FROM sqlite_master WHERE type = :type");
michael@0 791 stmt.params.type = "table";
michael@0 792 while (stmt.executeStep()) {
michael@0 793 let tableName = stmt.getString(0);
michael@0 794 let countStmt = DBConn.createStatement(
michael@0 795 "SELECT count(*) FROM " + tableName);
michael@0 796 countStmt.executeStep();
michael@0 797 tasks.log("Table " + tableName + " has " + countStmt.getInt32(0) + " records");
michael@0 798 countStmt.finalize();
michael@0 799 }
michael@0 800 stmt.reset();
michael@0 801
michael@0 802 stmt.params.type = "index";
michael@0 803 while (stmt.executeStep()) {
michael@0 804 tasks.log("Index " + stmt.getString(0));
michael@0 805 }
michael@0 806 stmt.reset();
michael@0 807
michael@0 808 stmt.params.type = "trigger";
michael@0 809 while (stmt.executeStep()) {
michael@0 810 tasks.log("Trigger " + stmt.getString(0));
michael@0 811 }
michael@0 812 stmt.finalize();
michael@0 813
michael@0 814 PlacesDBUtils._executeTasks(tasks);
michael@0 815 },
michael@0 816
michael@0 817 /**
michael@0 818 * Collects telemetry data.
michael@0 819 *
michael@0 820 * There are essentially two modes of collection and the mode is
michael@0 821 * determined by the presence of aHealthReportCallback. If
michael@0 822 * aHealthReportCallback is not defined (the default) then we are in
michael@0 823 * "Telemetry" mode. Results will be reported to Telemetry. If we are
michael@0 824 * in "Health Report" mode only the probes with a true healthreport
michael@0 825 * flag will be collected and the results will be reported to the
michael@0 826 * aHealthReportCallback.
michael@0 827 *
michael@0 828 * @param [optional] aTasks
michael@0 829 * Tasks object to execute.
michael@0 830 * @param [optional] aHealthReportCallback
michael@0 831 * Function to receive data relevant for Firefox Health Report.
michael@0 832 */
michael@0 833 telemetry: function PDBU_telemetry(aTasks, aHealthReportCallback=null)
michael@0 834 {
michael@0 835 let tasks = new Tasks(aTasks);
michael@0 836
michael@0 837 let isTelemetry = !aHealthReportCallback;
michael@0 838
michael@0 839 // This will be populated with one integer property for each probe result,
michael@0 840 // using the histogram name as key.
michael@0 841 let probeValues = {};
michael@0 842
michael@0 843 // The following array contains an ordered list of entries that are
michael@0 844 // processed to collect telemetry data. Each entry has these properties:
michael@0 845 //
michael@0 846 // histogram: Name of the telemetry histogram to update.
michael@0 847 // query: This is optional. If present, contains a database command
michael@0 848 // that will be executed asynchronously, and whose result will
michael@0 849 // be added to the telemetry histogram.
michael@0 850 // callback: This is optional. If present, contains a function that must
michael@0 851 // return the value that will be added to the telemetry
michael@0 852 // histogram. If a query is also present, its result is passed
michael@0 853 // as the first argument of the function. If the function
michael@0 854 // raises an exception, no data is added to the histogram.
michael@0 855 // healthreport: Boolean indicating whether this probe is relevant
michael@0 856 // to Firefox Health Report.
michael@0 857 //
michael@0 858 // Since all queries are executed in order by the database backend, the
michael@0 859 // callbacks can also use the result of previous queries stored in the
michael@0 860 // probeValues object.
michael@0 861 let probes = [
michael@0 862 { histogram: "PLACES_PAGES_COUNT",
michael@0 863 healthreport: true,
michael@0 864 query: "SELECT count(*) FROM moz_places" },
michael@0 865
michael@0 866 { histogram: "PLACES_BOOKMARKS_COUNT",
michael@0 867 healthreport: true,
michael@0 868 query: "SELECT count(*) FROM moz_bookmarks b "
michael@0 869 + "JOIN moz_bookmarks t ON t.id = b.parent "
michael@0 870 + "AND t.parent <> :tags_folder "
michael@0 871 + "WHERE b.type = :type_bookmark " },
michael@0 872
michael@0 873 { histogram: "PLACES_TAGS_COUNT",
michael@0 874 query: "SELECT count(*) FROM moz_bookmarks "
michael@0 875 + "WHERE parent = :tags_folder " },
michael@0 876
michael@0 877 { histogram: "PLACES_FOLDERS_COUNT",
michael@0 878 query: "SELECT count(*) FROM moz_bookmarks "
michael@0 879 + "WHERE TYPE = :type_folder "
michael@0 880 + "AND parent NOT IN (0, :places_root, :tags_folder) " },
michael@0 881
michael@0 882 { histogram: "PLACES_KEYWORDS_COUNT",
michael@0 883 query: "SELECT count(*) FROM moz_keywords " },
michael@0 884
michael@0 885 { histogram: "PLACES_SORTED_BOOKMARKS_PERC",
michael@0 886 query: "SELECT IFNULL(ROUND(( "
michael@0 887 + "SELECT count(*) FROM moz_bookmarks b "
michael@0 888 + "JOIN moz_bookmarks t ON t.id = b.parent "
michael@0 889 + "AND t.parent <> :tags_folder AND t.parent > :places_root "
michael@0 890 + "WHERE b.type = :type_bookmark "
michael@0 891 + ") * 100 / ( "
michael@0 892 + "SELECT count(*) FROM moz_bookmarks b "
michael@0 893 + "JOIN moz_bookmarks t ON t.id = b.parent "
michael@0 894 + "AND t.parent <> :tags_folder "
michael@0 895 + "WHERE b.type = :type_bookmark "
michael@0 896 + ")), 0) " },
michael@0 897
michael@0 898 { histogram: "PLACES_TAGGED_BOOKMARKS_PERC",
michael@0 899 query: "SELECT IFNULL(ROUND(( "
michael@0 900 + "SELECT count(*) FROM moz_bookmarks b "
michael@0 901 + "JOIN moz_bookmarks t ON t.id = b.parent "
michael@0 902 + "AND t.parent = :tags_folder "
michael@0 903 + ") * 100 / ( "
michael@0 904 + "SELECT count(*) FROM moz_bookmarks b "
michael@0 905 + "JOIN moz_bookmarks t ON t.id = b.parent "
michael@0 906 + "AND t.parent <> :tags_folder "
michael@0 907 + "WHERE b.type = :type_bookmark "
michael@0 908 + ")), 0) " },
michael@0 909
michael@0 910 { histogram: "PLACES_DATABASE_FILESIZE_MB",
michael@0 911 callback: function () {
michael@0 912 let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
michael@0 913 DBFile.append("places.sqlite");
michael@0 914 return parseInt(DBFile.fileSize / BYTES_PER_MEBIBYTE);
michael@0 915 }
michael@0 916 },
michael@0 917
michael@0 918 { histogram: "PLACES_DATABASE_JOURNALSIZE_MB",
michael@0 919 callback: function () {
michael@0 920 let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
michael@0 921 DBFile.append("places.sqlite-wal");
michael@0 922 return parseInt(DBFile.fileSize / BYTES_PER_MEBIBYTE);
michael@0 923 }
michael@0 924 },
michael@0 925
michael@0 926 { histogram: "PLACES_DATABASE_PAGESIZE_B",
michael@0 927 query: "PRAGMA page_size /* PlacesDBUtils.jsm PAGESIZE_B */" },
michael@0 928
michael@0 929 { histogram: "PLACES_DATABASE_SIZE_PER_PAGE_B",
michael@0 930 query: "PRAGMA page_count",
michael@0 931 callback: function (aDbPageCount) {
michael@0 932 // Note that the database file size would not be meaningful for this
michael@0 933 // calculation, because the file grows in fixed-size chunks.
michael@0 934 let dbPageSize = probeValues.PLACES_DATABASE_PAGESIZE_B;
michael@0 935 let placesPageCount = probeValues.PLACES_PAGES_COUNT;
michael@0 936 return Math.round((dbPageSize * aDbPageCount) / placesPageCount);
michael@0 937 }
michael@0 938 },
michael@0 939
michael@0 940 { histogram: "PLACES_ANNOS_BOOKMARKS_COUNT",
michael@0 941 query: "SELECT count(*) FROM moz_items_annos" },
michael@0 942
michael@0 943 // LENGTH is not a perfect measure, since it returns the number of bytes
michael@0 944 // only for BLOBs, the number of chars for anything else. Though it's
michael@0 945 // the best approximation we have.
michael@0 946 { histogram: "PLACES_ANNOS_BOOKMARKS_SIZE_KB",
michael@0 947 query: "SELECT SUM(LENGTH(content))/1024 FROM moz_items_annos" },
michael@0 948
michael@0 949 { histogram: "PLACES_ANNOS_PAGES_COUNT",
michael@0 950 query: "SELECT count(*) FROM moz_annos" },
michael@0 951
michael@0 952 { histogram: "PLACES_ANNOS_PAGES_SIZE_KB",
michael@0 953 query: "SELECT SUM(LENGTH(content))/1024 FROM moz_annos" },
michael@0 954 ];
michael@0 955
michael@0 956 let params = {
michael@0 957 tags_folder: PlacesUtils.tagsFolderId,
michael@0 958 type_folder: PlacesUtils.bookmarks.TYPE_FOLDER,
michael@0 959 type_bookmark: PlacesUtils.bookmarks.TYPE_BOOKMARK,
michael@0 960 places_root: PlacesUtils.placesRootId
michael@0 961 };
michael@0 962
michael@0 963 let outstandingProbes = 0;
michael@0 964
michael@0 965 function reportResult(aProbe, aValue) {
michael@0 966 outstandingProbes--;
michael@0 967
michael@0 968 let value = aValue;
michael@0 969 try {
michael@0 970 if ("callback" in aProbe) {
michael@0 971 value = aProbe.callback(value);
michael@0 972 }
michael@0 973 probeValues[aProbe.histogram] = value;
michael@0 974 Services.telemetry.getHistogramById(aProbe.histogram).add(value);
michael@0 975 } catch (ex) {
michael@0 976 Components.utils.reportError("Error adding value " + value +
michael@0 977 " to histogram " + aProbe.histogram +
michael@0 978 ": " + ex);
michael@0 979 }
michael@0 980
michael@0 981 if (!outstandingProbes && aHealthReportCallback) {
michael@0 982 try {
michael@0 983 aHealthReportCallback(probeValues);
michael@0 984 } catch (ex) {
michael@0 985 Components.utils.reportError(ex);
michael@0 986 }
michael@0 987 }
michael@0 988 }
michael@0 989
michael@0 990 for (let i = 0; i < probes.length; i++) {
michael@0 991 let probe = probes[i];
michael@0 992
michael@0 993 if (!isTelemetry && !probe.healthreport) {
michael@0 994 continue;
michael@0 995 }
michael@0 996
michael@0 997 outstandingProbes++;
michael@0 998
michael@0 999 if (!("query" in probe)) {
michael@0 1000 reportResult(probe);
michael@0 1001 continue;
michael@0 1002 }
michael@0 1003
michael@0 1004 let stmt = DBConn.createAsyncStatement(probe.query);
michael@0 1005 for (param in params) {
michael@0 1006 if (probe.query.indexOf(":" + param) > 0) {
michael@0 1007 stmt.params[param] = params[param];
michael@0 1008 }
michael@0 1009 }
michael@0 1010
michael@0 1011 try {
michael@0 1012 stmt.executeAsync({
michael@0 1013 handleError: PlacesDBUtils._handleError,
michael@0 1014 handleResult: function (aResultSet) {
michael@0 1015 let row = aResultSet.getNextRow();
michael@0 1016 reportResult(probe, row.getResultByIndex(0));
michael@0 1017 },
michael@0 1018 handleCompletion: function () {}
michael@0 1019 });
michael@0 1020 } finally{
michael@0 1021 stmt.finalize();
michael@0 1022 }
michael@0 1023 }
michael@0 1024
michael@0 1025 PlacesDBUtils._executeTasks(tasks);
michael@0 1026 },
michael@0 1027
michael@0 1028 /**
michael@0 1029 * Runs a list of tasks, notifying log messages to the callback.
michael@0 1030 *
michael@0 1031 * @param aTasks
michael@0 1032 * Array of tasks to be executed, in form of pointers to methods in
michael@0 1033 * this module.
michael@0 1034 * @param [optional] aCallback
michael@0 1035 * Callback to be invoked when done. It will receive an array of
michael@0 1036 * log messages.
michael@0 1037 */
michael@0 1038 runTasks: function PDBU_runTasks(aTasks, aCallback) {
michael@0 1039 let tasks = new Tasks(aTasks);
michael@0 1040 tasks.callback = aCallback;
michael@0 1041 PlacesDBUtils._executeTasks(tasks);
michael@0 1042 }
michael@0 1043 };
michael@0 1044
michael@0 1045 /**
michael@0 1046 * LIFO tasks stack.
michael@0 1047 *
michael@0 1048 * @param [optional] aTasks
michael@0 1049 * Array of tasks or another Tasks object to clone.
michael@0 1050 */
michael@0 1051 function Tasks(aTasks)
michael@0 1052 {
michael@0 1053 if (aTasks) {
michael@0 1054 if (Array.isArray(aTasks)) {
michael@0 1055 this._list = aTasks.slice(0, aTasks.length);
michael@0 1056 }
michael@0 1057 // This supports passing in a Tasks-like object, with a "list" property,
michael@0 1058 // for compatibility reasons.
michael@0 1059 else if (typeof(aTasks) == "object" &&
michael@0 1060 (Tasks instanceof Tasks || "list" in aTasks)) {
michael@0 1061 this._list = aTasks.list;
michael@0 1062 this._log = aTasks.messages;
michael@0 1063 this.callback = aTasks.callback;
michael@0 1064 this.scope = aTasks.scope;
michael@0 1065 this._telemetryStart = aTasks._telemetryStart;
michael@0 1066 }
michael@0 1067 }
michael@0 1068 }
michael@0 1069
michael@0 1070 Tasks.prototype = {
michael@0 1071 _list: [],
michael@0 1072 _log: [],
michael@0 1073 callback: null,
michael@0 1074 scope: null,
michael@0 1075 _telemetryStart: 0,
michael@0 1076
michael@0 1077 /**
michael@0 1078 * Adds a task to the top of the list.
michael@0 1079 *
michael@0 1080 * @param aNewElt
michael@0 1081 * Task to be added.
michael@0 1082 */
michael@0 1083 push: function T_push(aNewElt)
michael@0 1084 {
michael@0 1085 this._list.unshift(aNewElt);
michael@0 1086 },
michael@0 1087
michael@0 1088 /**
michael@0 1089 * Returns and consumes next task.
michael@0 1090 *
michael@0 1091 * @return next task or undefined if no task is left.
michael@0 1092 */
michael@0 1093 pop: function T_pop() this._list.shift(),
michael@0 1094
michael@0 1095 /**
michael@0 1096 * Removes all tasks.
michael@0 1097 */
michael@0 1098 clear: function T_clear()
michael@0 1099 {
michael@0 1100 this._list.length = 0;
michael@0 1101 },
michael@0 1102
michael@0 1103 /**
michael@0 1104 * Returns array of tasks ordered from the next to be run to the latest.
michael@0 1105 */
michael@0 1106 get list() this._list.slice(0, this._list.length),
michael@0 1107
michael@0 1108 /**
michael@0 1109 * Adds a message to the log.
michael@0 1110 *
michael@0 1111 * @param aMsg
michael@0 1112 * String message to be added.
michael@0 1113 */
michael@0 1114 log: function T_log(aMsg)
michael@0 1115 {
michael@0 1116 this._log.push(aMsg);
michael@0 1117 },
michael@0 1118
michael@0 1119 /**
michael@0 1120 * Returns array of log messages ordered from oldest to newest.
michael@0 1121 */
michael@0 1122 get messages() this._log.slice(0, this._log.length),
michael@0 1123 }

mercurial