Bug 1146299 - Fix bookmark keywords, after the previous schema migration broke most of them. r=ttaubert
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 24 Mar 2015 16:22:29 +0100
changeset 235420 191f0b9b6a65816e4622b9e0503afc62e60fe6ee
parent 235419 175e8e5c6d8eade64bd63ca106f1d88b1f337265
child 235421 6a19207aece1bbd984c9bf7cb39356e2f8d98bb4
push id28472
push userkwierso@gmail.com
push dateWed, 25 Mar 2015 01:13:09 +0000
treeherdermozilla-central@cc0950b7a369 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersttaubert
bugs1146299
milestone39.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 1146299 - Fix bookmark keywords, after the previous schema migration broke most of them. r=ttaubert
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/migration/places_v28.sqlite
toolkit/components/places/tests/migration/test_current_from_v26.js
toolkit/components/places/tests/migration/test_current_from_v27.js
toolkit/components/places/tests/migration/xpcshell.ini
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -735,17 +735,22 @@ Database::InitSchema(bool* aDatabaseMigr
 
       // Firefox 37 uses schema version 26.
 
       if (currentSchemaVersion < 27) {
         rv = MigrateV27Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
-      // Firefox 38 uses schema version 27.
+      if (currentSchemaVersion < 28) {
+        rv = MigrateV28Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Firefox 39 uses schema version 28.
 
       // Schema Upgrades must add migration code here.
 
       rv = UpdateBookmarkRootTitles();
       // We don't want a broken localization to cause us to think
       // the database is corrupt and needs to be replaced.
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
@@ -1531,18 +1536,18 @@ Database::MigrateV27Up() {
   // a different post_data value.
   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "INSERT OR REPLACE INTO moz_keywords (id, keyword, place_id, post_data) "
     "SELECT k.id, k.keyword, h.id, MAX(a.content) "
     "FROM moz_places h "
     "JOIN moz_bookmarks b ON b.fk = h.id "
     "JOIN moz_keywords k ON k.id = b.keyword_id "
     "LEFT JOIN moz_items_annos a ON a.item_id = b.id "
-    "LEFT JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id "
-                                   "AND n.name = 'bookmarkProperties/POSTData'"
+                               "AND a.anno_attribute_id = (SELECT id FROM moz_anno_attributes "
+                                                          "WHERE name = 'bookmarkProperties/POSTData') "
     "WHERE k.place_id ISNULL "
     "GROUP BY keyword"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Remove any keyword that points to a non-existing place id.
   rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "DELETE FROM moz_keywords "
     "WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = moz_keywords.place_id)"));
@@ -1558,16 +1563,48 @@ Database::MigrateV27Up() {
     "(SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id) + "
     "(SELECT count(*) FROM moz_keywords WHERE place_id = moz_places.id) "
   ));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+nsresult
+Database::MigrateV28Up() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // v27 migration was bogus and set some unrelated annotations as post_data for
+  // keywords having an annotated bookmark.
+  // The current v27 migration function is fixed, but we still need to handle
+  // users that hit the bogus version.  Since we can't distinguish, we'll just
+  // set again all of the post data.
+  DebugOnly<nsresult> rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "UPDATE moz_keywords "
+    "SET post_data = ( "
+      "SELECT content 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 "
+      "WHERE n.name = 'bookmarkProperties/POSTData' "
+      "AND b.keyword_id = moz_keywords.id "
+      "ORDER BY b.lastModified DESC "
+      "LIMIT 1 "
+    ") "
+    "WHERE EXISTS(SELECT 1 FROM moz_bookmarks WHERE keyword_id = moz_keywords.id) "
+  ));
+  // In case the update fails a constraint, we don't want to throw away the
+  // whole database for just a few keywords.  In rare cases the user might have
+  // to recreate them.  Though, at this point, there shouldn't be 2 keywords
+  // pointing to the same url and post data, cause the previous migration step
+  // removed them.
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  return NS_OK;
+}
+
 void
 Database::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShuttingDown);
   MOZ_ASSERT(!mClosed);
 
   mShuttingDown = true;
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -11,17 +11,17 @@
 #include "nsIObserver.h"
 #include "mozilla/storage.h"
 #include "mozilla/storage/StatementCache.h"
 #include "mozilla/Attributes.h"
 #include "nsIEventTarget.h"
 
 // This is the schema version. Update it at any schema change and add a
 // corresponding migrateVxx method below.
