Keep track of manually set ids (via a Notifier observer watching for 'add' events or manual Zotero.ID.skip() calls) so that subsequent calls to Zotero.ID.get() don't return them
This should fix hard-to-reproduce 'constraint failed' errors during syncing.
This commit is contained in:
parent
a610595b84
commit
d65e75fbc9
2 changed files with 115 additions and 23 deletions
|
@ -24,18 +24,19 @@ Zotero.ID = new function () {
|
||||||
this.get = get;
|
this.get = get;
|
||||||
this.getKey = getKey;
|
this.getKey = getKey;
|
||||||
this.getBigInt = getBigInt;
|
this.getBigInt = getBigInt;
|
||||||
|
this.skip = skip;
|
||||||
|
this.getTableName = getTableName;
|
||||||
|
|
||||||
_available = {};
|
_available = {};
|
||||||
_min = {};
|
_min = {};
|
||||||
|
_skip = {};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Gets an unused primary key id for a DB table
|
* Gets an unused primary key id for a DB table
|
||||||
*/
|
*/
|
||||||
function get(table, notNull, skip) {
|
function get(table, notNull) {
|
||||||
// Used in sync.js
|
table = this.getTableName(table);
|
||||||
if (table == 'searches') {
|
|
||||||
table = 'savedSearches';
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (table) {
|
switch (table) {
|
||||||
// Autoincrement tables
|
// Autoincrement tables
|
||||||
|
@ -48,9 +49,9 @@ Zotero.ID = new function () {
|
||||||
case 'collections':
|
case 'collections':
|
||||||
case 'savedSearches':
|
case 'savedSearches':
|
||||||
case 'tags':
|
case 'tags':
|
||||||
var id = _getNextAvailable(table, skip);
|
var id = _getNextAvailable(table);
|
||||||
if (!id && notNull) {
|
if (!id && notNull) {
|
||||||
return _getNext(table, skip);
|
return _getNext(table);
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
|
|
||||||
|
@ -58,10 +59,10 @@ Zotero.ID = new function () {
|
||||||
//
|
//
|
||||||
// TODO: use autoincrement instead where available in 1.5
|
// TODO: use autoincrement instead where available in 1.5
|
||||||
case 'itemDataValues':
|
case 'itemDataValues':
|
||||||
var id = _getNextAvailable(table, skip);
|
var id = _getNextAvailable(table);
|
||||||
if (!id) {
|
if (!id) {
|
||||||
// If we can't find an empty id quickly, just use MAX() + 1
|
// If we can't find an empty id quickly, just use MAX() + 1
|
||||||
return _getNext(table, skip);
|
return _getNext(table);
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
|
|
||||||
|
@ -82,10 +83,67 @@ Zotero.ID = new function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark ids as used
|
||||||
|
*
|
||||||
|
* @param string table
|
||||||
|
* @param int|array ids Item ids to skip
|
||||||
|
*/
|
||||||
|
function skip(table, ids) {
|
||||||
|
table = this.getTableName(table);
|
||||||
|
|
||||||
|
switch (ids.constructor.name) {
|
||||||
|
case 'Array':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Number':
|
||||||
|
ids = [ids];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw ("ids must be an int or array of ints in Zotero.ID.skip()");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ids.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_skip[table]) {
|
||||||
|
_skip[table] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i=0, len=ids.length; i<len; i++) {
|
||||||
|
_skip[table][ids[i]] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getTableName(table) {
|
||||||
|
// Used in sync.js
|
||||||
|
if (table == 'searches') {
|
||||||
|
table = 'savedSearches';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (table) {
|
||||||
|
case 'collections':
|
||||||
|
case 'creators':
|
||||||
|
case 'creatorData':
|
||||||
|
case 'itemDataValues':
|
||||||
|
case 'items':
|
||||||
|
case 'savedSearches':
|
||||||
|
case 'tags':
|
||||||
|
return table;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw ("Invalid table '" + table + "' in Zotero.ID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the lowest available unused primary key id for table
|
* Returns the lowest available unused primary key id for table
|
||||||
*/
|
*/
|
||||||
function _getNextAvailable(table, skip) {
|
function _getNextAvailable(table) {
|
||||||
if (!_available[table]) {
|
if (!_available[table]) {
|
||||||
_loadAvailable(table);
|
_loadAvailable(table);
|
||||||
}
|
}
|
||||||
|
@ -95,7 +153,7 @@ Zotero.ID = new function () {
|
||||||
for (var i in arr) {
|
for (var i in arr) {
|
||||||
var id = arr[i][0];
|
var id = arr[i][0];
|
||||||
|
|
||||||
if (skip && skip.indexOf(id) != -1) {
|
if (_skip[table] && _skip[table][id]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,12 +181,20 @@ Zotero.ID = new function () {
|
||||||
/*
|
/*
|
||||||
* Get MAX(id) + 1 from table
|
* Get MAX(id) + 1 from table
|
||||||
*/
|
*/
|
||||||
function _getNext(table, skip) {
|
function _getNext(table) {
|
||||||
var column = _getTableColumn(table);
|
var column = _getTableColumn(table);
|
||||||
|
|
||||||
var sql = 'SELECT MAX(';
|
var sql = 'SELECT MAX(';
|
||||||
if (skip && skip.length) {
|
if (_skip[table]) {
|
||||||
var max = Math.max.apply(this, skip);
|
var max = 0;
|
||||||
|
for (var id in _skip[table]) {
|
||||||
|
if (id > max) {
|
||||||
|
max = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!max) {
|
||||||
|
throw ("_skip['" + table + "'] must contain positive values in Zotero.ID._getNext()");
|
||||||
|
}
|
||||||
sql += 'MAX(' + column + ', ' + max + ')';
|
sql += 'MAX(' + column + ', ' + max + ')';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -287,3 +353,31 @@ Zotero.ID = new function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifier observer to mark saved object ids as used
|
||||||
|
*/
|
||||||
|
Zotero.ID.EventListener = new function () {
|
||||||
|
this.init = init;
|
||||||
|
this.notify = notify;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
Zotero.Notifier.registerObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function notify(event, type, ids) {
|
||||||
|
if (event == 'add') {
|
||||||
|
try {
|
||||||
|
var table = Zotero.ID.getTableName(type);
|
||||||
|
}
|
||||||
|
// Skip if not a table we handle
|
||||||
|
catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Zotero.ID.skip(table, ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1189,15 +1189,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
else {
|
else {
|
||||||
var oldID = parseInt(xmlNode.@id);
|
var oldID = parseInt(xmlNode.@id);
|
||||||
|
|
||||||
// Don't use assigned-but-unsaved ids for the new id
|
var newID = Zotero.ID.get(types, true);
|
||||||
var skip = [];
|
|
||||||
for each(var o in toSaveParents) {
|
|
||||||
skip.push(o.id);
|
|
||||||
}
|
|
||||||
for each(var o in toSaveChildren) {
|
|
||||||
skip.push(o.id);
|
|
||||||
}
|
|
||||||
var newID = Zotero.ID.get(types, true, skip);
|
|
||||||
|
|
||||||
Zotero.debug("Changing " + type + " " + oldID + " id to " + newID);
|
Zotero.debug("Changing " + type + " " + oldID + " id to " + newID);
|
||||||
|
|
||||||
|
@ -1289,6 +1281,9 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
else {
|
else {
|
||||||
toSaveParents.push(obj);
|
toSaveParents.push(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't use assigned-but-unsaved ids for new ids
|
||||||
|
Zotero.ID.skip(types, obj.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1378,6 +1373,9 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
toSaveChildren.push(obj.ref);
|
toSaveChildren.push(obj.ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't use assigned-but-unsaved ids for new ids
|
||||||
|
Zotero.ID.skip(types, obj.id);
|
||||||
|
|
||||||
// Item had been deleted locally, so remove from
|
// Item had been deleted locally, so remove from
|
||||||
// deleted array
|
// deleted array
|
||||||
if (obj.left == 'deleted') {
|
if (obj.left == 'deleted') {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue