zotero/chrome/content/zotero/xpcom/id.js
Dan Stillman fabc2ba6a2 Always start at MAX() + 1 for Zotero.ID.get(), and deasyncify
Instead of getting batches of unused primary key ids, even if they're lower
than other ids, which for some reason seemed like a good idea in 2008, just do
a `MAX()` on the table at startup and return the next available id on each call
to `Zotero.ID.get()`. This is much simpler, and not reusing ids allows them to
be used as a chronological sort field.

While SQLite's `SELECT last_insert_rowid()` could return auto-increment values,
it's unsafe with async DB access, since a second `INSERT` can come in before
the first `last_insert_rowid()` is called. This is true even in a transaction
unless a function that calls it is never called in parallel (e.g., with
`Zotero.Promise.all()`, which can be faster than sequential `yield`s).

Note that the next id is always initialized as MAX() + 1, so if an object is
added and then deleted, after a restart the same id will be given. (This is
equivalent to (though unrelated to) SQLite's `INTEGER PRIMARY KEY` behavior,
as opposed to its `INTEGER PRIMARY KEY AUTOINCREMENT` behavior.)

Closes #993, Feed items out of order
2016-03-30 01:39:43 -04:00

96 lines
2.2 KiB
JavaScript

/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2016 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
https://www.zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
Zotero.ID_Tracker = function () {
var _tables = [
'collections',
'creators',
'customFields',
'customItemTypes',
'itemDataValues',
'items',
'libraries',
'proxies',
'savedSearches',
'tags'
];
var _nextIDs = {};
this.init = Zotero.Promise.coroutine(function* () {
for (let table of _tables) {
_nextIDs[table] = yield _getNext(table);
}
});
/**
* Gets an unused primary key id for a DB table
*/
this.get = function (table) {
if (!_nextIDs[table]) {
throw new Error("IDs not loaded for table '" + table + "'");
}
return ++_nextIDs[table];
};
function _getTableColumn(table) {
switch (table) {
case 'libraries':
return 'libraryID';
case 'itemDataValues':
return 'valueID';
case 'savedSearches':
return 'savedSearchID';
case 'creatorData':
return 'creatorDataID';
case 'proxies':
return 'proxyID';
default:
return table.substr(0, table.length - 1) + 'ID';
}
}
/**
* Get MAX(id) + 1 from table
*
* @return {Promise<Integer>}
*/
function _getNext(table) {
var sql = 'SELECT COALESCE(MAX(' + _getTableColumn(table) + ') + 1, 1) FROM ' + table;
return Zotero.DB.valueQueryAsync(sql);
};
}
Zotero.ID = new Zotero.ID_Tracker;