Bug 1310295 - Provide a Places database migration routine to remove non-built-in roots. r?mak draft
authorMark Banner <standard8@mozilla.com>
Fri, 02 Mar 2018 11:09:12 +0000
changeset 767467 ccb4e30c799479d3a0efabd63db1ab9085e124ad
parent 767466 4e29f78ae9b3345e86bdc4ec6251b6927aae0039
child 767569 058db4a662dd95aea859292cf08e4f05554f6c0c
push id102609
push userbmo:standard8@mozilla.com
push dateWed, 14 Mar 2018 17:33:35 +0000
reviewersmak
bugs1310295
milestone61.0a1
Bug 1310295 - Provide a Places database migration routine to remove non-built-in roots. r?mak MozReview-Commit-ID: G2vdW3PRlqo
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/migration/places_v43.sqlite
toolkit/components/places/tests/migration/test_current_from_v43.js
toolkit/components/places/tests/migration/xpcshell.ini
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -1223,16 +1223,23 @@ Database::InitSchema(bool* aDatabaseMigr
 
       if (currentSchemaVersion < 43) {
         rv = MigrateV43Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       // Firefox 60 uses schema version 43.
 
+      if (currentSchemaVersion < 44) {
+        rv = MigrateV44Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Firefox 61 uses schema version 44.
+
       // Schema Upgrades must add migration code here.
       // >>> IMPORTANT! <<<
       // NEVER MIX UP SYNC AND ASYNC EXECUTION IN MIGRATORS, YOU MAY LOCK THE
       // CONNECTION AND CAUSE FURTHER STEPS TO FAIL.
       // In case, set a bool and do the async work in the ScopeExit guard just
       // before the migration steps.
     }
   }
@@ -1938,16 +1945,140 @@ Database::MigrateV43Up() {
     "WHERE post_data ISNULL "
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
+Database::MigrateV44Up() {
+  // We need to remove any non-builtin roots and their descendants.
+
+  // Install a temp trigger to clean up linked tables when the main
+  // bookmarks are deleted.
+  nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TEMP TRIGGER moz_migrate_bookmarks_trigger "
+    "AFTER DELETE ON moz_bookmarks FOR EACH ROW "
+    "BEGIN "
+      // Insert tombstones.
+      "INSERT OR IGNORE INTO moz_bookmarks_deleted (guid, dateRemoved) "
+        "VALUES (OLD.guid, strftime('%s', 'now', 'localtime', 'utc') * 1000); "
+      // Remove old annotations for the bookmarks.
+      "DELETE FROM moz_items_annos "
+        "WHERE item_id = OLD.id; "
+      // Decrease the foreign_count in moz_places.
+      "UPDATE moz_places "
+        "SET foreign_count = foreign_count - 1 "
+        "WHERE id = OLD.fk; "
+    "END "
+  ));
+  if (NS_FAILED(rv)) return rv;
+
+  // This trigger listens for moz_places deletes, and updates moz_annos and
+  // moz_keywords accordingly.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TEMP TRIGGER moz_migrate_annos_trigger "
+    "AFTER UPDATE ON moz_places FOR EACH ROW "
+    // Only remove from moz_places if we don't have any remaining keywords pointing to
+    // this place, and it hasn't been visited. Note: orphan keywords are tidied up below.
+    "WHEN NEW.visit_count = 0 AND "
+      " NEW.foreign_count = (SELECT COUNT(*) FROM moz_keywords WHERE place_id = NEW.id) "
+    "BEGIN "
+      // No more references to the place, so we can delete the place itself.
+      "DELETE FROM moz_places "
+        "WHERE id = NEW.id; "
+      // Delete annotations relating to the place.
+      "DELETE FROM moz_annos "
+        "WHERE place_id = NEW.id; "
+      // Delete keywords relating to the place.
+      "DELETE FROM moz_keywords "
+        "WHERE place_id = NEW.id; "
+    "END "
+  ));
+  if (NS_FAILED(rv)) return rv;
+
+  // Listens to moz_keyword deletions, to ensure moz_places gets the
+  // foreign_count updated corrrectly.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TEMP TRIGGER moz_migrate_keyword_trigger "
+    "AFTER DELETE ON moz_keywords FOR EACH ROW "
+    "BEGIN "
+      // If we remove a keyword, then reduce the foreign_count.
+      "UPDATE moz_places "
+        "SET foreign_count = foreign_count - 1 "
+          "WHERE id = OLD.place_id; "
+    "END "
+  ));
+  if (NS_FAILED(rv)) return rv;
+
+  // First of all, find the non-builtin roots.
+  nsCOMPtr<mozIStorageStatement> deleteStmt;
+  rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "WITH RECURSIVE "
+    "itemsToRemove(id, guid) AS ( "
+      "SELECT b.id, b.guid FROM moz_bookmarks b "
+      "JOIN moz_bookmarks p ON b.parent = p.id "
+      "WHERE p.guid = 'root________' AND "
+        "b.guid NOT IN ('menu________', 'toolbar_____', 'tags________', 'unfiled_____', 'mobile______') "
+      "UNION ALL "
+      "SELECT b.id, b.guid FROM moz_bookmarks b "
+      "JOIN itemsToRemove d ON d.id = b.parent "
+    ") "
+    "DELETE FROM moz_bookmarks "
+      "WHERE id IN (SELECT id FROM itemsToRemove) "
+  ), getter_AddRefs(deleteStmt));
+  if (NS_FAILED(rv)) return rv;
+
+  rv = deleteStmt->Execute();
+  if (NS_FAILED(rv)) return rv;
+
+  // Before we remove the triggers, check for keywords attached to places which
+  // no longer have a bookmark to them. We do this before removing the triggers,
+  // so that we can make use of the keyword trigger to update the counts in
+  // moz_places.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DELETE FROM moz_keywords WHERE place_id IN ( "
+      "SELECT h.id FROM moz_keywords k "
+      "JOIN moz_places h ON h.id = k.place_id "
+      "GROUP BY place_id HAVING h.foreign_count = count(*) "
+    ")"
+  ));
+  if (NS_FAILED(rv)) return rv;
+
+  // Now remove the temp triggers.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TRIGGER moz_migrate_bookmarks_trigger "
+  ));
+  if (NS_FAILED(rv)) return rv;
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TRIGGER moz_migrate_annos_trigger "
+  ));
+  if (NS_FAILED(rv)) return rv;
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TRIGGER moz_migrate_keyword_trigger "
+  ));
+  if (NS_FAILED(rv)) return rv;
+
+  // Cleanup any orphan annotation attributes.
+  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DELETE FROM moz_anno_attributes WHERE id IN ( "
+      "SELECT id FROM moz_anno_attributes n "
+      "EXCEPT "
+      "SELECT DISTINCT anno_attribute_id FROM moz_annos "
+      "EXCEPT "
+      "SELECT DISTINCT anno_attribute_id FROM moz_items_annos "
+    ")"
+  ));
+  if (NS_FAILED(rv)) return rv;
+
+  return rv;
+}
+
+nsresult
 Database::GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
                            nsTArray<int64_t>& aItemIds)
 {
   nsCOMPtr<mozIStorageStatement> stmt;
   nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT b.id FROM moz_items_annos a "
     "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
     "JOIN moz_bookmarks b ON b.id = a.item_id "
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -14,17 +14,17 @@
 #include "mozilla/storage/StatementCache.h"
 #include "mozilla/Attributes.h"
 #include "nsIEventTarget.h"
 #include "Shutdown.h"
 #include "nsCategoryCache.h"
 
 // This is the schema version. Update it at any schema change and add a
 // corresponding migrateVxx method below.
-#define DATABASE_SCHEMA_VERSION 43
+#define DATABASE_SCHEMA_VERSION 44
 
 // Fired after Places inited.
 #define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
 // This topic is received when the profile is about to be lost.  Places does
 // initial shutdown work and notifies TOPIC_PLACES_SHUTDOWN to all listeners.
 // Any shutdown work that requires the Places APIs should happen here.
 #define TOPIC_PROFILE_CHANGE_TEARDOWN "profile-change-teardown"
 // Fired when Places is shutting down.  Any code should stop accessing Places
@@ -299,16 +299,17 @@ protected:
   nsresult MigrateV36Up();
   nsresult MigrateV37Up();
   nsresult MigrateV38Up();
   nsresult MigrateV39Up();
   nsresult MigrateV40Up();
   nsresult MigrateV41Up();
   nsresult MigrateV42Up();
   nsresult MigrateV43Up();
+  nsresult MigrateV44Up();
 
   nsresult UpdateBookmarkRootTitles();
 
   friend class ConnectionShutdownBlocker;
 
   int64_t CreateMobileRoot();
   nsresult GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
                             nsTArray<int64_t>& aItemIds);
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -1,17 +1,14 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * 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/. */
 
