Change the keywords schema associating them with uris

RESOLVED FIXED in Firefox 39

Status

()

defect
RESOLVED FIXED
4 years ago
2 years ago

People

(Reporter: mak, Assigned: mak)

Tracking

unspecified
mozilla39
Points:
5
Dependency tree / graph
Bug Flags:
firefox-backlog +
in-testsuite +
qe-verify -

Firefox Tracking Flags

(firefox39 fixed)

Details

Attachments

(1 attachment, 2 obsolete attachments)

we need to change the current schema so that keywords are directly associated with uris and they increase foreign_count (so the uri is not expired)

this means changing from multiple uris for one keyword to 1 uri - 1 keyword, that will allow us to simplify management and caching.
Flags: qe-verify-
Flags: in-testsuite?
Flags: firefox-backlog+
Points: --- → 5
Assignee: nobody → mak77
Status: NEW → ASSIGNED
Iteration: --- → 38.2 - 9 Feb
Posted patch patch v1 (obsolete) — Splinter Review
More or less, this is the patch.

The main change is that now we can only have multiple keywords per uri (provided the post data changes), but not multiple uris per keyword.
I could also remove the keywords cache from the bookmarks service, since I figured we were already running a query for the post data, so at that point I just replaced the query.

This means I can now remove keywords support from Bookmarks.jsm and we can use the old keywords APIs until I introduce the new one. The 2 will already be able to work in parallel. So this speeds up the conversion.
Attachment #8560453 - Flags: review?(ttaubert)
Blocks: 1125118
Iteration: 38.2 - 9 Feb → 38.3 - 23 Feb
Iteration: 38.3 - 23 Feb → 39.1 - 9 Mar
Comment on attachment 8560453 [details] [diff] [review]
patch v1

Review of attachment 8560453 [details] [diff] [review]:
-----------------------------------------------------------------

Sorry again for the delay... Can you please post updates to this patch as interdiffs? Thanks :)

::: toolkit/components/places/Database.cpp
@@ +1529,5 @@
> +  // bookmarks uris, or multiple keywords could be associated to the same uri.
> +  // The new system only allows multiple uris per keyword, provided they have
> +  // a different post_data value.
> +  rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
> +    "INSERT OR REPLACE INTO moz_keywords (id, keyword, place_id, post_data) " 

Nit: trailing white space

