www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit d92e3661d51dd001f5678d09669e17e8c3711a7d
parent 7236aaf58ed0fdd4db4c52fef1f890aa8246bc36
Author: Dan Stillman <dstillman@zotero.org>
Date:   Sat,  9 Feb 2008 09:46:29 +0000

- 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



Diffstat:
Mchrome/content/zotero/xpcom/data_access.js | 265+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mchrome/content/zotero/xpcom/schema.js | 16++++++++--------
Mchrome/content/zotero/xpcom/search.js | 3+--
Mchrome/content/zotero/xpcom/zotero.js | 35-----------------------------------
4 files changed, 263 insertions(+), 56 deletions(-)

diff --git a/chrome/content/zotero/xpcom/data_access.js b/chrome/content/zotero/xpcom/data_access.js @@ -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'; + } + } +} + diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js @@ -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; diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js @@ -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 (?,?)"; diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js @@ -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;