
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
96 lines
2.2 KiB
JavaScript
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;
|