::: toolkit/components/places/PlacesDBUtils.jsm
@@ +663,5 @@
>      let deleteUnusedKeywords = DBConn.createAsyncStatement(
>        `DELETE FROM moz_keywords WHERE id IN (
>           SELECT id FROM moz_keywords k
>           WHERE NOT EXISTS
> +           (SELECT 1 FROM moz_places h WHERE k.place_id = h.id)

Could we also do this with a trigger? When deleting a row from moz_places, couldn't we also remove all keywords for that place_id?

::: toolkit/components/places/PlacesUtils.jsm
@@ +815,5 @@
> +  setPostDataForBookmark(aBookmarkId, aPostData) {
> +    // For now we don't have a unified API to create a keyword with postData,
> +    // thus here we can just try to complete a keyword that should already exist
> +    // without any post data.
> +    let nullPostDataFragment = aPostData ? "AND post_data ISNULL" : "";

This doesn't quite seem to match the previous behavior as it doesn't allow us updating a bookmark's post data. Without the keyword to update we don't really know which one of the keywords we should update. Instead we seem to use a random matching keyword with post_data=NULL.

This probably confuses me because the old API doesn't at all fit in with the new DB scheme, like you hinted above in the comment. Did you choose to update keywords with post_data=null because we usually only use this function right after creating a new keyword?

@@ +819,5 @@
> +    let nullPostDataFragment = aPostData ? "AND post_data ISNULL" : "";
> +    let stmt = PlacesUtils.history.DBConnection.createStatement(
> +      `UPDATE moz_keywords SET post_data = :post_data
> +       WHERE place_id = (SELECT fk FROM moz_bookmarks WHERE id = :item_id)
> +      ${nullPostDataFragment}`);

With multiple keywords having post_data=null with the same place_id, will this try to update all of them and try to invalidate the (placed_id,post_data) unique constraint and fail? Maybe it doesn't, maybe we should add a LIMIT 1?

@@ +821,5 @@
> +      `UPDATE moz_keywords SET post_data = :post_data
> +       WHERE place_id = (SELECT fk FROM moz_bookmarks WHERE id = :item_id)
> +      ${nullPostDataFragment}`);
> +    stmt.params.item_id = aBookmarkId;
> +    stmt.params.post_data = aPostData;

Is aPostData=null correctly cast to SQL's NULL here? I'm not too familiar with that.

@@ +860,5 @@
>     * @returns an array containing a string URL and a string of POST data
>     */
> +  getURLAndPostDataForKeyword(aKeyword) {
> +    // In case of multiple urls associated to this keyword, we return the most
> +    // recently created keyword.

That comment seems stale? We have multiple keywords per URI only, right? So we'll only ever find a single URL for a given keyword.

@@ +872,5 @@
> +      if (!stmt.executeStep())
> +        return [ null, null ];
> +      return [ stmt.row.url, stmt.row.post_data ];
> +     }
> +     finally {

Nit: the indentation seems off a little.

::: toolkit/components/places/nsNavBookmarks.cpp
@@ +2243,2 @@
>  {
> +  // If there are no keywords for this URI, there's nothing to do. 

Nit: trailing white space

@@ +2274,5 @@
> +  nsTArray<BookmarkData> bookmarks;
> +  rv = GetBookmarksForURI(uri, bookmarks);
> +  NS_ENSURE_SUCCESS(rv, rv);
> +  if (bookmarks.Length() == 0) {
> +    for (uint32_t i = 0; i < keywords.Length(); ++i) {

Nit: int32_t

@@ +2402,4 @@
>      NS_ENSURE_SUCCESS(rv, rv);
> +
> +    bool hasMore;
> +    while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {

We only expect a single result here, no?

@@ +2422,3 @@
>      NS_ENSURE_SUCCESS(rv, rv);
> +    for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
> +      NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,

Don't we notify at the bottom of the function already?

@@ +2501,4 @@
>    NS_ENSURE_SUCCESS(rv, rv);
>  
> +  bool hasMore;
> +  while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {

Maybe if () ?

@@ +2540,4 @@
>    NS_ENSURE_SUCCESS(rv, rv);
>  
>    bool hasMore;
>    while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {

if () ? Or is there a reason we're using while everywhere?

::: toolkit/components/places/tests/bookmarks/test_keywords.js
@@ +25,5 @@
>  }
>  
> +function check_orphans() {
> + let db = yield PlacesUtils.promiseDBConnection();
> +  let rows = yield db.executeCached(

Nit: indentation is off.

@@ +45,4 @@
>  
> +      if (name.startsWith("onItemChanged")) {
> +        return () => {
> +          if (arguments[1] != "keyword")

Maybe declare the argument explicitly?

@@ +59,5 @@
> +      }
> +
> +      if (name in target)
> +        return target[name];
> +      return undefined;

return target[name]?
Attachment #8560453 - Flags: review?(ttaubert) → feedback+
(In reply to Tim Taubert [:ttaubert] from comment #2)
> ::: toolkit/components/places/PlacesDBUtils.jsm
> @@ +663,5 @@
> >      let deleteUnusedKeywords = DBConn.createAsyncStatement(
> >        `DELETE FROM moz_keywords WHERE id IN (
> >           SELECT id FROM moz_keywords k
> >           WHERE NOT EXISTS
> > +           (SELECT 1 FROM moz_places h WHERE k.place_id = h.id)
> 
> Could we also do this with a trigger? When deleting a row from moz_places,
> couldn't we also remove all keywords for that place_id?

Since keywords increase foreign_count and a page should never be removed unless it has foreign_count to zero, that would not be useful.
PlacesDBUtils exists only to cleanup mess caused by corruption (And soon or later we should rewrite it).

> ::: toolkit/components/places/PlacesUtils.jsm
> @@ +815,5 @@
> > +  setPostDataForBookmark(aBookmarkId, aPostData) {
> > +    // For now we don't have a unified API to create a keyword with postData,
> > +    // thus here we can just try to complete a keyword that should already exist
> > +    // without any post data.
> > +    let nullPostDataFragment = aPostData ? "AND post_data ISNULL" : "";
> 
> This doesn't quite seem to match the previous behavior as it doesn't allow
> us updating a bookmark's post data.

we don't have a ui to update post data. what we do currently is creating a bookmark, creating a keyword and then adding post data, all from a single ui.

Without the keyword to update we don't
> really know which one of the keywords we should update. Instead we seem to
> use a random matching keyword with post_data=NULL.

Yes, we assume we created a keyword without post data, and now we are setting post data for it.
Note that all of this code is going away, the new API will set a keyword with or without post data, and won't allow changing that.

> This probably confuses me because the old API doesn't at all fit in with the
> new DB scheme, like you hinted above in the comment. Did you choose to
> update keywords with post_data=null because we usually only use this
> function right after creating a new keyword?

That's right, post data was added after keywords feature, as an annotation, so they had separate APIs and one had to create a keyword without post data and then add it.

> @@ +819,5 @@
> > +    let nullPostDataFragment = aPostData ? "AND post_data ISNULL" : "";
> > +    let stmt = PlacesUtils.history.DBConnection.createStatement(
> > +      `UPDATE moz_keywords SET post_data = :post_data
> > +       WHERE place_id = (SELECT fk FROM moz_bookmarks WHERE id = :item_id)
> > +      ${nullPostDataFragment}`);
> 
> With multiple keywords having post_data=null with the same place_id, will
> this try to update all of them and try to invalidate the
> (placed_id,post_data) unique constraint and fail? Maybe it doesn't, maybe we
> should add a LIMIT 1?

We don't support LIMIT in UPDATE queries.
But you are right, if multiple keywords to the same page exist without post data, this will throw.
The new API won't have this issue cause it creates keywords with post data already (clearly it will still throw if one tries to create a duped keyword/pd tuple).
I will see what I can do, I guess I can probably limit 1 in a subquery, and filter by id.

> @@ +821,5 @@
> > +      `UPDATE moz_keywords SET post_data = :post_data
> > +       WHERE place_id = (SELECT fk FROM moz_bookmarks WHERE id = :item_id)
> > +      ${nullPostDataFragment}`);
> > +    stmt.params.item_id = aBookmarkId;
> > +    stmt.params.post_data = aPostData;
> 
> Is aPostData=null correctly cast to SQL's NULL here? I'm not too familiar
> with that.

yes, null is properly converted to NULL.

> @@ +860,5 @@
> >     * @returns an array containing a string URL and a string of POST data
> >     */
> > +  getURLAndPostDataForKeyword(aKeyword) {
> > +    // In case of multiple urls associated to this keyword, we return the most
> > +    // recently created keyword.
> 
> That comment seems stale? We have multiple keywords per URI only, right? So
> we'll only ever find a single URL for a given keyword.

oh yeah.

> ::: toolkit/components/places/nsNavBookmarks.cpp
> @@ +2274,5 @@
> > +  nsTArray<BookmarkData> bookmarks;
> > +  rv = GetBookmarksForURI(uri, bookmarks);
> > +  NS_ENSURE_SUCCESS(rv, rv);
> > +  if (bookmarks.Length() == 0) {
> > +    for (uint32_t i = 0; i < keywords.Length(); ++i) {
> 
> Nit: int32_t

nsTArray Length is unsigned, iirc, using int32_t would warn and fail on warning.

> @@ +2402,4 @@
> >      NS_ENSURE_SUCCESS(rv, rv);
> > +
> > +    bool hasMore;
> > +    while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
> 
> We only expect a single result here, no?

yes

> @@ +2422,3 @@
> >      NS_ENSURE_SUCCESS(rv, rv);
> > +    for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
> > +      NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
> 
> Don't we notify at the bottom of the function already?

here we are notifying that a keyword is being removed (notice EmptyCString()) while below we notify an addition.

> @@ +2540,4 @@
> >    NS_ENSURE_SUCCESS(rv, rv);
> >  
> >    bool hasMore;
> >    while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
> 
> if () ? Or is there a reason we're using while everywhere?

I think it was just bad habits...
Posted patch interdiff (obsolete) — Splinter Review
Attachment #8573466 - Flags: review?(ttaubert)
Posted patch patch v2Splinter Review
merged patch
Attachment #8560453 - Attachment is obsolete: true
Attachment #8573468 - Flags: review?(ttaubert)
Duplicate of this bug: 1125118
Blocks: 1140395
No longer blocks: placesAsyncBookmarks
Attachment #8573466 - Flags: review?(ttaubert) → review+
Comment on attachment 8573468 [details] [diff] [review]
patch v2

Review of attachment 8573468 [details] [diff] [review]:
-----------------------------------------------------------------

r+ because v1 + interdiff lgtm.
Attachment #8573468 - Flags: review?(ttaubert) → review+
Iteration: 39.1 - 9 Mar → 39.2 - 23 Mar
Attachment #8573466 - Attachment is obsolete: true
https://hg.mozilla.org/integration/fx-team/rev/687b6a735b34
Flags: in-testsuite? → in-testsuite+
Target Milestone: --- → mozilla39
https://hg.mozilla.org/mozilla-central/rev/687b6a735b34
Status: ASSIGNED → RESOLVED
Last Resolved: 4 years ago
Resolution: --- → FIXED
Depends on: 1146253
Depends on: 1146299
Depends on: 1146461

Updated

4 years ago
Depends on: 1150678

Updated

4 years ago
Depends on: 1167915
Depends on: 1180419
Depends on: 1227374

Updated

2 years ago
Depends on: 1332305
You need to log in before you can comment on or make changes to this bug.