Bug 725881 - Add a deleted form history table. r=dolske
authorWes Johnston <wjohnston@mozilla.com>
Mon, 27 Feb 2012 10:10:16 -0800
changeset 87850 289cd639ff3418020d6adb18153a1b0b4c9de552
parent 87849 00929ec6099170cb7c22169bf9ea5e7db85630f2
child 87851 2cd9b8fc084d3a04c819454c10c79c7af10b5258
push id22160
push usermbrubeck@mozilla.com
push dateTue, 28 Feb 2012 17:21:33 +0000
treeherdermozilla-central@dde4e0089a18 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske
bugs725881
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 725881 - Add a deleted form history table. r=dolske
toolkit/components/satchel/Makefile.in
toolkit/components/satchel/nsFormHistory.js
toolkit/components/satchel/test/unit/formhistory_v3.sqlite
toolkit/components/satchel/test/unit/formhistory_v3v4.sqlite
toolkit/components/satchel/test/unit/formhistory_v999a.sqlite
toolkit/components/satchel/test/unit/head_satchel.js
toolkit/components/satchel/test/unit/test_db_update_v4.js
toolkit/components/satchel/test/unit/test_db_update_v4b.js
toolkit/components/satchel/test/unit/xpcshell.ini
--- a/toolkit/components/satchel/Makefile.in
+++ b/toolkit/components/satchel/Makefile.in
@@ -62,20 +62,23 @@ LOCAL_INCLUDES = \
   $(NULL)
 
 CPPSRCS = \
   nsFormFillController.cpp \
   $(NULL)
 
 EXTRA_COMPONENTS = \
   nsFormAutoComplete.js \
-  nsFormHistory.js \
   nsInputListAutoComplete.js \
   satchel.manifest \
   $(NULL)
 
+EXTRA_PP_COMPONENTS = \
+	nsFormHistory.js \
+	$(NULL)
+
 EXTRA_JS_MODULES = \
   nsFormAutoCompleteResult.jsm \
   $(NULL)
 
 TEST_DIRS += test
 
 include $(topsrcdir)/config/rules.mk
