- Added Zotero.ID.get(table) method for getting a primary key id -- it first tries to find the lowest unused integer in the PK column and falls back to using MAX() + 1 if it can't find one quickly
- Removed Zotero.getRandomID() (moved into Zotero.ID, but unused) - Purge itemDataValues on item delete
This commit is contained in:
parent
7236aaf58e
commit
d92e3661d5
4 changed files with 263 additions and 56 deletions
|
@ -877,7 +877,7 @@ Zotero.Item.prototype.save = function(){
|
|||
valueStatement.reset();
|
||||
|
||||
if (!valueID) {
|
||||
valueID = Zotero.getRandomID('itemDataValues', 'valueID', 2097152); // stored in 3 bytes
|
||||
valueID = Zotero.ID.get('itemDataValues');
|
||||
insertStatement.bindInt32Parameter(0, valueID);
|
||||
|
||||
if (Zotero.ItemFields.getID('accessDate') == fieldID
|
||||
|
@ -983,9 +983,12 @@ Zotero.Item.prototype.save = function(){
|
|||
//
|
||||
// Primary fields
|
||||
//
|
||||
sqlColumns.push('itemID');
|
||||
var itemID = Zotero.getRandomID('items', 'itemID');
|
||||
sqlValues.push(itemID);
|
||||
var itemID = Zotero.ID.get('items');
|
||||
// If available id value, use it -- otherwise we'll use autoincrement
|
||||
if (itemID) {
|
||||
sqlColumns.push('itemID');
|
||||
sqlValues.push(itemID);
|
||||
}
|
||||
|
||||
sqlColumns.push('itemTypeID');
|
||||
sqlValues.push({'int':this.getField('itemTypeID')});
|
||||
|
@ -1009,7 +1012,10 @@ Zotero.Item.prototype.save = function(){
|
|||
sql = sql.substring(0,sql.length-1) + ")";
|
||||
|
||||
// Save basic data to items table
|
||||
Zotero.DB.query(sql, sqlValues);
|
||||
var lastInsertID = Zotero.DB.query(sql, sqlValues);
|
||||
if (!itemID) {
|
||||
itemID = lastInsertID;
|
||||
}
|
||||
this._data['itemID'] = itemID;
|
||||
|
||||
Zotero.History.setAssociatedID(itemID);
|
||||
|
@ -1045,7 +1051,7 @@ Zotero.Item.prototype.save = function(){
|
|||
valueStatement.reset();
|
||||
|
||||
if (!valueID) {
|
||||
valueID = Zotero.getRandomID('itemDataValues', 'valueID', 2097152); // stored in 3 bytes
|
||||
valueID = Zotero.ID.get('itemDataValues');
|
||||
insertValueStatement.bindInt32Parameter(0, valueID);
|
||||
|
||||
if (Zotero.ItemFields.getID('accessDate') == fieldID
|
||||
|
@ -2034,7 +2040,7 @@ Zotero.Item.prototype.removeTag = function(tagID){
|
|||
|
||||
Zotero.DB.beginTransaction();
|
||||
var sql = "DELETE FROM itemTags WHERE itemID=? AND tagID=?";
|
||||
Zotero.DB.query(sql, [this.getID(), tagID]);
|
||||
Zotero.DB.query(sql, [this.getID(), { int: tagID }]);
|
||||
Zotero.Tags.purge();
|
||||
Zotero.DB.commitTransaction();
|
||||
Zotero.Notifier.trigger('modify', 'item', this.getID());
|
||||
|
@ -2956,7 +2962,11 @@ Zotero.Items = new function(){
|
|||
Zotero.Creators.purge();
|
||||
Zotero.Tags.purge();
|
||||
Zotero.Fulltext.purgeUnusedWords();
|
||||
// TODO: purge itemDataValues?
|
||||
|
||||
// Purge unused values
|
||||
var sql = "DELETE FROM itemDataValues WHERE valueID NOT IN "
|
||||
+ "(SELECT valueID FROM itemData)";
|
||||
Zotero.DB.query(sql);
|
||||
}
|
||||
|
||||
|
||||
|
@ -3695,7 +3705,7 @@ Zotero.Collections = new function(){
|
|||
|
||||
var parentParam = parent ? {'int':parent} : {'null':true};
|
||||
|
||||
var rnd = Zotero.getRandomID('collections', 'collectionID');
|
||||
var rnd = Zotero.ID.get('collections');
|
||||
|
||||
var sql = "INSERT INTO collections VALUES (?,?,?)";
|
||||
var sqlValues = [ {'int':rnd}, {'string':name}, parentParam ];
|
||||
|
@ -3882,7 +3892,7 @@ Zotero.Creators = new function(){
|
|||
Zotero.DB.beginTransaction();
|
||||
|
||||
var sql = 'INSERT INTO creators VALUES (?,?,?,?)';
|
||||
var rnd = Zotero.getRandomID('creators', 'creatorID');
|
||||
var rnd = Zotero.ID.get('creators');
|
||||
var params = [
|
||||
rnd, fieldMode ? '' : {string: firstName}, {string: lastName},
|
||||
fieldMode ? 1 : 0
|
||||
|
@ -4161,7 +4171,7 @@ Zotero.Tags = new function(){
|
|||
Zotero.DB.beginTransaction();
|
||||
|
||||
var sql = 'INSERT INTO tags VALUES (?,?,?)';
|
||||
var rnd = Zotero.getRandomID('tags', 'tagID');
|
||||
var rnd = Zotero.ID.get('tags');
|
||||
Zotero.DB.query(sql, [{int: rnd}, {string: tag}, {int: type}]);
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
|
@ -4182,6 +4192,7 @@ Zotero.Tags = new function(){
|
|||
notifierData[this.id] = { old: this.toArray() };
|
||||
|
||||
if (oldName == tag) {
|
||||
// Convert unchanged automatic tags to manual
|
||||
if (oldType != 0) {
|
||||
var sql = "UPDATE tags SET tagType=0 WHERE tagID=?";
|
||||
Zotero.DB.query(sql, tagID);
|
||||
|
@ -4947,6 +4958,238 @@ Zotero.ItemFields = new function(){
|
|||
}
|
||||
|
||||
|
||||
Zotero.ID = new function () {
|
||||
this.get = get;
|
||||
|
||||
_available = {};
|
||||
|
||||
/*
|
||||
* Gets an unused primary key id for a DB table
|
||||
*/
|
||||
function get(table, notNull) {
|
||||
switch (table) {
|
||||
// Autoincrement tables
|
||||
//
|
||||
// Callers need to handle a potential NULL for these unless they
|
||||
// pass |notNull|
|
||||
case 'items':
|
||||
var id = _getNextAvailable(table);
|
||||
if (!id && notNull) {
|
||||
return _getNext(table);
|
||||
}
|
||||
return id;
|
||||
|
||||
// Non-autoincrement tables
|
||||
//
|
||||
// TODO: use autoincrement instead where available in 1.5
|
||||
case 'creators':
|
||||
case 'collections':
|
||||
case 'itemDataValues':
|
||||
case 'savedSearches':
|
||||
case 'tags':
|
||||
var id = _getNextAvailable(table);
|
||||
if (!id) {
|
||||
// If we can't find an empty id quickly, just use MAX() + 1
|
||||
return _getNext(table);
|
||||
}
|
||||
return id;
|
||||
|
||||
default:
|
||||
throw ("Unsupported table '" + table + "' in Zotero.ID.get()");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Returns the lowest available unused primary key id for table
|
||||
*/
|
||||
function _getNextAvailable(table) {
|
||||
if (!_available[table]) {
|
||||
_loadAvailable(table);
|
||||
}
|
||||
|
||||
var arr = _available[table];
|
||||
|
||||
for (var i in arr) {
|
||||
var id = arr[i][0];
|
||||
// End of range -- remove range
|
||||
if (id == arr[i][1]) {
|
||||
arr.shift();
|
||||
}
|
||||
// Within range -- increment
|
||||
else {
|
||||
arr[i][0]++;
|
||||
}
|
||||
|
||||
// Prepare table for refresh if all rows used
|
||||
if (arr.length == 0) {
|
||||
delete _available[table];
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get MAX(id) + 1 from table
|
||||
*/
|
||||
function _getNext(table) {
|
||||
var column = _getTableColumn(table);
|
||||
var sql = 'SELECT MAX(' + column + ') + 1 FROM ' + table;
|
||||
return Zotero.DB.valueQuery(sql);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Loads available ids for table into memory
|
||||
*/
|
||||
function _loadAvailable(table) {
|
||||
Zotero.debug("Loading available ids for table '" + table + "'");
|
||||
|
||||
var numIDs = 3; // Number of ids to compare against at a time
|
||||
var maxTries = 3; // Number of times to try increasing the maxID
|
||||
var maxToFind = 1000;
|
||||
|
||||
var column = _getTableColumn(table);
|
||||
|
||||
switch (table) {
|
||||
case 'creators':
|
||||
case 'items':
|
||||
case 'itemDataValues':
|
||||
case 'tags':
|
||||
break;
|
||||
|
||||
case 'collections':
|
||||
case 'savedSearches':
|
||||
var maxToFind = 100;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Unsupported table '" + table + "' in Zotero.ID._loadAvailable()");
|
||||
}
|
||||
|
||||
var maxID = numIDs;
|
||||
var sql = "SELECT " + column + " FROM " + table
|
||||
+ " WHERE " + column + "<=? ORDER BY " + column;
|
||||
var ids = Zotero.DB.columnQuery(sql, maxID);
|
||||
// If no ids found, we have maxID unused ids
|
||||
if (!ids) {
|
||||
Zotero.debug('none found');
|
||||
var found = Math.min(maxID, maxToFind);
|
||||
Zotero.debug("Found " + found + " available ids in table '" + table + "'");
|
||||
_available[table] = [[1, found]];
|
||||
return;
|
||||
}
|
||||
|
||||
// If we didn't find any unused ids, try increasing maxID a few times
|
||||
while (ids.length == maxID && maxTries>0) {
|
||||
Zotero.debug('nope');
|
||||
maxID = maxID + numIDs;
|
||||
ids = Zotero.DB.columnQuery(sql, maxID);
|
||||
maxTries--;
|
||||
}
|
||||
|
||||
// Didn't find any unused ids
|
||||
if (ids.length == maxID) {
|
||||
Zotero.debug('none!');
|
||||
Zotero.debug("Found 0 available ids in table '" + table + "'");
|
||||
_available[table] = [];
|
||||
return;
|
||||
}
|
||||
|
||||
var available = [], found = 0, j=0, availableStart = null;
|
||||
|
||||
for (var i=1; i<=maxID && found<maxToFind; i++) {
|
||||
// We've gone past the found ids, so all remaining ids up to maxID
|
||||
// are available
|
||||
if (!ids[j]) {
|
||||
Zotero.debug('all remaining are available');
|
||||
available.push([i, maxID]);
|
||||
found += (maxID - i) + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip ahead while ids are occupied
|
||||
if (ids[j] == i) {
|
||||
Zotero.debug('skipping');
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Advance counter while it's below the next used id
|
||||
while (ids[j] > i && i<=maxID) {
|
||||
Zotero.debug('b');
|
||||
if (!availableStart) {
|
||||
availableStart = i;
|
||||
}
|
||||
i++;
|
||||
|
||||
if ((found + (i - availableStart) + 1) > maxToFind) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (availableStart) {
|
||||
available.push([availableStart, i-1]);
|
||||
// Keep track of how many empties we've found
|
||||
found += ((i-1) - availableStart) + 1;
|
||||
availableStart = null;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
Zotero.debug("Found " + found + " available ids in table '" + table + "'");
|
||||
|
||||
_available[table] = available;
|
||||
Zotero.debug(available);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a unique random id for use in a DB table
|
||||
*
|
||||
* (No longer used)
|
||||
**/
|
||||
function _getRandomID(table, max){
|
||||
var column = _getTableColumn(table);
|
||||
|
||||
var sql = 'SELECT COUNT(*) FROM ' + table + ' WHERE ' + column + '= ?';
|
||||
|
||||
if (!max){
|
||||
max = 16383;
|
||||
}
|
||||
|
||||
max--; // since we use ceil(), decrement max by 1
|
||||
var tries = 3; // # of tries to find a unique id
|
||||
for (var i=0; i<tries; i++) {
|
||||
var rnd = Math.ceil(Math.random() * max);
|
||||
var exists = Zotero.DB.valueQuery(sql, { int: rnd });
|
||||
if (!exists) {
|
||||
return rnd;
|
||||
}
|
||||
}
|
||||
|
||||
// If no luck after number of tries, try a larger range
|
||||
var sql = 'SELECT MAX(' + column + ') + 1 FROM ' + table;
|
||||
return Zotero.valueQuery(sql);
|
||||
}
|
||||
|
||||
|
||||
function _getTableColumn(table) {
|
||||
switch (table) {
|
||||
case 'itemDataValues':
|
||||
return 'valueID';
|
||||
|
||||
case 'savedSearches':
|
||||
return 'savedSearchID';
|
||||
|
||||
default:
|
||||
return table.substr(0, table.length - 1) + 'ID';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -916,7 +916,7 @@ Zotero.Schema = new function(){
|
|||
var values = Zotero.DB.columnQuery("SELECT DISTINCT value FROM itemData");
|
||||
if (values) {
|
||||
for (var j=0; j<values.length; j++) {
|
||||
var valueID = Zotero.getRandomID('itemDataValues', 'valueID', 2097152); // Stored in 3 bytes
|
||||
var valueID = Zotero.ID.get('itemDataValues');
|
||||
Zotero.DB.query("INSERT INTO itemDataValues VALUES (?,?)", [valueID, values[j]]);
|
||||
}
|
||||
}
|
||||
|
@ -935,7 +935,7 @@ Zotero.Schema = new function(){
|
|||
if (rows) {
|
||||
for (var j=0; j<rows.length; j++) {
|
||||
for (var j=0; j<values.length; j++) {
|
||||
var valueID = Zotero.getRandomID('itemDataValues', 'valueID', 2097152); // Stored in 3 bytes
|
||||
var valueID = Zotero.ID.get('itemDataValues');
|
||||
Zotero.DB.query("INSERT INTO itemDataValues VALUES (?,?)", [valueID, values[j]]);
|
||||
Zotero.DB.query("UPDATE itemData SET valueID=? WHERE itemID=? AND fieldID=?", [valueID, rows[j]['itemID'], rows[j]['fieldID']]);
|
||||
}
|
||||
|
@ -1021,14 +1021,14 @@ Zotero.Schema = new function(){
|
|||
|
||||
if (i==22) {
|
||||
if (Zotero.DB.valueQuery("SELECT COUNT(*) FROM items WHERE itemID=0")) {
|
||||
var itemID = Zotero.getRandomID('items', 'itemID');
|
||||
var itemID = Zotero.ID.get('items', true);
|
||||
Zotero.DB.query("UPDATE items SET itemID=? WHERE itemID=?", [itemID, 0]);
|
||||
Zotero.DB.query("UPDATE itemData SET itemID=? WHERE itemID=?", [itemID, 0]);
|
||||
Zotero.DB.query("UPDATE itemNotes SET itemID=? WHERE itemID=?", [itemID, 0]);
|
||||
Zotero.DB.query("UPDATE itemAttachments SET itemID=? WHERE itemID=?", [itemID, 0]);
|
||||
}
|
||||
if (Zotero.DB.valueQuery("SELECT COUNT(*) FROM collections WHERE collectionID=0")) {
|
||||
var collectionID = Zotero.getRandomID('collections', 'collectionID');
|
||||
var collectionID = Zotero.ID.get('collections');
|
||||
Zotero.DB.query("UPDATE collections SET collectionID=? WHERE collectionID=0", [collectionID]);
|
||||
Zotero.DB.query("UPDATE collectionItems SET collectionID=? WHERE collectionID=0", [collectionID]);
|
||||
}
|
||||
|
@ -1051,7 +1051,7 @@ Zotero.Schema = new function(){
|
|||
var values = Zotero.DB.columnQuery("SELECT DISTINCT value FROM itemData");
|
||||
if (values) {
|
||||
for (var j=0; j<values.length; j++) {
|
||||
var valueID = Zotero.getRandomID('itemDataValues', 'valueID', 2097152); // Stored in 3 bytes
|
||||
var valueID = Zotero.ID.get('itemDataValues');
|
||||
Zotero.DB.query("INSERT INTO itemDataValues VALUES (?,?)", [valueID, values[j]]);
|
||||
}
|
||||
}
|
||||
|
@ -1071,7 +1071,7 @@ Zotero.Schema = new function(){
|
|||
var value = Zotero.Date.strToMultipart(rows[j]['value']);
|
||||
var valueID = Zotero.DB.valueQuery("SELECT valueID FROM itemDataValues WHERE value=?", rows[j]['value']);
|
||||
if (!valueID) {
|
||||
var valueID = Zotero.getRandomID('itemDataValues', 'valueID', 2097152);
|
||||
var valueID = Zotero.ID.get('itemDataValues');
|
||||
Zotero.DB.query("INSERT INTO itemDataValues VALUES (?,?)", [valueID, value]);
|
||||
}
|
||||
Zotero.DB.query("UPDATE itemData SET valueID=? WHERE itemID=? AND fieldID=?", [valueID, rows[j]['itemID'], rows[j]['fieldID']]);
|
||||
|
@ -1145,7 +1145,7 @@ Zotero.Schema = new function(){
|
|||
if (i==33) {
|
||||
var rows = Zotero.DB.query("SELECT * FROM itemNotes WHERE itemID NOT IN (SELECT itemID FROM items)");
|
||||
if (rows) {
|
||||
var colID = Zotero.getRandomID('collections', 'collectionID');
|
||||
var colID = Zotero.ID.get('collections');
|
||||
Zotero.DB.query("INSERT INTO collections VALUES (?,?,?)", [colID, "[Recovered Notes]", null]);
|
||||
|
||||
for (var j=0; j<rows.length; j++) {
|
||||
|
@ -1164,7 +1164,7 @@ Zotero.Schema = new function(){
|
|||
}
|
||||
var exists = Zotero.DB.valueQuery("SELECT COUNT(*) FROM itemNotes WHERE itemID=?", rows[j].note);
|
||||
if (exists) {
|
||||
var noteItemID = Zotero.getRandomID('items', 'itemID');
|
||||
var noteItemID = Zotero.ID.get('items', true);
|
||||
}
|
||||
else {
|
||||
var noteItemID = rows[j].note;
|
||||
|
|
|
@ -134,8 +134,7 @@ Zotero.Search.prototype.save = function(fixGaps) {
|
|||
else {
|
||||
var isNew = true;
|
||||
|
||||
this._savedSearchID
|
||||
= Zotero.getRandomID('savedSearches', 'savedSearchID');
|
||||
this._savedSearchID = Zotero.ID.get('savedSearches');
|
||||
|
||||
var sql = "INSERT INTO savedSearches (savedSearchID, savedSearchName) "
|
||||
+ "VALUES (?,?)";
|
||||
|
|
|
@ -59,7 +59,6 @@ var Zotero = new function(){
|
|||
this.arrayToHash = arrayToHash;
|
||||
this.hasValues = hasValues;
|
||||
this.randomString = randomString;
|
||||
this.getRandomID = getRandomID;
|
||||
this.moveToUnique = moveToUnique;
|
||||
|
||||
// Public properties
|
||||
|
@ -813,40 +812,6 @@ var Zotero = new function(){
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a unique random id for use in a DB table
|
||||
**/
|
||||
function getRandomID(table, column, max){
|
||||
if (!table){
|
||||
throw('SQL query not provided');
|
||||
}
|
||||
|
||||
if (!column){
|
||||
throw('SQL query not provided');
|
||||
}
|
||||
|
||||
var sql = 'SELECT COUNT(*) FROM ' + table + ' WHERE ' + column + '=';
|
||||
|
||||
if (!max){
|
||||
max = 16383;
|
||||
}
|
||||
|
||||
max--; // since we use ceil(), decrement max by 1
|
||||
var tries = 3; // # of tries to find a unique id
|
||||
do {
|
||||
// If no luck after number of tries, try a larger range
|
||||
if (!tries){
|
||||
max = max * 128;
|
||||
}
|
||||
var rnd = Math.ceil(Math.random()*max);
|
||||
var exists = Zotero.DB.valueQuery(sql + rnd);
|
||||
tries--;
|
||||
}
|
||||
while (exists);
|
||||
return rnd;
|
||||
}
|
||||
|
||||
|
||||
function moveToUnique(file, newFile){
|
||||
newFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
|
||||
var newName = newFile.leafName;
|
||||
|
|
Loading…
Reference in a new issue