-// It is expected that the test files importing this file define Cu etc.
-/* global Cu, Ci, Cc, Cr */
-
-const CURRENT_SCHEMA_VERSION = 43;
+const CURRENT_SCHEMA_VERSION = 44;
 const FIRST_UPGRADABLE_SCHEMA_VERSION = 30;
 
 const NS_APP_USER_PROFILE_50_DIR = "ProfD";
 
 // Shortcuts to transitions type.
 const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;
 const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
 const TRANSITION_BOOKMARK = Ci.nsINavHistoryService.TRANSITION_BOOKMARK;
new file mode 100644
index 0000000000000000000000000000000000000000..8ce5766f15cb40fbc441b2622880d4f7f9d38578
GIT binary patch
literal 5242880
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
zy5@C-<`s!+O|!3``D2de`}_0pOKi)dm0bC9uKY;8JTQNb*AHCE7K^#UBM)xto!Q&?
zeHV2ct<84w^5N`2@!ZJhv7`GQUiIk3*SxW}sUz`FZF}pJ)wVa=UmCBzX`q~~T*_5y
zU3MKheR?2&ao^d;kL^q(e&VM;+Wjm4<e5xuiE9=+?f6Alxy@Y{H<-Qdo1&L;uDq#Q
zDitaV_NP)T4Gv~&(?2#bK3+UOF?{5}$mxeyUEg}-;DyGH#L>i_g>UC*?!nyUT%k0U
zD=)e0<=j}QP-`_<8+g;y?#FwMpPtyc<LONe53l;E_g(s#^XZPn;l!?6NA4dVt`E4>
zz;Ql2-d`Jc|JMDd&+Q*MpM7foP;K1b_~ujZct@%u@mS)~TX(C!P#WGmvlmN_Jz1z;
z8qOCt50?tj_-Bu9Ix|(gQaOC2a^~SxKl5kTeru$mBT@SEyk(Yr@)v*IywLWxW4rnW
zcJ=SsGjjUy*@sts?n_TUS4nmx!k_PzR(dKoP_4|(x^wI1t=XRMHt9RR@WC^=laC#I
z`f%mQ(d}0rUiBwO$F{cDKGZYehZErs!WY874F5FzWGw&y00000000000000000000
z0000000000000000DwDdWu__7oXjMf*CZN;M%rF^skw1gV^bm-6{S1kvg@B|X<U(x
zo3%8z*NfJzT$P$>)vzosdU2w&ab-hOq9rbii(Y7ZTVqGE*0H75F)n%M7e3e?&HCIO
zK5GB~000000000000000000000000000000000000Jy_iqvW|eya)gQ0000000000
z00000000000000000000000000Jy{2Yky7QEs5}-!@mw64L=;l!rN;h0000000000
z00000000000000000000000000002|54oo)ljvz^PK*_@#awmsXlW{6C}cO6%EO84
zt1_9y`dZy+zP~?T-8@hlO<aF%V<xeyRyLTsoGX;Za^;0xUX#uwcGRl-$A{~s3u?Pl
znZzSeZJ{*0xtJfmR4q&{Y;jLReL2~|!BVlZu&y(iNjwlOXMSO8?HroIU?Ti}cqRON
zcqzOqJW~q+00000000000000000000000000000000000007`G+}c!6a;%Uo=Bk@V
zOH=tmA-lO$9&TEmdZ1-qygao&Ihyb9&sR4Oltwdmr*<U=bC+|4(pauMzhC2BsU6Aw
z@!@*Gyo&T|QjaA23#H-B#r*K4YGHDIWhz=kc5tv%tjsTNXn*F_#66Q=YY3kYFNcw^
zC#0r7JN-AO4@@^){l%-7ukL#G51;+`v(G&HmS;21eE6BlGjD#TY3kRe-aqxfO?6Lx
z?N0A2000000000000000000000000000000000000Kor@o$Hn-T9Pfv^@&6#^I%KM
z+NOgOd!wI&-gmM$(Ks~H_R34mvw3bJ{-NmJzM3DnC+e4M*)-E{&0TZ-&gU!nYHo0e
z-rJ(yJMNBqH{UnYd-aXpOLXgry7k_*Jdvz*TsPCLAv4o$iB3_UzBhNpb0r_DcS?44
zruG&J-TO+Vk<n~<q;hlL;U^}Kly;O(J#p^X`P8<x_0DT%I<M+%IGY`w>GS^X+5F(K
z!#j`m6pxQQe(3Sk&NcO|nK`-S%FY9&E5$-7J9taKiNl94z4dr+|IWk3CsW&3*EeP6
z?2;=wkK`-WQhBo8`aQd6^YYfgi+$VrCby3kCQ_R^>wA)z>DAC!Z}#rfvnTY#sS~4D
zjvd)~xccOtrud?7oV((Q=G$KJ-7D)$dSGVo<qc<Y1G!?gdu|i1Z~as~Kb<(V``FpZ
zvuB=qdf;fKVb6+s&j)9Ewxv#%D%I}!_r{C&)t7jE`-jdA<VMf+=8g=U8Xs!d-BI7a
zde`Qb*3{u#wfkuCa;b26rt7=D_VR3g;=ry$yU*@DQP_20`<s{7uhxc{o-Iw8)A8@C
zcb}~Pw0Yd=g_ob3&2tk6PhEOy&$&}4i~IJs*E{c->D=7ZcpzUH7_U^iPt|`ny}MNG
zK6+;V&4XMV8b5q-q(6V`a(VcXW%WTe%?#4il#Xt;$<lbad!U@HT*_5$?tUq^>%ykd
zr>~rT^4x{JZT0RuX1ZsZQnPcP%~h(oa`)L%sc_2>dv`rOls|X&{Alq|@8Q<^5RcY}
zNH#V#%$$7pf!Uvp?>?2QUMZDFZXRdk<m83D#Y5ZA9^N+9@RpYPW$2w5DBUzW&c5;C
z?z83Wz(}rq^9Tn<`*-a<GIsgQfeV#}w=~!9f!VW3H66;Ab3>(xxebkemHy@-HXS^E
zqWsi_p{;v%Upmwj-Q060)6&q;aPtsna+TWV4&1u0)qPV#69>xsCwCVQ_h#x3$=$OL
zNqV%@pD*O%wo_MU`uxc6)l1{oMqk7B#szPiWJ7AaI8>`1yt&s*alO~pbiLP_*>_Y!
zb@o}A?NZC5vc4I;skleW+&tCVHSW)r7xbw;EHlM%pY+y-xKs1&i>YBWR~%o^qxM8b
zWwWz1ZcIi!nw#5adn{;Odta1GrRr?!miFj9@!TE!U;+RD00000000000000000000
z000000000000000fbRjVQS#g!UIYLD00000000000000000000000000000000000
z0Ni2iwZC+DB@w<I{&V<3?EnA(000000000000000000000000000000000000RD<w
zo^DLMHkrsxWCsUJ#cgZaQ;msrbMfkyhQ>r^vVC;0*w~zGOsq_{_fMr;YyHyUw-ezz
z;hW*N!+*Nd=Kufz000000000000000000000000000000000000DqyDrCyuNO=JfL
zOT}&Jrc`Hgbg<aamRdJkm}*R|Z0VnBuQg4EZzgL00RR9100000000000000000000
z00000000000002M_lXtB)Pu><(p29_Zt_a0JXlG$*Ge11Q;G1^@Y(RQ;oY@E00000
z0000000000000000000000000000000002^OSHZ*l{k93kR8ZX9xD%Li}|TsdCT0<
z<KwyV<o)SX;`owH4wVXnx$?SHD)Goz+-6H|VxTZSm^)N1jqb}1jEs+UHKY<d>H}<f
zVl-Q>?kkl>YWYZ4vQ{x#>dzN)Tl#0pDrZWiYHMv}jbS7az8e0Y@N)P+YKH&-00000
z0000000000000000000000000000000PvUTHK|9EV}<NMuCgUJF;EyE%pEG1M)ze0
zM#jfdU8x-{qow|QA-AQ!R2mu0mPaaQN~P-ksiVo$ai_=1!`WhfDp%ezcl7vpt~}ZJ
zK<fC?O%9a`gSm40uGEg1Hd~&kja}V0Ge<*vw6f>!@Xi1L00000000000000000000
z0000000000000000KgsAUi(`<y*d$oKm2ldU#Nz&VN+<I{>t>PPQN_;-1OP$?X^k(
z000000000000000000000000000000000000D$jP_bzWuB$FM@$(Bs!u2fGt(Ks~H
z_R34mv-!R4H|o0<sej|LsQ$s`WU^M@xVKQ)S1OH+X3Ha$bn2F|+HQ=sc5bZqchBbc
zw?_4A;<45?p3M%=jd7#Ry)8G!ShL6&4>d>i_r+tZX*^K6QY@6RgNqH+bYq~^a|69+
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
zRnKod(Kj;C+Y%KV+toL)tAEd)k<*9IHbe#cx9&fEZvV*n>{I)P(ow<g-cv)>$1ZNl
zj-T1KJSy0A==AA<{Kb7|A3wIUDJs~xb!6M|!qiCd;K}nVqJm9RyC3g4etKf(j;A*@
zw$?U26F!s(e-yqFelz^P;h)t4000000000000000000000000000000000000002^
z9<@B3%p}(&8iz*OUU{iG6Nepfc>Oaijd9ps57(_s$KkR#d~u>P6^9LR_(I#;8kVJ7
zGA(iR&M$nhwKjGp{D(yNv+)0gKMemRe6<z;000000000000000000000000000000
z00000006*WH;t)eGTj)Z4XJdhCE1WpH77Gsl1`<XTUu*PGU4YF;g7>t!>i#l;ZwB$
z000000000000000000000000000000000000002K_bp3hl8MHlk+xS}YR=3B*FV$J
zcq3T1GCdc(IMJD!3tnh@TSHqa(-H^o{K5xY!!IT72?vtlJK>MQH^P4mzZ?E-_+0pO
z_?7T~g^z}R5Pl;3Xn0R}SNNguY#0y4FdY8(a50<-$HRd;y%7Ka000000000000000
z000000000000000000000DN!Bq?0Ylwd>++%{_6p`tCUE?25BhYvXL?nmAjrI?k4_
zjI*{Can{-qXD!R)thqhTnwG^`rY+7ITjMO<5@)IAc<N+r+C(zha92I+tY=O2EK|=K
z>sh*<rRrHjV=ZfGXbqoC+!G#6hCdDeE&M_FTKEs))$r@#mGEfz7vZ0VkA)9~zaKsj
z-W%Q(o)1%@97e)mczZY-PKHBwdfx#600000000000000000000000000000000000
z0QjDfNw*}EP3to0WJ~g{dumy8@~*p^(#d49Gu2hk8`j3PoonLS&ed^gQ)YQRZ)~sU
z>1Fji)mG0NTH{_#Epe}=<~T}q#8GNR+@_(SDPBTyU0m9*GG0vPt~hJ#jI(qm&QgtW
z){t&Yx73EIrM33ybaQh{YxwcRJ=2#H;Sa-C!f%Ct89os{65bPjD2#`p@MJg=_JlWv
zbz#}`f1m#5^cSZ;H~r72e{TAxrr$UH&RQn`000000000000000000000000000000
z000000090cEl(#i$+d~bp^>&%UTR(!hihiTd*X2QY<PDZcFu-fak#1;zQ22I9Il)V
z*Tmt9dib8*tK)Ea99}=2SQ&?H^>F)#R>Waz9KP#oFL%UYOB}xN@^j1Muz5CYkHe<f
za9JE?X2Z5PY@7{S<1jrNw!~p-Hf(NBCo;*|6S`|b)VZt{)z|RKyH7`3RjbORlZoWo
zbvGK;&ZXf-^x}P|Q#Yd81WogzANjq^f~fIEG<Eg4bZc#jd!|2@2!9;D8eR>b37-l-
zAO1mjDO?XBltV6D2*<+@hMw@c&^rC!r~m8pm#2Sg`qR_DH2tyAb*Jwl0000000000
z0000000000000000000000000004mRW6M%Azg_>zOU?Jp5AL2H+%-Swx)Hp;d+q#S
z&5htayI0=`-hH}r{s8Odg6ms9wQ??a@xIe5=7M*9?d6WS;Dwi;TRuN%pC2rnAGFO6
zTIUBX^MmI3LDO6?b@jQ-{Gf4uke(l;<_8UJsZ7gk&|2Hnd!`R3!XJd+4Zjrrari)Z
zAxwnfa6TLi+rtClGhtOoPXE{GFHV1U`v0E($n=j*zc@WLou9r?>jVG*0000000000
z0000000000000000000000000z|B^q9!yRgK78q|$9wyC9xgt)CMp;&4&@8E!M;di
zR8TFI3jNt~T#$+jvcr|W8R@uS-_+2=f%5*z-NnOA9jOOf%B50uwnfySymjzm-?qNV
z?W2W>)ltD{slPT!y+I}}$Q8$Ddo)A^BPS;>>@6PJe)jOTsn)3AQf}9UO`}g=IsN3h
z3(KN{;U^}Kly;O(J#p^X`L3uSH<2A2EEVH1?wVJyEh@NYUO`V(aCg1n;HgVb?KyYq
zWO3i}sNmwz_~DBq{rO{;%fsza!MTCl=(*n9k%3d=Lo1?!Cr+Ihy>jfx&coFw_jE=D
zCl2g7wEOJd6NOy|ws%AYhjt%3J9+lZQ%?^Zt*nd+4jwza^Jq`;_{igj9$y<392o82
zwfD%_<ueB^R9faOtT$TNx~O39uBV6c=gyuVEgtG^iwZU!Jbt45)P<p~dv;%HZmnIs
zboh^n@SX5S;TyFB000000000000000000000000000000000000002^>nf8@CX#E?
zake^D%i5ET4UM&|CEXg;KX-@M2LJ#700000000000000000000000000000000000
zcUWufZ+UocB77(OVfcFZQg}6dKKxqvmGFu1bKxI_p9~+URRRD2000000000000000
z00000000000000000000!2fbh>0~l_S892jcQr1n=c)EMU)$JP&r@x2zNWFco~K&k
ze05`{o~N4PyffWc&r|6*U)hj~^A!yZjp<}dOXg0WEdT%j00000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000Kos>9vl(?00008`v2M)761SM000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
z00000000000000000000000000000000000000000000000000000000000000000
S0000000000000000QdxG3DLs<
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v43.js
@@ -0,0 +1,210 @@
+/* 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____",
+];
+
+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 = OS.Path.join(OS.Constants.Path.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");
+
+  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}`);
+  }
+});
+
+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();
+
+  for (let anno of EXPECTED_REMOVED_ANNOTATIONS) {
+    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`);
+  }
+});
+
+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();
+
+  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.`);
+});
+
+add_task(async function test_mobile_bookmarks_root_still_exists() {
+  let db = await PlacesUtils.promiseDBConnection();
+
+  let rows = await db.execute(`
+    SELECT id FROM moz_anno_attributes
+    WHERE name = 'mobile/bookmarksRoot'
+  `);
+
+  Assert.equal(rows.length, 1,
+    "Mobile bookmarks root annotation should still exist");
+});
+
+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.`);
+});
--- a/toolkit/components/places/tests/migration/xpcshell.ini
+++ b/toolkit/components/places/tests/migration/xpcshell.ini
@@ -5,19 +5,21 @@ support-files =
   favicons_v41.sqlite
   places_outdated.sqlite
   places_v31.sqlite
   places_v34.sqlite
   places_v35.sqlite
   places_v36.sqlite
   places_v38.sqlite
   places_v42.sqlite
+  places_v43.sqlite
 
 [test_current_from_downgraded.js]
 [test_current_from_outdated.js]
 [test_current_from_v31.js]
 [test_current_from_v34.js]
 [test_current_from_v34_no_roots.js]
 [test_current_from_v35.js]
 [test_current_from_v36.js]
 [test_current_from_v38.js]
 [test_current_from_v41.js]
 [test_current_from_v42.js]
+[test_current_from_v43.js]