forked from mirrors/gecko-dev
Bug 1884308 - Chunk bookmark GUIDs when retrieving details to avoid going over Sqlite variables limit. r=mconley,places-reviewers,Standard8
Differential Revision: https://phabricator.services.mozilla.com/D204660
This commit is contained in:
parent
27ecbe8d71
commit
5a15e88f6a
8 changed files with 100 additions and 41 deletions
5
dom/cache/Connection.cpp
vendored
5
dom/cache/Connection.cpp
vendored
|
|
@ -226,6 +226,11 @@ Connection::GetVariableLimit(int32_t* aResultOut) {
|
|||
return mBase->GetVariableLimit(aResultOut);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Connection::SetVariableLimit(int32_t aLimit) {
|
||||
return mBase->SetVariableLimit(aLimit);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Connection::BeginTransaction() { return mBase->BeginTransaction(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -70,8 +70,12 @@ interface mozIStorageAsyncConnection : nsISupports {
|
|||
*
|
||||
* If you bind more params than this limit, `create{Async}Statement` will
|
||||
* fail with a "too many SQL variables" error.
|
||||
* It's possible to lower the limit, but it's not possible to set it higher
|
||||
* than the default value, passing an higher value will silently truncate to
|
||||
* the default. Lowering the limit is particularly useful for testing code
|
||||
* that may bind many variables.
|
||||
*/
|
||||
readonly attribute int32_t variableLimit;
|
||||
attribute int32_t variableLimit;
|
||||
|
||||
/**
|
||||
* Returns true if a transaction is active on this connection.
|
||||
|
|
|
|||
|
|
@ -2468,6 +2468,18 @@ Connection::GetVariableLimit(int32_t* _limit) {
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Connection::SetVariableLimit(int32_t limit) {
|
||||
if (!connectionReady()) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
int oldLimit = ::sqlite3_limit(mDBConn, SQLITE_LIMIT_VARIABLE_NUMBER, limit);
|
||||
if (oldLimit < 0) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Connection::BeginTransaction() {
|
||||
if (!connectionReady()) {
|
||||
|
|
|
|||
|
|
@ -980,6 +980,10 @@ add_task(async function test_variableLimit() {
|
|||
info("Open connection");
|
||||
let db = Services.storage.openDatabase(getTestDB());
|
||||
Assert.equal(db.variableLimit, 32766, "Should return default limit");
|
||||
db.variableLimit = 999;
|
||||
Assert.equal(db.variableLimit, 999, "Should return the set limit");
|
||||
db.variableLimit = 33000;
|
||||
Assert.equal(db.variableLimit, 32766, "Should silently truncate");
|
||||
await asyncClose(db);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3335,42 +3335,38 @@ async function retrieveFullBookmarkPath(guid, options = {}) {
|
|||
*/
|
||||
async function getBookmarkDetailMap(aGuids) {
|
||||
return lazy.PlacesUtils.withConnectionWrapper(
|
||||
"Bookmarks.geBookmarkDetails",
|
||||
"Bookmarks.geBookmarkDetailMap",
|
||||
async db => {
|
||||
const rows = await db.executeCached(
|
||||
`
|
||||
SELECT
|
||||
b.guid,
|
||||
b.id,
|
||||
b.parent,
|
||||
IFNULL(h.frecency, 0),
|
||||
IFNULL(h.hidden, 0),
|
||||
IFNULL(h.visit_count, 0),
|
||||
h.last_visit_date,
|
||||
(
|
||||
SELECT group_concat(pp.title ORDER BY pp.title)
|
||||
FROM moz_bookmarks bb
|
||||
JOIN moz_bookmarks pp ON pp.id = bb.parent
|
||||
JOIN moz_bookmarks gg ON gg.id = pp.parent
|
||||
WHERE bb.fk = h.id
|
||||
AND gg.guid = '${Bookmarks.tagsGuid}'
|
||||
),
|
||||
t.guid, t.id, t.title
|
||||
FROM moz_bookmarks b
|
||||
LEFT JOIN moz_places h ON h.id = b.fk
|
||||
LEFT JOIN moz_bookmarks t ON t.guid = target_folder_guid(h.url)
|
||||
WHERE b.guid IN (${lazy.PlacesUtils.sqlBindPlaceholders(aGuids)})
|
||||
`,
|
||||
aGuids
|
||||
);
|
||||
|
||||
return new Map(
|
||||
rows.map(row => {
|
||||
const lastVisitDate = row.getResultByIndex(6);
|
||||
|
||||
return [
|
||||
row.getResultByIndex(0),
|
||||
{
|
||||
let entries = new Map();
|
||||
for (let chunk of lazy.PlacesUtils.chunkArray(aGuids, db.variableLimit)) {
|
||||
await db.executeCached(
|
||||
`
|
||||
SELECT
|
||||
b.guid,
|
||||
b.id,
|
||||
b.parent,
|
||||
IFNULL(h.frecency, 0),
|
||||
IFNULL(h.hidden, 0),
|
||||
IFNULL(h.visit_count, 0),
|
||||
h.last_visit_date,
|
||||
(
|
||||
SELECT group_concat(pp.title ORDER BY pp.title)
|
||||
FROM moz_bookmarks bb
|
||||
JOIN moz_bookmarks pp ON pp.id = bb.parent
|
||||
JOIN moz_bookmarks gg ON gg.id = pp.parent
|
||||
WHERE bb.fk = h.id
|
||||
AND gg.guid = '${Bookmarks.tagsGuid}'
|
||||
),
|
||||
t.guid, t.id, t.title
|
||||
FROM moz_bookmarks b
|
||||
LEFT JOIN moz_places h ON h.id = b.fk
|
||||
LEFT JOIN moz_bookmarks t ON t.guid = target_folder_guid(h.url)
|
||||
WHERE b.guid IN (${lazy.PlacesUtils.sqlBindPlaceholders(chunk)})
|
||||
`,
|
||||
chunk,
|
||||
row => {
|
||||
const lastVisitDate = row.getResultByIndex(6);
|
||||
entries.set(row.getResultByIndex(0), {
|
||||
id: row.getResultByIndex(1),
|
||||
parentId: row.getResultByIndex(2),
|
||||
frecency: row.getResultByIndex(3),
|
||||
|
|
@ -3383,10 +3379,11 @@ async function getBookmarkDetailMap(aGuids) {
|
|||
targetFolderGuid: row.getResultByIndex(8),
|
||||
targetFolderItemId: row.getResultByIndex(9),
|
||||
targetFolderTitle: row.getResultByIndex(10),
|
||||
},
|
||||
];
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test `insertTree()` with more bookmarks than the Sqlite variables limit.
|
||||
|
||||
add_task(async function () {
|
||||
const NUM_BOOKMARKS = 1000;
|
||||
await PlacesUtils.withConnectionWrapper("test", async db => {
|
||||
db.variableLimit = NUM_BOOKMARKS - 100;
|
||||
Assert.greater(
|
||||
NUM_BOOKMARKS,
|
||||
db.variableLimit,
|
||||
"Insert more bookmarks than the Sqlite variables limit."
|
||||
);
|
||||
});
|
||||
let children = [];
|
||||
for (let i = 0; i < NUM_BOOKMARKS; ++i) {
|
||||
children.push({ url: "http://www.mozilla.org/" + i });
|
||||
}
|
||||
await PlacesUtils.bookmarks.insertTree({
|
||||
guid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
children,
|
||||
});
|
||||
});
|
||||
|
|
@ -70,6 +70,8 @@ support-files = ["bookmarks_long_tag.json"]
|
|||
|
||||
["test_bookmarks_update.js"]
|
||||
|
||||
["test_insert_thousands_bookmarks.js"]
|
||||
|
||||
["test_insertTree_fixupOrSkipInvalidEntries.js"]
|
||||
|
||||
["test_keywords.js"]
|
||||
|
|
|
|||
|
|
@ -1688,12 +1688,23 @@ OpenedConnection.prototype = Object.freeze({
|
|||
* Returns the maximum number of bound parameters for statements executed
|
||||
* on this connection.
|
||||
*
|
||||
* @type {number}
|
||||
* @returns {number} The bound parameters limit.
|
||||
*/
|
||||
get variableLimit() {
|
||||
return this.unsafeRawConnection.variableLimit;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the the maximum number of bound parameters for statements executed
|
||||
* on this connection. If the passed-in value is higher than the maximum
|
||||
* default value, it will be silently truncated.
|
||||
*
|
||||
* @param {number} newLimit The bound parameters limit.
|
||||
*/
|
||||
set variableLimit(newLimit) {
|
||||
this.unsafeRawConnection.variableLimit = newLimit;
|
||||
},
|
||||
|
||||
/**
|
||||
* The integer schema version of the database.
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in a new issue