Bug 1293445 - Part 1: Add migration removing `RESULTS_AS_TAG_CONTENTS` from places.sqlite and associated tests. r?mak draft
authorMilindL <i.milind.luthra@gmail.com>
Wed, 04 Oct 2017 13:50:25 +0530
changeset 700539 df80df559ca8bfee012d4c3a01dbd8e7021ca939
parent 685447 967c95cee709756596860ed2a3e6ac06ea3a053f
child 700540 f40421807d8390b32979e8e3b6737f52056b0222
child 700542 24008650188fb354a08a2df22d2f2382aa84f479
push id89886
push userbmo:i.milind.luthra@gmail.com
push dateMon, 20 Nov 2017 10:17:19 +0000
reviewersmak
bugs1293445
milestone58.0a1
Bug 1293445 - Part 1: Add migration removing `RESULTS_AS_TAG_CONTENTS` from places.sqlite and associated tests. r?mak MozReview-Commit-ID: BpvhvEWBNoc
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/migration/places_v41.sqlite
toolkit/components/places/tests/migration/test_current_from_v41.js
toolkit/components/places/tests/migration/xpcshell.ini
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -5,30 +5,32 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ScopeExit.h"
 
 #include "Database.h"
 
 #include "nsIAnnotationService.h"
+#include "nsClassHashtable.h"
 #include "nsINavBookmarksService.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIFile.h"
 #include "nsIWritablePropertyBag2.h"
 
 #include "nsNavHistory.h"
 #include "nsPlacesTables.h"
 #include "nsPlacesIndexes.h"
 #include "nsPlacesTriggers.h"
 #include "nsPlacesMacros.h"
 #include "nsVariant.h"
 #include "SQLFunctions.h"
 #include "Helpers.h"
 #include "nsFaviconService.h"
+#include "nsNavHistoryQuery.h"
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "prenv.h"
 #include "prsystem.h"
 #include "nsPrintfCString.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