-#define DATABASE_SCHEMA_VERSION 27
+#define DATABASE_SCHEMA_VERSION 28
 
 // Fired after Places inited.
 #define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
 // Fired when initialization fails due to a locked database.
 #define TOPIC_DATABASE_LOCKED "places-database-locked"
 // 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.
@@ -270,16 +270,17 @@ protected:
   nsresult MigrateV20Up();
   nsresult MigrateV21Up();
   nsresult MigrateV22Up();
   nsresult MigrateV23Up();
   nsresult MigrateV24Up();
   nsresult MigrateV25Up();
   nsresult MigrateV26Up();
   nsresult MigrateV27Up();
+  nsresult MigrateV28Up();
 
   nsresult UpdateBookmarkRootTitles();
 
 private:
   ~Database();
 
   /**
    * Singleton getter, invoked by class instantiation.
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -1,14 +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/. */
 
-const CURRENT_SCHEMA_VERSION = 27;
+const CURRENT_SCHEMA_VERSION = 28;
 const FIRST_UPGRADABLE_SCHEMA_VERSION = 11;
 
 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..9a27db32409aef699041ce3c04300e13215db582
GIT binary patch
literal 1212416
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
zPq>LD8!m2|((;7gT9cKh-2J6zNIsqE=|9y)9DCQO3pZ2kHQ$N^?+i8_`QDM6kE}g1
z`|y#&E3bX<+FTe0000000000000000000000000000000000000D$M6(dyc|$TjO1
zC#$QY(aL$%`-;VZ!bJ@YLqkJL`f`Ud*=%Y_F5lCT?q9rXM?-fepYF~LH}q$E_7$@u
zrF@~7AM9^fJ62G?d90v*ZK<Gs`(S_l)>KcrC!brA%4Rc%a(zom9asE3QZf7J%zeum
zm$o15z3|}bEnAnaUD{bw7s*8yCzI8YXPU_==WyaEUFAW#BD1!wziivkitc?YFTXI;
zo2-ij&pyaNKDR&JSu9MB)_mcT##Ga@4YudOeVr{UE=;c~boK5ZZo0U-F0w5WsSXFL
zj?7Dz|6%%zO8@@*CCla)a{1yVO)c~LQp4NRg~4pGa7k0+p5f;F!Jc&Mo`K%((OFe9
zBcmfHHt)`5yVChfR&Cj`VbjX(8+LSTYTLYP{&;lP=C$iLwyo)yES)IWxn;}7RV%m8
zKQNfikL)bXplN&tOOKzy{Pb{VcCahGEv%SHhmrJ_-0E%J%T~8GuNr8stc$EXaYE&8
zRvh0;b6cvA+1H-g(LU0)wW2PvE3&vc++eY=(KBzb)ZW}+@ubB~L~a@w-m>b@j>|?i
zY+k*4SE4S`@mzcC%=H)ZnZ1L>^hw>uBR3V=2llSnQAo8f&9wYZye`uBT)Umv7oCHJ
zVy-W9=;YpGk(+uCW;<7}$!sd@Z%*%y)kQkXy(gc0@B7l(fs;Cp+|;!v-E^Su!h>z=
zGN~)0b&*WD-^ed8>vXs%sq8deuQ=E<*x$AFiq4h;YwN=2Lp=CYB>2zZD`5fv00000
z000000000000000000000000000000006(rUJ$Q}M5C3_>R4^0V)oIQ`<69U%#Kw>
zlBHNpIn))IRWU1C6{(I^CaaU>(4GhP)s<RbcbaDp0000000000000000000000000
z00000000000001|SxxD1-D#c#000000000000000000000000000000000000001-
zW_95w8hj@b{to~E000000000000000000000000000000000000KX2-j8;Zo8mo@<
z<ql=C+0>F;z9({1O;`{Oeh>-&2LJ#70000000000000000000000000000000001h
zUmr8$FHQF44rQ|0)RJ7jr!EYgaqWjA!9NGL1Xl-*N1izH;Um``dF7Gl;ZGla%i-TX
z{E}-Q3yT2&00000000000000000000000000000000000fL~%SKVwED8i_{3)5^+u
z@mMTUG5cuU9p`^-B5lv@9ckS&(73*PWOwV#8Ifc(ntW-wa&>Hbx-;EhtlydHDU_@K
z>Dyj4mOlCTwsq|->sAczXv?&<TvAtVR^41~RyE!%J5s+Ym+S3I<$KdzW6df)JeKyZ
z8yZ~OooZ{}-rBKY=du}N&F7VySH?Hx3dQ=}nL?&G-tHZPjbrJTKfb1Q*@i7US`IB<
z+Ond#w%jiIo8@*DRq>U3bA!eDU1M{-?OWd;OMg1Nr?6-9#{T_{`wtCmUtTlTXl$@V
zRjfT-tY6=MFqb_zKH6=+vtleQ+_}Gf*S7tOH||a^9^QO$a%^oH#ul<_^<bfx>&qNU
z*RRdw)7`n@a-&<nvUM!|&xcoRT)wVr^RB*ih3@<-s>d#|soXeLwJx0<s898G)$bfE
z=JJ_Tc5KeK9(Z^xz2)=G>vtYX_mAx9?>W$RQPtRY`0}yVv2caT|1SOUBR5|$aoua1
z`c|jfcJ1G>q5bj|mEp+AXmYG=JiOXsroX3tM=_P3Sl+ik@aeJiM-LCQZ(Oru&lUT(
zZES0PMMY`UWORJa@m>9eT&d0Ew>mcGZD%bgr?))y@L+#;CY$c+C|QyiYkk)EoG16t
zMEx)=hOv%u7sbo<$Co!YIY#}?TrRsel|Qa+xFL#T;d0yMv2xq$g(tS%l<prqu3@;}
z`@-17pbMj=hSk+|V+~K7Up|*BPE-x6L@I($MS>p(-w7TK9t=+b000000000000000
z00000000000000000000007`uRc%H5#nHk3?o2k_RZ*RYpA{{pdJ2i!co;6`a@oD9
ze7rgqhWpa}gRx{Zeok^Ao9av#YQn}9!Tpio$>6)ew}OYmQvd(}000000000000000
z00000000000000000000_?1>0e{poMzdMsncU6?mi@98OZz><Jj-M4Rrg{pAWc-}u
zKsMExE|fxj>Hfi3U8&)9r+MN400000000000000000000000000000000000003~B
z)szm`o#sgZ00000000000000000000000000000000000006*gR#!S)cbX>w00000
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
z000000KfD$Cq~bU?kg4t3Kumr^yLoa@;ysByZV=er<rUvwWL2?Z0JjOWl{~9zMh8e
zOg`P68*b=M9n5s*`j>QdE?d^z+SuB-x4U&o7^!$wVl-avF6?b+XlSyVu-k^Ejg74h
zeW|YW;=zH%<2^JqFK%jCy1c2ixutnoYxDBNFWkY>sU0*n$A94tnx=Hnvb-^NY1l#X
z7wMpJY6mT?b>XVUg8L%DBjEu600000000000000000000000000000000000004f4
z&56Y$ty{CH&UB$+M_(#mT$Ri9hH3BIXgsoh?|5cwJ~xog7c*g{u5_U@pBX4-a{V=7
z`&jUIk>K0m0RR9100000000000000000000000000000000000ewEFQuTSpH<$C*4
z`QEMh+(0^C%%lqqUFkw+J~L3v<oeH!w??;SQ=MUSM_8-4YP@!=uGIdz(>#9w00000
z00000000000000000000000000000000216>cUSVsEGt`4fX}C;Ryf$0000000000
z00000000000000000000000000KeuICTb(mXk>16G+J3XGqye5neH#{O!X8J@kqt&
zqji7!qsT<s6fak8Dp#!=ubLfMmCN<^rSiS$u8Af+S5KtB6)QJ+X}QUa*rr^exI0tG
z6esJx<BJcErC<K|{^*QIG#MEks~lh1)m6WDyz4@JF;`#QmnqbD=dxYtd?{N<r^8UX
zuf8i&7|5nZ(p~kr{!*lV)nu!kxm<Q{DnCBf+uv~YL|PYKc_L_v1osDT4z3PQ00000
z0000000000000000000000000000000002sxmcV?TyXK6zTBaXy}4X(Un<{QI4;wX
zDW>}}{axwd*81?jhIrzFOP{IwKq{Z^FOD~8u5Y?9mbhTYv-O`%6^ebiu1t3(-F0lN
zrP0I%>(6|Kragn1u8zU}%z?r5c&nzG@RDkSPey{D1y2S~1m6uF555^Z8ay2QQ*eJ6
z1^@s60000000000000000000000000000000001hUm+FoXtbiXbWBu~j`2k47>g(3
z$!IJQk3?%qhh*tcT{={i4wdnEBv})-t_?1W1pgiUB=|w_Nbu?4gTbxAjX^Q!2*UsX
z000000000000000000000000000000000000PqWXVSI74C!brA%4Rc%a(zpNhK82Z
zP8Y11E|{DySTS8NF<me|T`+cLd~x!m=J80hx^$>29V%<Wnb!uNi3C3jo(!G{z8gFq
zd^317_)_p-@VPJy000000000000000000000000000000000000002LViNIWw6=7p
zDIJoDcy+Y8bf_vFDoclo(jieg#7l=*JYJow30u|%_e6rHf*%Lp555yT7JN1MQt(f~
zXM_8~FaQ7m00000000000000000000000000000000000{PL@e$D)y&D#nt;SP~yg
zVioaNGAxTnqg^%SV=@u1j#ifrRi#5^Dfsi+ny{PN;4dP<&w?j|CxY(=j|blj9u2+}
z+#mc+7zO|U00000000000000000000000000000000000fL~HG;+4@z#q6Uq_bqFz
zoy>GaX4On)_B^;RnTSWC)ulsK=}=iZRFn>h(ji_t#NzQtvL@^?9y}Nco(R4fCIA2c
z00000000000000000000000000000000000@VqfI5v`2YMk;0>ow;vWW4xTLDQCMP
zvto5&HkmAE_dK|-CafP1eh>+M7Cady00000000000000000000000000000000000
z0002+>!BhZjmF}McrrZ3tCKZhC?0${68tFmc9;MF0000000000000000000000000
z00000000000KoIijCf@<QZf7J%zeum<CB@L$gEgxyfRtN?0IlsO<41c;146gQ^5~|
z{|Fun9t|D}J|BE0_(bs0;O^jk!MlUo!(spc000000000000000000000000000000
z00000060A=6VYU}dS*GPsw*dzGs;OtZ8=HQl#_U}oW!at63MViWg-%d#?L4xv8u78
zVk}9FCGoK&RuLx2SWP(88NnY!g8vSF9DFZ$JotL><=_j!XM;}!9}Vsf-WR+(xGgLO
z00000000000000000000000000000000000004l~p(+uLMyukrV`;3WG7(KitCC?-
z9j&S^=i-&+Tzp2kPApzEmd4H~m&Rt6OJjAFiDWbyuP7(6L`5PQwhWJ9Q6f=YovaB5
zJ0n;S34Rzn7CaREeel=8UBPX^n}h3vLeLfL3f2b8!(spc00000000000000000000
z00000000000000004ST8h*n0cBNelc*8S;|kIyV;tH!du>*~tc%5wG{U;g-va<-zJ
zz3r#NwdHJLEL*s<rkstJv$y`|!^v_sHkQ5R^VM~UWM#CRd;5<buB=N$#&dU^|FtuY
z%f*i6-Z9u1KbE`gTi>rbmb>MthbxZdww<*gF(b?+Cvr96wbdTJJrX<=yeAk6Rs}CO
z^2H;!AK82OUk`sO3<CfF00000000000000000000000000000000000{L+{o-xeLn
zraIFX6-Nfrmn@rK$mNTdG_}m{OAT*N7Y4J%!X-_O)#t`nj#n#}uQ<NA@|wix$ar~o
zF58vPU$Sb;mJOR$Zr`w@V^iDaUGvAIyEd;~zp-si$7Jb5!OksPHm+K^eg1*Lbbe%K
zX(mnMGg*54Q1jEno!P;z^tP~KCLKnqE{O-xeZ}HH;i86yp`oEAeYr!KY&Nwdm+xs9
z$mjN_JBx*e?o2-2of~dwzHmuns;QJM6!U}q4awGc?v$-38ue#-_7$@u$6D1~9KYt2
zt<wFAckMWJ+uDYB=W}ekFP$A|h_%G~o@4#a!9p?DmpPPfh&RPE$>-Q8-CxY7vJH#l
z9nUgMXD+On**jQFHzXFscRfq()ZW}+v7w?S++(%D+DP!d;L+e8f_sAZ27es9J{Sr1
z1(%0m000000000000000000000000000000000000002^1<Z=iix%1k_O97cNVPA`
zv?NM_f#EHy4(+&XWW(mwyDLh8-h<iB)oU`F3j3SW@lqhYCAWH8_p;Tk&8r5gN`bCD
z>81mH7anX|mr2D+fzFl{7p7Mgx_b8yHziAfJ;TlUgFWfiJp;Yn)uq6;^_OiMTG734
z<>eP<YR-tyORhNBGuYp?^@`4x18Ykan%h!^%)a)_j`oqZ%2Hry`@!A|53b&_b?Mrr
zwKd`F&j@0X;CsQN!2`iZgZ~@+QE+209P|XcgSA0x@Uq~{uowUU000000000000000
z00000000000000000000fakqg@p;jq!L{9~w)X9<9UFGml>&qP-I;8<tD~f%6e#9$
z*}bWJIS?-gQay!^afvhH^OE^ot~e1WRTyaBxMs(mEB0^O*w$QG3iPG>2PZ1TN`d|D
zySD9Lym5DW@$lxFQeaPE&*qK&`y2Nk8roi43T#`~-m-4R;EuLTTg%K+pgp&Dq;=0g
z<NEHA-Di{nYn%F3r`mSy-?5?n@~To`P3y7^TXwV@TD-Jn#f(y5#m439x;F3XTUY4L
zCrg3m^*axx`$zWl_Z(=et|=|Sb*K3&00000000000000000000000000000000000
z0001h)2ya+xb8Gh0ssI200000000000000000000000000000000000PP3ZulL)>M
z37!g`2>v}h0RR910000000000000000000000000000000001h=cTGdG#Z^5OO(^P
RSiGFhh{Y-r(PT3I{{gy^q7485
--- a/toolkit/components/places/tests/migration/test_current_from_v26.js
+++ b/toolkit/components/places/tests/migration/test_current_from_v26.js
@@ -5,22 +5,25 @@ add_task(function* setup() {
   yield setupPlacesDatabase("places_v26.sqlite");
   // Setup database contents to be migrated.
   let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
   let db = yield Sqlite.openConnection({ path });
   // Add pages.
   yield db.execute(`INSERT INTO moz_places (url, guid)
                     VALUES ("http://test1.com/", "test1_______")
                          , ("http://test2.com/", "test2_______")
+                         , ("http://test3.com/", "test3_______")
                    `);
   // Add keywords.
   yield db.execute(`INSERT INTO moz_keywords (keyword)
                     VALUES ("kw1")
                          , ("kw2")
                          , ("kw3")
+                         , ("kw4")
+                         , ("kw5")
                    `);
   // Add bookmarks.
   let now = Date.now() * 1000;
   let index = 0;
   yield db.execute(`INSERT INTO moz_bookmarks (type, fk, parent, position, dateAdded, lastModified, keyword_id, guid)
                     VALUES (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
                              (SELECT id FROM moz_keywords WHERE keyword = 'kw1'), "bookmark1___")
                             /* same uri, different keyword */
@@ -33,25 +36,38 @@ add_task(function* setup() {
                          , (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
                              (SELECT id FROM moz_keywords WHERE keyword = 'kw1'), "bookmark4___")
                            /* same uri, same keyword as 2 */
                          , (1, (SELECT id FROM moz_places WHERE guid = 'test2_______'), 3, ${index++}, ${now}, ${now},
                              (SELECT id FROM moz_keywords WHERE keyword = 'kw2'), "bookmark5___")
                            /* different uri, same keyword as 1 */
                          , (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
                              (SELECT id FROM moz_keywords WHERE keyword = 'kw3'), "bookmark6___")
+                         , (1, (SELECT id FROM moz_places WHERE guid = 'test3_______'), 3, ${index++}, ${now}, ${now},
+                             (SELECT id FROM moz_keywords WHERE keyword = 'kw4'), "bookmark7___")
+                         /* same uri and post_data as bookmark7, different keyword */
+                         , (1, (SELECT id FROM moz_places WHERE guid = 'test3_______'), 3, ${index++}, ${now}, ${now},
+                             (SELECT id FROM moz_keywords WHERE keyword = 'kw5'), "bookmark8___")
                    `);
   // Add postData.
   yield db.execute(`INSERT INTO moz_anno_attributes (name)
-                    VALUES ("bookmarkProperties/POSTData")`);
+                    VALUES ("bookmarkProperties/POSTData")
+                         , ("someOtherAnno")`);
   yield db.execute(`INSERT INTO moz_items_annos(anno_attribute_id, item_id, content)
                     VALUES ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
                             (SELECT id FROM moz_bookmarks WHERE guid = "bookmark3___"), "postData1")
                          , ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
-                            (SELECT id FROM moz_bookmarks WHERE guid = "bookmark5___"), "postData2")`);
+                            (SELECT id FROM moz_bookmarks WHERE guid = "bookmark5___"), "postData2")
+                         , ((SELECT id FROM moz_anno_attributes where name = "someOtherAnno"),
+                            (SELECT id FROM moz_bookmarks WHERE guid = "bookmark5___"), "zzzzzzzzzz")
+                         , ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
+                            (SELECT id FROM moz_bookmarks WHERE guid = "bookmark7___"), "postData3")
+                         , ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
+                            (SELECT id FROM moz_bookmarks WHERE guid = "bookmark8___"), "postData3")
+                    `);
   yield db.close();
 });
 
 add_task(function* database_is_valid() {
   Assert.equal(PlacesUtils.history.databaseStatus,
                PlacesUtils.history.DATABASE_STATUS_UPGRADED);
 
   let db = yield PlacesUtils.promiseDBConnection();
@@ -64,12 +80,20 @@ add_task(function* test_keywords() {
   let [ url1, postData1 ] = PlacesUtils.getURLAndPostDataForKeyword("kw1");
   Assert.equal(url1, "http://test2.com/");
   Assert.equal(postData1, "postData1");
   let [ url2, postData2 ] = PlacesUtils.getURLAndPostDataForKeyword("kw2");
   Assert.equal(url2, "http://test2.com/");
   Assert.equal(postData2, "postData2");
   let [ url3, postData3 ] = PlacesUtils.getURLAndPostDataForKeyword("kw3");
   Assert.equal(url3, "http://test1.com/");
+  Assert.equal(postData3, null);
+  let [ url4, postData4 ] = PlacesUtils.getURLAndPostDataForKeyword("kw4");
+  Assert.equal(url4, null);
+  Assert.equal(postData4, null);
+  let [ url5, postData5 ] = PlacesUtils.getURLAndPostDataForKeyword("kw5");
+  Assert.equal(url5, "http://test3.com/");
+  Assert.equal(postData5, "postData3");
 
   Assert.equal((yield foreign_count("http://test1.com/")), 5); // 4 bookmark2 + 1 keywords
   Assert.equal((yield foreign_count("http://test2.com/")), 4); // 2 bookmark2 + 2 keywords
+  Assert.equal((yield foreign_count("http://test3.com/")), 3); // 2 bookmark2 + 1 keywords
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v27.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* setup() {
+  yield setupPlacesDatabase("places_v27.sqlite");
+  // Setup database contents to be migrated.
+  let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
+  let db = yield Sqlite.openConnection({ path });
+  // Add pages.
+  yield db.execute(`INSERT INTO moz_places (url, guid)
+                    VALUES ("http://test1.com/", "test1_______")
+                         , ("http://test2.com/", "test2_______")
+                   `);
+  // Add keywords.
+  yield db.execute(`INSERT INTO moz_keywords (keyword, place_id, post_data)
+                    VALUES ("kw1", (SELECT id FROM moz_places WHERE guid = "test2_______"), "broken data")
+                         , ("kw2", (SELECT id FROM moz_places WHERE guid = "test2_______"), NULL)
+                         , ("kw3", (SELECT id FROM moz_places WHERE guid = "test1_______"), "zzzzzzzzzz")
+                   `);
+  // Add bookmarks.
+  let now = Date.now() * 1000;
+  let index = 0;
+  yield db.execute(`INSERT INTO moz_bookmarks (type, fk, parent, position, dateAdded, lastModified, keyword_id, guid)
+                    VALUES (1, (SELECT id FROM moz_places WHERE guid = "test1_______"), 3, ${index++}, ${now}, ${now},
+                             (SELECT id FROM moz_keywords WHERE keyword = "kw1"), "bookmark1___")
+                            /* same uri, different keyword */
+                         , (1, (SELECT id FROM moz_places WHERE guid = "test1_______"), 3, ${index++}, ${now}, ${now},
+                             (SELECT id FROM moz_keywords WHERE keyword = "kw2"), "bookmark2___")
+                           /* different uri, same keyword as 1 */
+                         , (1, (SELECT id FROM moz_places WHERE guid = "test2_______"), 3, ${index++}, ${now}, ${now},
+                             (SELECT id FROM moz_keywords WHERE keyword = "kw1"), "bookmark3___")
+                           /* same uri, same keyword as 1 */
+                         , (1, (SELECT id FROM moz_places WHERE guid = "test1_______"), 3, ${index++}, ${now}, ${now},
+                             (SELECT id FROM moz_keywords WHERE keyword = "kw1"), "bookmark4___")
+                           /* same uri, same keyword as 2 */
+                         , (1, (SELECT id FROM moz_places WHERE guid = "test2_______"), 3, ${index++}, ${now}, ${now},
+                             (SELECT id FROM moz_keywords WHERE keyword = "kw2"), "bookmark5___")
+                           /* different uri, same keyword as 1 */
+                         , (1, (SELECT id FROM moz_places WHERE guid = "test1_______"), 3, ${index++}, ${now}, ${now},
+                             (SELECT id FROM moz_keywords WHERE keyword = "kw3"), "bookmark6___")
+                   `);
+  // Add postData.
+  yield db.execute(`INSERT INTO moz_anno_attributes (name)
+                    VALUES ("bookmarkProperties/POSTData")
+                         , ("someOtherAnno")`);
+  yield db.execute(`INSERT INTO moz_items_annos(anno_attribute_id, item_id, content)
+                    VALUES ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
+                            (SELECT id FROM moz_bookmarks WHERE guid = "bookmark3___"), "postData1")
+                         , ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
+                            (SELECT id FROM moz_bookmarks WHERE guid = "bookmark5___"), "postData2")
+                         , ((SELECT id FROM moz_anno_attributes where name = "someOtherAnno"),
+                            (SELECT id FROM moz_bookmarks WHERE guid = "bookmark5___"), "zzzzzzzzzz")
+                    `);
+  yield db.close();
+});
+
+add_task(function* database_is_valid() {
+  Assert.equal(PlacesUtils.history.databaseStatus,
+               PlacesUtils.history.DATABASE_STATUS_UPGRADED);
+
+  let db = yield PlacesUtils.promiseDBConnection();
+  Assert.equal((yield db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(function* test_keywords() {
+  // When 2 urls have the same keyword, if one has postData it will be
+  // preferred.
+  let [ url1, postData1 ] = PlacesUtils.getURLAndPostDataForKeyword("kw1");
+  Assert.equal(url1, "http://test2.com/");
+  Assert.equal(postData1, "postData1");
+  let [ url2, postData2 ] = PlacesUtils.getURLAndPostDataForKeyword("kw2");
+  Assert.equal(url2, "http://test2.com/");
+  Assert.equal(postData2, "postData2");
+  let [ url3, postData3 ] = PlacesUtils.getURLAndPostDataForKeyword("kw3");
+  Assert.equal(url3, "http://test1.com/");
+  Assert.equal(postData3, null);
+});
--- a/toolkit/components/places/tests/migration/xpcshell.ini
+++ b/toolkit/components/places/tests/migration/xpcshell.ini
@@ -11,16 +11,18 @@ support-files =
   places_v19.sqlite
   places_v21.sqlite
   places_v22.sqlite
   places_v23.sqlite
   places_v24.sqlite
   places_v25.sqlite
   places_v26.sqlite
   places_v27.sqlite
+  places_v28.sqlite
 
 [test_current_from_downgraded.js]
 [test_current_from_v6.js]
 [test_current_from_v16.js]
 [test_current_from_v19.js]
 [test_current_from_v24.js]
 [test_current_from_v25.js]
 [test_current_from_v26.js]
+[test_current_from_v27.js]