--- a/toolkit/components/satchel/nsFormHistory.js
+++ b/toolkit/components/satchel/nsFormHistory.js
@@ -38,17 +38,17 @@
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
-const DB_VERSION = 3;
+const DB_VERSION = 4;
 const DAY_IN_MS  = 86400000; // 1 day in milliseconds
 
 function FormHistory() {
     this.init();
 }
 
 FormHistory.prototype = {
     classID          : Components.ID("{0c1bb408-71a2-403f-854a-3a0659829ded}"),
@@ -69,16 +69,21 @@ FormHistory.prototype = {
                 "id"        : "INTEGER PRIMARY KEY",
                 "fieldname" : "TEXT NOT NULL",
                 "value"     : "TEXT NOT NULL",
                 "timesUsed" : "INTEGER",
                 "firstUsed" : "INTEGER",
                 "lastUsed"  : "INTEGER",
                 "guid"      : "TEXT"
             },
+            moz_deleted_formhistory: {
+                "id"          : "INTEGER PRIMARY KEY",
+                "timeDeleted" : "INTEGER",
+                "guid"        : "TEXT"
+            }
         },
         indices : {
             moz_formhistory_index : {
                 table   : "moz_formhistory",
                 columns : ["fieldname"]
             },
             moz_formhistory_lastused_index : {
                 table   : "moz_formhistory",
@@ -240,74 +245,101 @@ FormHistory.prototype = {
         let [id, guid] = this.getExistingEntryID(name, value);
         this.sendStringNotification("before-removeEntry", name, value, guid);
 
         let stmt;
         let query = "DELETE FROM moz_formhistory WHERE id = :id";
         let params = { id : id };
 
         try {
+            this.dbConnection.beginTransaction();
+            this.moveToDeletedTable("VALUES (:guid, :timeDeleted)", {
+              guid: guid,
+              timeDeleted: Date.now()
+            });
+
+            // remove from the formhistory database
             stmt = this.dbCreateStatement(query, params);
             stmt.execute();
             this.sendStringNotification("removeEntry", name, value, guid);
         } catch (e) {
+            this.dbConnection.rollbackTransaction();
             this.log("removeEntry failed: " + e);
             throw e;
         } finally {
             if (stmt) {
                 stmt.reset();
             }
         }
+        this.dbConnection.commitTransaction();
     },
 
 
     removeEntriesForName : function (name) {
         this.log("removeEntriesForName with name=" + name);
 
         this.sendStringNotification("before-removeEntriesForName", name);
 
         let stmt;
         let query = "DELETE FROM moz_formhistory WHERE fieldname = :fieldname";
         let params = { fieldname : name };
 
         try {
+            this.dbConnection.beginTransaction();
+            this.moveToDeletedTable(
+              "SELECT guid, :timeDeleted FROM moz_formhistory " +
+              "WHERE fieldname = :fieldname", {
+                fieldname: name,
+                timeDeleted: Date.now()
+            });
+
             stmt = this.dbCreateStatement(query, params);
             stmt.execute();
             this.sendStringNotification("removeEntriesForName", name);
         } catch (e) {
+            this.dbConnection.rollbackTransaction();
             this.log("removeEntriesForName failed: " + e);
             throw e;
         } finally {
             if (stmt) {
                 stmt.reset();
             }
         }
+        this.dbConnection.commitTransaction();
     },
 
 
     removeAllEntries : function () {
         this.log("removeAllEntries");
 
         this.sendNotification("before-removeAllEntries", null);
 
         let stmt;
         let query = "DELETE FROM moz_formhistory";
 
         try {
+            this.dbConnection.beginTransaction();
+            this.moveToDeletedTable(
+              "SELECT guid, :timeDeleted FROM moz_formhistory", {
+              timeDeleted: Date.now()
+            });
+
             stmt = this.dbCreateStatement(query);
             stmt.execute();
             this.sendNotification("removeAllEntries", null);
         } catch (e) {
+            this.dbConnection.rollbackTransaction();
             this.log("removeEntriesForName failed: " + e);
             throw e;
         } finally {
             if (stmt) {
                 stmt.reset();
             }
         }
+        this.dbConnection.commitTransaction();
     },
 
 
     nameExists : function (name) {
         this.log("nameExists for name=" + name);
         let stmt;
         let query = "SELECT COUNT(1) AS numEntries FROM moz_formhistory WHERE fieldname = :fieldname";
         let params = { fieldname : name };
@@ -339,28 +371,59 @@ FormHistory.prototype = {
 
         let stmt;
         let query = "DELETE FROM moz_formhistory WHERE firstUsed >= :beginTime AND firstUsed <= :endTime";
         let params = {
                         beginTime : beginTime,
                         endTime   : endTime
                      };
         try {
+            this.dbConnection.beginTransaction();
+            this.moveToDeletedTable(
+                  "SELECT guid, :timeDeleted FROM moz_formhistory " +
+                  "WHERE firstUsed >= :beginTime AND firstUsed <= :endTime", {
+              beginTime: beginTime,
+              endTime: endTime
+            });
+
             stmt = this.dbCreateStatement(query, params);
             stmt.executeStep();
             this.sendIntNotification("removeEntriesByTimeframe", beginTime, endTime);
         } catch (e) {
             this.log("removeEntriesByTimeframe failed: " + e);
             throw e;
         } finally {
             if (stmt) {
                 stmt.reset();
             }
         }
+        this.dbConnection.commitTransaction();
+    },
 
+    moveToDeletedTable : function (values, params) {
+#ifdef ANDROID
+        this.log("move entries to deleted");
+
+        let stmt;
+
+        try {
+            // move the entry to the deleted items table
+            let query = "INSERT INTO moz_deleted_formhistory (guid, timeDeleted) ";
+            if (values) query += values;
+            stmt = this.dbCreateStatement(query, params);
+            stmt.execute();
+        } catch (e) {
+            this.log("move entry failed: " + e);
+            throw e;
+        } finally {
+            if (stmt) {
+                stmt.reset();
+            }
+        }
+#endif
     },
 
     get dbConnection() {
         // Make sure dbConnection can't be called from now to prevent infinite loops.
         delete FormHistory.prototype.dbConnection;
 
         try {
             this.dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
@@ -642,31 +705,35 @@ FormHistory.prototype = {
             this.dbMigrate(version);
     },
 
 
     dbCreate: function () {
         this.log("Creating DB -- tables");
         for (let name in this.dbSchema.tables) {
             let table = this.dbSchema.tables[name];
-            let tSQL = [[col, table[col]].join(" ") for (col in table)].join(", ");
-            this.dbConnection.createTable(name, tSQL);
+            this.dbCreateTable(name, table);
         }
 
         this.log("Creating DB -- indices");
         for (let name in this.dbSchema.indices) {
             let index = this.dbSchema.indices[name];
             let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
                             "(" + index.columns.join(", ") + ")";
             this.dbConnection.executeSimpleSQL(statement);
         }
 
         this.dbConnection.schemaVersion = DB_VERSION;
     },
 
+    dbCreateTable: function(name, table) {
+        let tSQL = [[col, table[col]].join(" ") for (col in table)].join(", ");
+        this.log("Creating table " + name + " with " + tSQL);
+        this.dbConnection.createTable(name, tSQL);
+    },
 
     dbMigrate : function (oldVersion) {
         this.log("Attempting to migrate from version " + oldVersion);
 
         if (oldVersion > DB_VERSION) {
             this.log("Downgrading to version " + DB_VERSION);
             // User's DB is newer. Sanity check that our expected columns are
             // present, and if so mark the lower version and merrily continue
@@ -815,16 +882,21 @@ FormHistory.prototype = {
             } finally {
                 if (stmt) {
                     stmt.reset();
                 }
             }
         }
     },
 
+    dbMigrateToVersion4 : function () {
+        if (!this.dbConnection.tableExists("moz_deleted_formhistory")) {
+            this.dbCreateTable("moz_deleted_formhistory", this.dbSchema.tables.moz_deleted_formhistory);
+        }
+    },
 
     /*
      * dbAreExpectedColumnsPresent
      *
      * Sanity check to ensure that the columns this version of the code expects
      * are present in the DB we're using.
      */
     dbAreExpectedColumnsPresent : function () {
new file mode 100644
index 0000000000000000000000000000000000000000..e0e8fe2468da9844ef8c1e853271d0408651800f
GIT binary patch
literal 5120
zc$^Ck^vNtqRY=P(%1ta$FlJz3U}R))P*7lCVBiE|Rt5xM0b)iZK8VfCfFE!&=suF?
z1!-p7$-u0`w1deA$QT8bgK`#jaYt>&M*hsal+=pc{Hl0R9A;z|m*f{!#;2ENro>~D
zVRa62bqsM;@bq(WjZna?O~Ky}T~<K@B(2%V%*-zC3A9-rtIauy#U-W1sn~62!D~CJ
zR;(@nDG3EC(JW$O7dOZ0eQbivc<sh*Wm;xxPD);4ZmMQuAS1iDwl-r^WJzLDPAZy8
zG%gd=x)4VvA6HCMGgE*L32}9I4N?dQ^7M5Kid67+jnq+q+ollW8WE!4=MTi8K0Z1M
zWr;bZ7=k64xv9mV@PHWxGN1?&L<k{Rpdmy+5eYU+6O=cZm=`cGp8yi0U=)mkK?T&9
zI2a_Q8QItvK!t*?6Sx@Abz)>-X}-G6X=fyiHe-aU(*>(@gs5|50m;wY_-zM>p2a+w
zH6=YIF(|F7I3&;CKgzt^n~MRI|6egMe+3ewU=)mkVG6jIB^e=Q2os#?$Oy^*&ls3L
z1Bp>E3P!;&1U$@a@D>9Tj00*8AoBk+=GO!5%26;1Mgc7V3o`>FlL)geBYN|n5dc|h
B5=sC7
new file mode 100644
index 0000000000000000000000000000000000000000..8eab177e97f759e9749c58011afed48d111c85f8
GIT binary patch
literal 6144
zc%1E+zi!h&7{Kp*j*S}r2norm82ZTrs+DAbN_6OgLo8%9IMldO!x9<UX)W1_6gw2U
z86JTNCSHLBwQs-+@B$1SU_nAK<BMx2P3<tDqUe6I&%W=zKj-szr?a2#w7tlM{%{o7
z5o{nHVT>LCAcU&PDWEBp<is;|dQZ?|s;KdG?HV=4RYdCCF?SE2;ObwjE{C{Q5v8py
z90!s8+;@ZFQO|LGH*%dG9i9U(jE19EC1T;Bsi~F*mfCD<AeU&#urBK@8Fa(a9%&{#
zG4;o)*@bPb3t)6CFm~H*sDr#e_8b{3ZO=+7kr%kF)LCw-9P*;1)^L2o8#wMu8gC{}
zkJ|T&S}JG#)Qy(52jynaG3Hxgoi-~mA)=%wk=F_%`*s+OL$^3OzcBiF)50E5mEA-|
z8F8YtS^D@zLb5P+dC0!!`p&=(TqWMYqExHl*WElp<`QG3%AHGy3tl~^C<*G2N1MF-
zJSXb#!uH1(gfufkIv$xb>R^-((NvTT+EhYc<iwXk=O#x!Ao4jW7!1aL#U1VnTD^@0
z0ntUF(VQ#;jV4C?n=hZ5C*ACNAD`(pCVFbBr}DJ@-N)a@^!lE>tvLIRZT62sYtZRD
z+dSM}L3I9qL*!RdFc^#_WreKbbPeJ1TNS7C{}hp*Nx@(+mXNDN$af4}R-xO0>HI$>
t-!HqD3<l$0z!QYITcm;KxBr-a|6}q4B?p7SSaPmo9^Dk~Ze;%j{s5eCQKbL?
index f1462cb243c60230b8fafff2b9ca848886d2a799..14279f05ff4e078e38f75e6b35e223bfc5976806
GIT binary patch
literal 6144
zc%1E5y>HV%6u%EUfl4h)QB+lkQzWEDNz|ktNl+IY*R)Ynhd3^6$`HB4X$^6sCQiS$
zFu)&BiGcwjbzx#-goTNP0Wp9CLcmZ7W;P@^*We^hQrKEl|KzjZy}RG@@1A^Ty<9e}
z87e7Ns=8v3alilw!4*OP00XpnKoe(aLoIy>06qXmV*rFdU+l*<z(NAxEA$gR#P=Zi
z3+n`-5oEo-sVUgd4P~XQ>XqF>Q7x;6S}d43ThnSrrMlC@?cpVXlLaDk$+STHJ!bjI
zh+ixENlKK389^e~q|`ho<;k3oCq&H1L|jOxNr?DY>*h3BSdwYS&~$a$3T9ISjd|$x
z1%vQT)>i9K+0sRA>Kub)L@Rbv6e1-}Ef?1nT_x#^ICGto_*qU0kxivsceqT6wS}5$
z2Qtakn$hkmD^8EO&ckH}3fN;VXRDd}x~}gOx^<b3)wB%uT1}veyuNr4HqL45MRnVe
zgF-ha{{5bmI4vwWvt}_!M(j+dowfk|ss<fj8MDQBeOyl+W<t7(VUHBkq;*wf4hQ=2
zdBCE5fFI#C^d9Zw$Z_m47cP1t&YS^57of)zHn&fhKRju|{1^nxje!>*o<I2WOzM^8
ziH)e3-Q?>^m0yVCuEb~uaksXvEiRYD++=2cabi)$Z^3X6F)^0L9JXT$R!jkzp*}sm
z%UI9*Uv8`Q{LPe}$mVBO5^`iUhT3>Zi<j(>A{(QrSt+q0jVqbuEH@W}Z9LB6aUHx!
zMp-p(ZEfeux%AZ%SEW)%{w2A6w6XXlz~Av#{1LyQrVAG?{x|gDA!r>*tZnj4)5Nx!
zM7GJneoBS-2cVY=7cNc_5JdFYIH3RkJ%C^1J^T!R!;g<!l?xZA8iP0touv$8Iy=e~
z)9EZ2km+<*1CWM24B;$GSo8mn@DqUF(VGhwE>184hymfabIV7^#wR9Y@x<jwU(>t<
R>C{o@K`<GkGomfiegR?6r>Ot{
--- a/toolkit/components/satchel/test/unit/head_satchel.js
+++ b/toolkit/components/satchel/test/unit/head_satchel.js
@@ -33,17 +33,17 @@
  * 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 ***** */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 
-const CURRENT_SCHEMA = 3;
+const CURRENT_SCHEMA = 4;
 const PR_HOURS = 60 * 60 * 1000000;
 
 do_get_profile();
 
 var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
              getService(Ci.nsIProperties);
 
 function getDBVersion(dbfile) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/satchel/test/unit/test_db_update_v4.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var testnum = 0;
+var fh;
+
+function run_test()
+{
+  try {
+
+  // ===== test init =====
+  var testfile = do_get_file("formhistory_v3.sqlite");
+  var profileDir = dirSvc.get("ProfD", Ci.nsIFile);
+
+  // Cleanup from any previous tests or failures.
+  var destFile = profileDir.clone();
+  destFile.append("formhistory.sqlite");
+  if (destFile.exists())
+    destFile.remove(false);
+
+  testfile.copyTo(profileDir, "formhistory.sqlite");
+  do_check_eq(3, getDBVersion(testfile));
+
+  fh = Cc["@mozilla.org/satchel/form-history;1"].
+       getService(Ci.nsIFormHistory2);
+
+
+  // ===== 1 =====
+  testnum++;
+
+  // Check that the index was added
+  do_check_true(fh.DBConnection.tableExists("moz_deleted_formhistory"));
+  // check for upgraded schema.
+  do_check_eq(CURRENT_SCHEMA, fh.DBConnection.schemaVersion);
+  // check that an entry still exists
+  do_check_true(fh.entryExists("name-A", "value-A"));
+
+  } catch (e) {
+    throw "FAILED in test #" + testnum + " -- " + e;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/satchel/test/unit/test_db_update_v4b.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var testnum = 0;
+var fh;
+
+function run_test()
+{
+  try {
+
+  // ===== test init =====
+  var testfile = do_get_file("formhistory_v3v4.sqlite");
+  var profileDir = dirSvc.get("ProfD", Ci.nsIFile);
+
+  // Cleanup from any previous tests or failures.
+  var destFile = profileDir.clone();
+  destFile.append("formhistory.sqlite");
+  if (destFile.exists())
+    destFile.remove(false);
+
+  testfile.copyTo(profileDir, "formhistory.sqlite");
+  do_check_eq(3, getDBVersion(testfile));
+
+  fh = Cc["@mozilla.org/satchel/form-history;1"].
+       getService(Ci.nsIFormHistory2);
+
+
+  // ===== 1 =====
+  testnum++;
+
+  // Check that the index was added
+  do_check_true(fh.DBConnection.tableExists("moz_deleted_formhistory"));
+  // check for upgraded schema.
+  do_check_eq(CURRENT_SCHEMA, fh.DBConnection.schemaVersion);
+  // check that an entry still exists
+  do_check_true(fh.entryExists("name-A", "value-A"));
+
+  } catch (e) {
+    throw "FAILED in test #" + testnum + " -- " + e;
+  }
+}
--- a/toolkit/components/satchel/test/unit/xpcshell.ini
+++ b/toolkit/components/satchel/test/unit/xpcshell.ini
@@ -8,13 +8,15 @@ tail =
 skip-if = os == "android"
 [test_db_corrupt.js]
 [test_db_update_v1.js]
 [test_db_update_v1b.js]
 [test_db_update_v2.js]
 [test_db_update_v2b.js]
 [test_db_update_v3.js]
 [test_db_update_v3b.js]
+[test_db_update_v4.js]
+[test_db_update_v4b.js]
 [test_db_update_v999a.js]
 [test_db_update_v999b.js]
 [test_expire.js]
 [test_history_api.js]
 [test_notify.js]