@@ -1061,16 +1063,23 @@ Database::InitSchema(bool* aDatabaseMigr
 
       if (currentSchemaVersion < 41) {
         rv = MigrateV41Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       // Firefox 58 uses schema version 41.
 
+      if (currentSchemaVersion < 42) {
+        rv = MigrateV42Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Firefox 59 uses schema version 42.
+
       // 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.
 
       rv = UpdateBookmarkRootTitles();
@@ -1810,16 +1819,128 @@ Database::MigrateV41Up() {
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "DROP TABLE IF EXISTS moz_favicons"));
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 nsresult
+Database::MigrateV42Up()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<mozIStorageStatement> placesQueryStmt;
+  nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id, url FROM moz_places "
+    "WHERE (url_hash BETWEEN hash(\"place\", \"prefix_lo\") AND "
+                            "hash(\"place\", \"prefix_hi\")) AND "
+    "url LIKE \"place:%type=7%\""
+  ), getter_AddRefs(placesQueryStmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool hasMoreQueries = false;
+  nsDataHashtable<nsUint32HashKey, nsCString> idURLMap;
+  while (NS_SUCCEEDED(rv = placesQueryStmt->ExecuteStep(&hasMoreQueries)) && hasMoreQueries) {
+    nsCString queryUrl;
+    int64_t queryId;
+    placesQueryStmt->GetInt64(0, &queryId);
+    placesQueryStmt->GetUTF8String(1, queryUrl);
+    idURLMap.Put((uint32_t) queryId, queryUrl);
+  }
+
+  if (idURLMap.Count() == 0) {
+    // Quit early, otherwise the replacementParamsArray will be empty causing
+    // BindParameters to fail.
+    return NS_OK;
+  }
+
+  nsNavHistory* historyService = nsNavHistory::GetHistoryService();
+  nsCOMPtr<mozIStorageAsyncStatement> replacementStmt;
+  nsCOMPtr<mozIStorageBindingParamsArray> replacementParamsArray;
+  rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+    "UPDATE moz_places SET url=:new_url,"
+    "url_hash=hash(:new_url) "
+    "WHERE id=:id"
+  ), getter_AddRefs(replacementStmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = replacementStmt->NewBindingParamsArray(getter_AddRefs(replacementParamsArray));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStorageStatement> tagStmt;
+  bool hasMoreTags;
+  nsString tagName;
+  for (auto iter = idURLMap.Iter(); !iter.Done(); iter.Next()) {
+    nsCString queryString = iter.UserData();
+    uint32_t queryId = iter.Key();
+    nsCOMArray<nsNavHistoryQuery> queries;
+    nsCOMPtr<nsNavHistoryQueryOptions> options;
+    historyService->QueryStringToQueryArray(queryString, &queries, getter_AddRefs(options));
+
+    for (uint32_t i = 0; i < queries.Length(); i++) {
+      uint16_t resultType;
+      rv = options->GetResultType(&resultType);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (resultType != nsNavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
+        continue;
+      }
+
+      uint32_t folderCount;
+      int64_t* folders;
+      nsTArray<nsString> associatedTags;
+      queries[i]->GetFolders(&folderCount, &folders);
+      // The folder count should be exactly one for RESULTS_AS_TAG_CONTENTS.
+      MOZ_ASSERT(folderCount == 1);
+      queries[i]->SetFolders(nullptr, 0);
+
+      rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+        "SELECT b.title FROM moz_bookmarks b "
+        "JOIN moz_bookmarks h ON b.parent = h.id "
+        "WHERE b.id = :folder_id AND h.guid = \"tags________\""
+      ), getter_AddRefs(tagStmt));
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = tagStmt->BindInt64ByName(NS_LITERAL_CSTRING("folder_id"), folders[0]);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = tagStmt->ExecuteStep(&hasMoreTags);
+      if (!hasMoreTags || NS_FAILED(rv)) {
+        // If the associated tag isn't found, don't rewrite the query.
+        continue;
+      }
+
+      rv = tagStmt->GetString(0, tagName);
+      NS_ENSURE_SUCCESS(rv, rv);
+      ToLowerCase(tagName);
+      associatedTags.AppendElement(tagName);
+      queries[i]->SetTags(associatedTags);
+    }
+    options->SetResultType(nsINavHistoryQueryOptions::RESULTS_AS_URI);
+    historyService->QueriesToQueryString((nsINavHistoryQuery**)queries.Elements(),
+                                         queries.Length(), options, queryString);
+
+    nsCOMPtr<mozIStorageBindingParams> replacementParams;
+    rv = replacementParamsArray->NewBindingParams(getter_AddRefs(replacementParams));
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = replacementParams->BindInt64ByName(NS_LITERAL_CSTRING("id"), queryId);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = replacementParams->BindUTF8StringByName(NS_LITERAL_CSTRING("new_url"),
+                                                 queryString);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = replacementParamsArray->AddParams(replacementParams);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = replacementStmt->BindParameters(replacementParamsArray);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
+  rv = replacementStmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+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 41
+#define DATABASE_SCHEMA_VERSION 42
 
 // 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
@@ -286,16 +286,17 @@ protected:
   nsresult MigrateV34Up();
   nsresult MigrateV35Up();
   nsresult MigrateV36Up();
   nsresult MigrateV37Up();
   nsresult MigrateV38Up();
   nsresult MigrateV39Up();
   nsresult MigrateV40Up();
   nsresult MigrateV41Up();
+  nsresult MigrateV42Up();
 
   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,17 @@
 /* -*- 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 = 41;
+const CURRENT_SCHEMA_VERSION = 42;
 const FIRST_UPGRADABLE_SCHEMA_VERSION = 30;
 
 const NS_APP_USER_PROFILE_50_DIR = "ProfD";
 const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
 
 // Shortcuts to transitions type.
 const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;
 const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
new file mode 100644
index 0000000000000000000000000000000000000000..8a8c03665d1495e392e4572e79c3da44698f2aed
GIT binary patch
literal 1146880
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
zUpf4__dnASKJ4PbpT)u-000000000000000000000000000000000000001ZW4I;W
z8q37f@k}Pxa(?RNPd+u8=m?AB!FOZf4*&oF000000000000000000000000000000
z00000yuoDRsrah4%y@aGJ<Q_4Yq9VL00000000000000000000000000000000000
z0002qXfmnzs<zB{d8Q*wT7ySp!Jh=52|gS=A4~*47N!6I00000000000000000000
z0000000000000000Px>rU28hFInfrIC}vCfTJLyyrcf+qd&`xP%<U`F>Db+gOl-U`
zI9RCl4wc7a&#!BV+Gk?fT&`TIE^Z%t{!OWLY-d=P%U{eF%M<y^)zx<-)3NPg_2A@4
zqx7oUo<utKa8z3?kMx!bBcrwA^wlj^#nZ8SqU9_uY!A<&HOR$+Zv_7?_~qcIgLehb
zged?3000000000000000000000000000000000000Q`4%YjSgZqL?k^YrW&;nL@Fc
z?JZYET300RX<L-9NZuVEFANSAYQ00{@$_xUo$*}$V!l|O$X6EkORY_Aj}K0cGzu0~
zB=1N*93L!}M|w+zk<nUldU0hUT0}ONE0?N^i(5Lv^{tz^B^G=x_+Nr2g7vdspZ(Bm
zdG@WBzk2xtm(N{Z{p{zT{d>=zcy`S*fAGvNJu~~v!DqT=zBu#qGZQoYVJ83p00000
z00000000000000000000000000001hH-g@ED`IW&cs%@=POoo^cXcOXE$?{cT`zq&
z)`*{d^wi0xPi#B2wR+^qzP-tfx7FLXHQRS~H<~?{X~Yk%o!ENd%(*9v<<pNHKa||K
zw%$C`Y`$Wy`O!xk@zR4&A1<FCd}8mBslmfLlRwhEA{LLu<M+-rPpnGz7mGc+%jL20
zY-Oz4=(OvF<`PTWvweGZOr0Aoof}SXxwUaBYvww4tV*2Dj?DE*f2$F<zgVkG6%USH
z*gEj|fn0LOn#SqeHP>hPs=ei@Qn8%PHTr$w_q!VLzw}%<{`lFmyCydG5A1(Dxovgh
zyl$WCw`|qHLbX<|OgCEp!of!T{C#~-OxK2w>^M0#HvU9%<ErTN+U7Sq(b8x(bE0`-
z$HsO{4W2rXJv=n9qxFH6_1$Rhbz3t0YR*r++>+{R#zQmH2m8y#>~M8(<iT4SOS)%n
z@b<*X{7}AB>uGF4*dhLbSUsjb^WKBUMo(0>KY8Z#&i&=YuC7MU^>aPjl1IzcTF==+
zwNT6FqAp*1?dP6r4DqvTbK4)A-qUxncI41VU*bnQ8+*Q?-Zc}?WYQ<<fBL-VXydPr
z*L#2G)~-f;(^vNOPnDl|eE9HV1Lbpxy(=2Mcg^)~&9v+-REH+3)t+OGziHl6F7+Hb
zxu?;){ahoic=S>^xA}PP-1M1a2ev2P-q9Fk<J>6eOe(r)rpuF+o}o&%I-0Ma{?}gp
z-V2TXiJ#1GJ2&1}-ZfZ0I&vVfdwHY(_PPEonPhYJ)A?#GU+FnrE*Iy<_-3pTzx(2z
zTHm4ZEgO#vT*y9|IIyfS#v^lMq%w)Q4d~h1{HyLg$MUtQa%HSBz?c7aBYwT_*r6>)
z_wDH4dgkKHzT~6rjSF(?+(5}pbDZ6iBR!`p*`cv~rP1?Wf2$FH@9%Hjv-jxqz}bz*
z&+gxv>~Cv)o6XZnWcC*-`Qh@V`8|#PXndpVCmw9XPiGF!?9X55JDwY=O+KACkZIh8
z&9g})64#D#GG7h%cc{6wAAhd-MW1-OR4QycvAH<CYj5Vk)^IK11+~6;HV48h<51~h
zxp;Bz!g}>*{<0B2^4_D{P7ZGwcyROfv7<wYA5AxIm5p=Pej;_WJXk2^7e5QaZeLvW
zLNkun>j&z7w58Ge{^pIAJYE|OzkzEycfQz|`$FGjX*leht9MTHr5e50H0Mq<Z=v}v
zVXTE^19Kiu)_b&VYR)%5QT^H(%vP@I9o|i~MsdA&%R`BJ_soXtyC2P$CKvaO?)vet
ztT}Vb1M#SDCezXEyU@DvXsMLTwPx!?NA%2p?)Ce10{{R300000000000000000000
z000000000000000e;u?(hv#1BMF0Q*00000000000000000000000000000000000
z!0W6d{Fey+Bo=%(JOBUy00000000000000000000000000000000000z~6jriI&)^
zcx*gZN@e0Lv0LJ?!I@-xSdj?69Sgn}9smFU000000000000000000000000000000
z00000;0>cSxhg)MD<xWzx3mq;bc7}G;Ll^>4*&oF0000000000000000000000000
z0000000000yg_uulk4N-<(Yx8{Pa}0lB*^<!qRkbAQpT*_;m2$;O_+&!UO;S00000
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
z)4{I?zZ!fj_~&5*000000000000000000000000000000000000001ZJ*AV0c;@y@
z6k^Y}HbS})S{fnM2+2lBv?LR4VPP^8?}`qc(P2e&=!g!>qr<Z3&>kJy+QTWjf)B-l
z9|W%j-wysPcqRC1@TK4j!RLd2AAC0W&EPkJUkiRUYybcN00000000000000000000
z0000000000000000Dm1UPp0Fsmh)3DfAXo(Oez_RcSVQJ=&&L>bVP^c(P3G1XpatU
z(V;auq@zPibVx;qWOPU*ld-n;aOketzla6j489cnR`7}7!@>K4p9(GoBf;5Vf3Q7x
zORy?Pgbe@y000000000000000000000000000000000000Qj4wE4e;?vH0})-TN+9
zriO+uq@#k`*8P>?-7}ZQE}iM`iVCW`h7S$)ZyTMsbfDB06^vH<4<9@@bK=<k)45bs
zke}RAJ6hd7aOUKN{?^Xq`nKGOVzF51yYSfb%=xInK<(7tll}cqj2?OH=<<5OlUp|K
zJX||Jl^xG@Mg@;w7(cvia_IEN!`ZFLsNlqj{TB<z4)5B#b8v4WD%dx8cCvJ|yti-H
zzFd1$(7$!#@PUmd9@}vtKfWR=*frEw*?n;5Wci7)vn^4<&f_P>4xY-M-Cx~(ArlpB
ze`5G#Uv{Q;;P}LjWl_PlnSAxw;p+5*7msf2Xb-m}9(+3%{r~^~000000000000000
z00000000000000000000z#B+MDxPl3#9Gczz5L0iM%%-Zc<_T*_yYg{0000000000
z00000000000000000000000000B=aiWTve>jPc;rSoi|~00000000000000000000
z000000000000000003_&%aZA~Sj+jTmp}Q`XnUBi3y#Es?*;!k_|xEP!Iy&H3tkL9
z6MQQ8wcuX_9}PYjyg&Hq;2#8kH~8`3a&R#y2ctnYcrrK@b^-tZ000000000000000
z00000000000000000000fF-0;@wRx^x_aomtsYjat%r_V>tXqtdRVr)9@=lIhqkVI
z$aL02>xz0vchp16@_I-utA}KJJtW#%Qf*<&bSf5)Cs)=(qPr1RHA1El(v8s42&qO$
zHbSB$3~h<_aNX;IeX-#C!FPgh2CoGFDR?RP55d0+elz&>;8%l>1^+zwK=9MSdxD<|
zt_0ITB^V2af_DU`gQH<50000000000000000000000000000000000000013tTh#n
z$Ga2j(y4e`y!*B=Wa8ax>!qub-Hn*IwO+ewO}%#2>UwD=-PwpOD;hD?(TK_AjhI+g
z@0Dq<_sX=@vt(;MOLo=UBodi=X<}u)G;vEh)fSJptg44px*n1(^^i!lq}sy%;W2EO
zN@X%_?cws*&Hh9z_;&C|!S4qDCiqvuM}nUV-V^+tU@{m9&IN~phl2ZqJA#hc|9AGA
zvtOD0?b%Pw{_^ZUo&BG~P5=M^00000000000000000000000000000000000{CBn@
z6;H>zVlD4@<y|j*_R)3qw6l?ZaP4jNbVVb*^x)ch+R;e2zj$jsU0zSW@Lzh?)YD~+
z^z-+vuBYv>mh)3Dr#|!ETk2_BJ&phD+OB$<iPEoq=S^Sftf#Gwbj719>S?->CVsM`
zp0?D}SHJt>@_L$Tq+jn_R!@_S^!NULdp%7w(objFI#O-vcq9ABds{nFu_%j9;KQ-*
zMcJ+wRxJ;+w#M>jPRvz>t8Gd3t-LBr%x7PF?dP6LE@a=iwJWoby?UXqWg+|Gsu$9W
zvd$M%?cwgPoBdcUcs2NX@KW$w!EXe=9Q?E3r-SE%AgBcS;9PJxczdupcuUY7w9o!O
zv)`Ki>g?~%erEO)VJ83p000000000000000000000000000000000000A4T4lhNO<
zf5$8Ddg;TlbqmRJncEhUqmQm#NOrx@y^y58b?bcch2QU5vylA4!PN`N%!yTv<owjj
zEvdeh^GW;zv0IwS*IxU%=ep*T@7&texsbG<TQQ%!`n?xA7LsqqmM<h<{@cqIl7IcJ
z_J!mV54J5NAAc^hko?SFw$3MCT=hbFA?bXvWpR>PoFo?~iDk)jTQg}7H+9|Yfmra(
z;7h@81)m5$9K0|1so+vD5}Xb81>1sqf|Wsh_FJ=Gp8daPe|`2Nvp+NYcV}m23$y3K
zP5=M^00000000000000000000000000000000000T&pX&K7Og3+k8BCZu-oz1KU?b
z1(T)WLNT8kh_plnwaQfS;OK>|1CJlb-C8dwmy3hhO1(w0UXUHB4$Mh)CfB!B%H>+K
zAZqaR;qv*xC-xqh8a%vnZB#I^^}v~PPZrCkA3J{Nwy2=AJ=?cu$JDve(z)U8s9?N2
z7%rnRM7my(FHJVbOhg42jz50(?5>H;{R8_SUlSGNx1AgBEAJXCA00W+5f$XNKQ_Im
z?_%x9p^?6>sNmGer%!A<wY7TW$-ceoqJk4omr8|gCpH(SckRtY1;@sAObwnokUcy!
zuwzxd;Lw(%`*!qiJ#%qp-?FIS=(dx?TLvE7ynXEGP-|3hXlD9gf4P_)t`3f@j0z6U
z?9X55JDwY=O+MXLFE}=OqO$$TGpBd%FW(Xs?CYN@Kk@kR;l~Eb=Q^W;J+;0=<6AZ!
z8Mu&ra(Ps+b<f_T(*tKW9zVN(YkO4C_r!E<_{ffvV`Jk_tZom#yhQMyW5M^r0{{R3
z00000000000000000000000000000000000ym6#c@mM^SXbD4GvOOw!?sZ-f00000
z0000000000000000000000000000000002I&f3F&tAoK<@Rz~2gFg=bFnB5WT=3c8
zQ^CIq{<q*4gMS>nFZc(+yMlKHGeIS|5H<t=000000000000000000000000000000
z000000D!-e)>J$mUzc9hh^f`}cw73GMog`&$F=FsMoe|pV|RK*Bc?j)@z(URMocZQ
u$2IA;MohKW<LY#4Bc?L-xT+=Hh^dx(yd{~c$F5|uB^7ULYt$waiT@9h2iybz
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v41.js
@@ -0,0 +1,187 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { TYPE_BOOKMARK, TYPE_FOLDER } = Ci.nsINavBookmarksService;
+
+// Hashes are precalculated since URL generated each time is a constant.
+// They will need to be recalculated in case the test is changed.
+const gQueryData = [
+  {
+    statement: `INSERT INTO moz_places (url, guid, url_hash)
+                SELECT  'place:folder='||b.id||'&type=7&queryType=1', 'query1______', 268507487492160
+                FROM moz_bookmarks b
+                WHERE b.guid = 'tag1________';`,
+    guid: "query1______",
+    oldQueryString: null,
+    newQueryString: null
+  },
+  {
+    statement: `INSERT INTO moz_places (url, guid, url_hash)
+                SELECT  'place:folder='
+                ||(SELECT id FROM moz_bookmarks WHERE guid = 'tag1________')||
+                '&type=7&queryType=1&OR&folder='
+                ||(SELECT id FROM moz_bookmarks WHERE guid = 'tag2________')||
+                '&type=7&queryType=1', 'query2______', 268505990850137`,
+    guid: "query2______",
+    oldQueryString: null,
+    newQueryString: null
+  }
+];
+
+function makeTagGuid(tagName) {
+  // Be careful about having |tagName| > 12, since the guids
+  // will be unique only if tagName[0:12] is unique.
+  if (tagName.length >= 12) {
+    return tagName.slice(0, 12);
+  }
+
+  return tagName + "_".repeat(12 - tagName.length);
+}
+
+async function getIdForTag(tagName) {
+  return PlacesUtils.promiseItemId(makeTagGuid(tagName));
+}
+
+function copyPropertiesExcept(obj, except) {
+  let newObj = {};
+  for (const property in obj) {
+    if (obj.hasOwnProperty(property) &&
+        !except.includes(property) &&
+        typeof(obj[property]) !== "function") {
+      newObj[property] = obj[property];
+    }
+  }
+  return newObj;
+}
+
+async function insertPlacesQuery(db, qData) {
+  // Note that we cannot use Places.history here.
+  await db.execute(qData.statement);
+  const rows = await db.execute(`SELECT url FROM moz_places WHERE guid=:guid`,
+                                { guid: qData.guid });
+  Assert.equal(rows.length, 1);
+  qData.oldQueryString = rows[0].getResultByName("url");
+}
+
+async function validateMigration(qData) {
+  qData.newQueryString = (await PlacesUtils.history.fetch(qData.guid)).url;
+  do_print(`Old query string: ${qData.oldQueryString}`);
+  do_print(`New query string: ${qData.newQueryString}`);
+  let oldQueries = {}, oldOptions = {};
+  PlacesUtils.history.queryStringToQueries(qData.oldQueryString,
+                                           oldQueries,
+                                           { },
+                                           oldOptions);
+  let newQueries = {}, newOptions = {};
+  PlacesUtils.history.queryStringToQueries(qData.newQueryString,
+                                           newQueries,
+                                           { },
+                                           newOptions);
+
+  // Check options.
+  // Query types should be different.
+  Assert.equal(oldOptions.value.resultType, 7, "Old result type is 7.");
+  Assert.equal(newOptions.value.resultType, 0, "New result type is 0.");
+  // Rest of the options object should be the same.
+  Assert.deepEqual(copyPropertiesExcept(oldOptions.value, ["resultType"]),
+                   copyPropertiesExcept(newOptions.value, ["resultType"]),
+                   "Options are identical except for resultType.");
+
+  // Check queries.
+  Assert.equal(oldQueries.value.length, newQueries.value.length, "Number of queries is same.");
+  for (let i = 0; i < oldQueries.value.length; i++) {
+    // Number of tags == folderCount == 1.
+    // Folder ids should be replaced by correct tags.
+    Assert.equal(newQueries.value[i].tags.length, 1);
+    Assert.equal(oldQueries.value[i].getFolders().length, 1);
+    const tagName = newQueries.value[i].tags[0];
+    const folderId = oldQueries.value[i].getFolders()[0];
+    Assert.equal(folderId, (await getIdForTag(tagName)));
+
+    // Rest of the query object should be the same.
+    Assert.deepEqual(copyPropertiesExcept(oldQueries.value[i], ["folderCount", "tags"]),
+                     copyPropertiesExcept(newQueries.value[i], ["folderCount", "tags"]),
+                     "Query/Queries is/are identical except for folderCount and tags.");
+  }
+
+
+  // Query result URIs should be identical.
+  const oldRoot = PlacesUtils.history.executeQueries(oldQueries.value,
+                                                     oldQueries.value.length,
+                                                     oldOptions.value).root;
+  const newRoot = PlacesUtils.history.executeQueries(newQueries.value,
+                                                     newQueries.value.length,
+                                                     newOptions.value).root;
+
+  oldRoot.containerOpen = true;
+  newRoot.containerOpen = true;
+  const oldUris = [];
+  const newUris = [];
+  Assert.equal(oldRoot.childCount, newRoot.childCount, "Number of results is the same.");
+  for (let i = 0; i < oldRoot.childCount; i++) {
+    oldUris.push(oldRoot.getChild(i).uri);
+    newUris.push(newRoot.getChild(i).uri);
+  }
+
+  for (const uri of oldUris) {
+    Assert.ok(newUris.includes(uri));
+  }
+}
+
+add_task(async function setup() {
+  await setupPlacesDatabase("places_v41.sqlite");
+  let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
+  let db = await Sqlite.openConnection({ path });
+  // Add pages.
+  await db.execute(`INSERT INTO moz_places (url, guid, rev_host, hidden, frecency, url_hash)
+                    VALUES ("http://test1.com/", "test1_______", "moc.1tset.", 0, -1, 125509083372812)
+                         , ("http://test2.com/", "test2_______", "moc.2tset.", 0, -1, 125508024173255)
+                         , ("http://test3.com/", "test3_______", "moc.3tset.", 0, -1, 125511593739455)
+                   `);
+  // Add bookmarks.
+  let now = Date.now() * 1000;
+  let index = 0;
+  await db.execute(`INSERT INTO moz_bookmarks (type, fk, parent, position, dateAdded, lastModified, guid)
+                    VALUES (${TYPE_BOOKMARK}, (SELECT id FROM moz_places WHERE guid = 'test1_______'), (SELECT id FROM moz_places WHERE guid = 'toolbar_____'), ${index++}, ${now}, ${now}, "bookmark1___")
+                         , (${TYPE_BOOKMARK}, (SELECT id FROM moz_places WHERE guid = 'test2_______'), (SELECT id FROM moz_places WHERE guid = 'toolbar_____'), ${index++}, ${now}, ${now}, "bookmark2___")
+                         , (${TYPE_BOOKMARK}, (SELECT id FROM moz_places WHERE guid = 'test3_______'), (SELECT id FROM moz_places WHERE guid = 'toolbar_____'), ${index++}, ${now}, ${now}, "bookmark3___")
+                   `);
+
+  // Add tag folders.
+  await db.execute(`INSERT INTO moz_bookmarks (type, parent, position, title, dateAdded, lastModified, guid)
+                    VALUES (${TYPE_FOLDER}, (SELECT id FROM moz_bookmarks WHERE guid = 'tags________'), ${index++}, "tag1", ${now}, ${now}, "${makeTagGuid("tag1")}")
+                         , (${TYPE_FOLDER}, (SELECT id FROM moz_bookmarks WHERE guid = 'tags________'), ${index++}, "tag2", ${now}, ${now}, "${makeTagGuid("tag2")}")
+                         , (${TYPE_FOLDER}, (SELECT id FROM moz_bookmarks WHERE guid = 'tags________'), ${index++}, "tag3", ${now}, ${now}, "${makeTagGuid("tag3")}")
+                   `);
+
+  // Add tag-bookmark association
+  await db.execute(`INSERT INTO moz_bookmarks (type, fk, parent, position, dateAdded, lastModified, guid)
+                    VALUES (${TYPE_BOOKMARK}, (SELECT id FROM moz_places WHERE guid = 'test1_______'), (SELECT id FROM moz_bookmarks WHERE guid = 'tag1________'),${index++}, ${now}, ${now}, "bmkTagAsc1__")
+                         , (${TYPE_BOOKMARK}, (SELECT id FROM moz_places WHERE guid = 'test1_______'), (SELECT id FROM moz_bookmarks WHERE guid = 'tag2________'),${index++}, ${now}, ${now}, "bmkTagAsc2__")
+                         , (${TYPE_BOOKMARK}, (SELECT id FROM moz_places WHERE guid = 'test1_______'), (SELECT id FROM moz_bookmarks WHERE guid = 'tag3________'),${index++}, ${now}, ${now}, "bmkTagAsc3__")
+                         , (${TYPE_BOOKMARK}, (SELECT id FROM moz_places WHERE guid = 'test2_______'), (SELECT id FROM moz_bookmarks WHERE guid = 'tag1________'),${index++}, ${now}, ${now}, "bmkTagAsc4__")
+                         , (${TYPE_BOOKMARK}, (SELECT id FROM moz_places WHERE guid = 'test2_______'), (SELECT id FROM moz_bookmarks WHERE guid = 'tag2________'),${index++}, ${now}, ${now}, "bmkTagAsc5__")
+                         , (${TYPE_BOOKMARK}, (SELECT id FROM moz_places WHERE guid = 'test3_______'), (SELECT id FROM moz_bookmarks WHERE guid = 'tag1________'),${index++}, ${now}, ${now}, "bmkTagAsc6__")
+                  `);
+
+  // Add Places Queries.
+  for (const queryData of gQueryData) {
+    await insertPlacesQuery(db, queryData);
+  }
+  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);
+
+  for (const queryData of gQueryData) {
+    await validateMigration(queryData);
+  }
+
+  await db.close();
+});
--- a/toolkit/components/places/tests/migration/xpcshell.ini
+++ b/toolkit/components/places/tests/migration/xpcshell.ini
@@ -3,17 +3,19 @@ head = head_migration.js
 
 support-files =
   places_outdated.sqlite
   places_v31.sqlite
   places_v34.sqlite
   places_v35.sqlite
   places_v36.sqlite
   places_v38.sqlite
+  places_v41.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]