Bug 516932 - Add an advanced maintenance task to vacuum and check integrity, for support purposes r=dietrich
authorMarco Bonardo <mbonardo@mozilla.com>
Mon, 05 Oct 2009 12:12:55 +0200
changeset 33460 77e5bfb0ef739effa5fc9d215677cd6309f32f8e
parent 33459 c2906e26f75af2ed5c218fe81d679f87fb22add2
child 33461 4eaf2335e499f52270fbe84b88fd8f66157b457f
push idunknown
push userunknown
push dateunknown
reviewersdietrich
bugs516932
milestone1.9.3a1pre
Bug 516932 - Add an advanced maintenance task to vacuum and check integrity, for support purposes r=dietrich Adds a PlacesDBUtils.checkAndFixDatabase() that can be used by support team to help debugging Places database issues.
toolkit/components/places/src/PlacesDBUtils.jsm
toolkit/components/places/tests/unit/test_preventive_maintenance_checkAndFixDatabase.js
--- a/toolkit/components/places/src/PlacesDBUtils.jsm
+++ b/toolkit/components/places/src/PlacesDBUtils.jsm
@@ -118,17 +118,16 @@ nsPlacesDBUtils.prototype = {
   _idleLookupTimer: null,
   _statementsRunningCount: 0,
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsISupports
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsITimerCallback,
-    Ci.nsIObserver,
   ]),
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsITimerCallback
 
   notify: function PDBU_notify(aTimer) {
     switch (aTimer) {
       case this._idleLookUpTimer:
@@ -565,14 +564,124 @@ nsPlacesDBUtils.prototype = {
     // Used to keep track of last call to handleCompletion
     this._statementsRunningCount = cleanupStatements.length;
     // Statements are automatically queued-up by mozStorage
     cleanupStatements.forEach(function (aStatement) {
         aStatement.executeAsync(this);
         aStatement.finalize();
       }, this);
   },
+
+  /**
+   * This method is only for support purposes, it will run sync and will take
+   * lot of time on big databases, but can be manually triggered to help
+   * debugging common issues.
+   */
+  checkAndFixDatabase: function PDBU_checkAndFixDatabase() {
+    let log = [];
+    let self = this;
+    let sep = "- - -";
+
+    function integrity() {
+      let integrityCheckStmt =
+        self._dbConn.createStatement("PRAGMA integrity_check");
+      log.push("INTEGRITY");
+      let logIndex = log.length;
+      while (integrityCheckStmt.executeStep()) {
+        log.push(integrityCheckStmt.getString(0));
+      }
+      integrityCheckStmt.finalize();
+      log.push(sep);
+      return log[logIndex] == "ok";
+    }
+
+    function vacuum() {
+      log.push("VACUUM");
+      let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+                   getService(Ci.nsIProperties);
+      let placesDBFile = dirSvc.get("ProfD", Ci.nsILocalFile);
+      placesDBFile.append("places.sqlite");
+      log.push("places.sqlite: " + placesDBFile.fileSize + " byte");
+      self._dbConn.executeSimpleSQL("VACUUM");
+      log.push(sep);
+    }
+
+    function backup() {
+      log.push("BACKUP");
+      let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+                   getService(Ci.nsIProperties);
+      let profD = dirSvc.get("ProfD", Ci.nsILocalFile);
+      let placesDBFile = profD.clone();
+      placesDBFile.append("places.sqlite");
+      let backupDBFile = profD.clone();
+      backupDBFile.append("places.sqlite.corrupt");
+      backupDBFile.createUnique(backupDBFile.NORMAL_FILE_TYPE, 0666);
+      let backupName = backupDBFile.leafName;
+      backupDBFile.remove(false);
+      placesDBFile.copyTo(profD, backupName);
+      log.push(backupName);
+      log.push(sep);
+    }
+
+    function reindex() {
+      log.push("REINDEX");
+      self._dbConn.executeSimpleSQL("REINDEX");
+      log.push(sep);
+    }
+
+    function cleanup() {
+      log.push("CLEANUP");
+      self.maintenanceOnIdle()
+      log.push(sep);
+    }
+
+    function stats() {
+      log.push("STATS");
+      let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+                   getService(Ci.nsIProperties);
+      let placesDBFile = dirSvc.get("ProfD", Ci.nsILocalFile);
+      placesDBFile.append("places.sqlite");
+      log.push("places.sqlite: " + placesDBFile.fileSize + " byte");
+      let stmt = self._dbConn.createStatement(
+        "SELECT name FROM sqlite_master WHERE type = :DBType");
+      stmt.params["DBType"] = "table";
+      while (stmt.executeStep()) {
+        let tableName = stmt.getString(0);
+        let countStmt = self._dbConn.createStatement(
+        "SELECT count(*) FROM " + tableName);
+        countStmt.executeStep();
+        log.push(tableName + ": " + countStmt.getInt32(0));
+        countStmt.finalize();
+      }
+      stmt.finalize();
+      log.push(sep);
+    }
+
+    // First of all execute an integrity check.
+    let integrityIsGood = integrity();
+
+    // If integrity check did fail, we can try to fix the database through
+    // a reindex.
+    if (!integrityIsGood) {
+      // Backup current database.
+      backup();
+      // Execute a reindex.
+      reindex();
+      // Now check again the integrity.
+      integrityIsGood = integrity();
+    }
+
+    // If integrity is fine, let's force a maintenance, execute a vacuum and
+    // get some stats.
+    if (integrityIsGood) {
+      cleanup();
+      vacuum();
+      stats();
+    }
+
+    return log.join('\n');
+  }
 };
 
 __defineGetter__("PlacesDBUtils", function() {
   delete this.PlacesDBUtils;
   return this.PlacesDBUtils = new nsPlacesDBUtils;
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unit/test_preventive_maintenance_checkAndFixDatabase.js
@@ -0,0 +1,79 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Places unit test code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Marco Bonardo <mak77bonardo.net> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+ /**
+  * Test preventive maintenance checkAndFixDatabase.
+  */
+
+// Include PlacesDBUtils module.
+Components.utils.import("resource://gre/modules/PlacesDBUtils.jsm");
+
+const FINISHED_MAINTENANCE_NOTIFICATION_TOPIC = "places-maintenance-finished";
+
+let os = Cc["@mozilla.org/observer-service;1"].
+         getService(Ci.nsIObserverService);
+let log = [];
+
+let observer = {
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == FINISHED_MAINTENANCE_NOTIFICATION_TOPIC) {
+      // Integrity should be good.
+      do_check_eq(log[1], "ok");
+
+      // Check we are not wrongly executing bad integrity tasks.
+      const goodMsgs = ["INTEGRITY", "VACUUM", "CLEANUP", "STATS"];
+      const badMsgs = ["BACKUP", "REINDEX"];
+      log.forEach(function (aLogMsg) {
+        do_check_eq(badMsgs.indexOf(aLogMsg), -1);
+        let index = goodMsgs.indexOf(aLogMsg);
+        if (index != -1)
+          goodMsgs.splice(index, 1);
+      });
+      // Check we have run all tasks.
+      do_check_eq(goodMsgs.length, 0);
+
+      do_test_finished();
+    }
+  }
+}
+os.addObserver(observer, FINISHED_MAINTENANCE_NOTIFICATION_TOPIC, false);
+
+function run_test() {
+  do_test_pending();
+  log = PlacesDBUtils.checkAndFixDatabase().split("\n");
+}