Closes #711, Remove support for nested transactions

This commit is contained in:
Dan Stillman 2015-05-10 04:20:47 -04:00
parent e584dbf5dd
commit 14d435b8d8
23 changed files with 862 additions and 856 deletions

View file

@ -1109,7 +1109,7 @@
this.item.setType(itemTypeID);
if (this.saveOnEdit) {
this.item.save();
this.item.saveTx();
}
else {
this.refresh();
@ -1357,7 +1357,7 @@
return;
}
this.item.removeCreator(index);
this.item.save();
this.item.saveTx();
]]>
</body>
</method>
@ -1766,7 +1766,7 @@
}
if (this.saveOnEdit) {
yield this.item.save();
yield this.item.saveTx();
}
var val = this.item.getCreator(creatorIndex);
@ -1915,7 +1915,7 @@
return Zotero.spawn(function* () {
this.item.setField(field, value);
if (save) {
yield this.item.save();
yield this.item.saveTx();
}
}, this);
]]></body>
@ -2034,14 +2034,14 @@
}
this.item.removeCreator(index);
if (this.saveOnEdit && !skipSave) {
return this.item.save();
return this.item.saveTx();
}
return;
}
var changed = this.item.setCreator(index, fields);
if (changed && this.saveOnEdit && !skipSave) {
return this.item.save();
return this.item.saveTx();
}
}.bind(this));
]]></body>
@ -2091,7 +2091,7 @@
this.item.setCreator(newIndex, a);
this.item.setCreator(index, b);
if (this.saveOnEdit) {
return this.item.save();
return this.item.saveTx();
}
}.bind(this));
]]>

View file

@ -239,7 +239,7 @@
let changed = this.item.setNote(noteField.value);
if (changed && this.saveOnEdit) {
yield this.item.save();
yield this.item.saveTx();
}
return;
}
@ -254,7 +254,7 @@
item.parentKey = this.parentItem.key;
}
if (this.saveOnEdit) {
var id = yield item.save();
var id = yield item.saveTx();
if (!this.parentItem && this.collection) {
this.collection.addItem(id);

View file

@ -333,7 +333,7 @@
if (tagData) {
let item = document.getBindingParent(this).item
item.removeTag(tagName);
yield item.save()
yield item.saveTx()
}
// Return focus to items pane
@ -682,13 +682,13 @@
if (oldValue !== value) {
// The existing textbox will be removed in notify()
this.item.replaceTag(oldValue, value);
yield this.item.save();
yield this.item.saveTx();
}
}
// Existing tag cleared
else {
this.item.removeTag(oldValue);
yield this.item.save();
yield this.item.saveTx();
}
}
// Multiple tags
@ -710,7 +710,7 @@
}
this.item.addTags(tags);
yield this.item.save();
yield this.item.saveTx();
if (lastTag) {
this._lastTabIndex = this.item.getTags().length;
@ -724,7 +724,7 @@
// if it doesn't already exist.
row.parentNode.removeChild(row);
this.item.addTag(value);
yield this.item.save();
yield this.item.saveTx();
}
}.bind(this));
]]></body>

View file

@ -45,7 +45,7 @@ Zotero.Attachments = new function(){
throw ("'" + file.leafName + "' must be a file in Zotero.Attachments.importFromFile()");
}
var itemID, newFile;
var itemID, newFile, contentType;
yield Zotero.DB.executeTransaction(function* () {
// Create a new attachment
var attachmentItem = new Zotero.Item('attachment');
@ -73,15 +73,15 @@ Zotero.Attachments = new function(){
// Copy file to unique filename, which automatically shortens long filenames
newFile = Zotero.File.copyToUnique(file, newFile);
var contentType = yield Zotero.MIME.getMIMETypeFromFile(newFile);
contentType = yield Zotero.MIME.getMIMETypeFromFile(newFile);
attachmentItem.attachmentContentType = contentType;
attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_FILE);
yield attachmentItem.save();
// Determine charset and build fulltext index
yield _postProcessFile(itemID, newFile, contentType);
}.bind(this))
.then(function () {
return _postProcessFile(itemID, newFile, contentType);
})
.catch(function (e) {
Zotero.debug(e, 1);
var msg = "Failed importing file " + file.path;
@ -112,21 +112,21 @@ Zotero.Attachments = new function(){
var title = file.leafName;
var contentType = yield Zotero.MIME.getMIMETypeFromFile(file);
var itemID;
return Zotero.DB.executeTransaction(function* () {
var itemID = yield _addToDB({
itemID = yield _addToDB({
file: file,
title: title,
linkMode: this.LINK_MODE_LINKED_FILE,
contentType: contentType,
parentItemID: parentItemID
});
// Determine charset and build fulltext index
yield _postProcessFile(itemID, file, contentType);
return itemID;
}.bind(this));
}.bind(this))
.then(function () {
return _postProcessFile(itemID, file, contentType);
})
.then(() => itemID);
});
@ -148,7 +148,7 @@ Zotero.Attachments = new function(){
throw new Error("parentItemID not provided");
}
var destDir;
var itemID, destDir, newFile;
yield Zotero.DB.executeTransaction(function* () {
// Create a new attachment
var attachmentItem = new Zotero.Item('attachment');
@ -164,7 +164,7 @@ Zotero.Attachments = new function(){
// DEBUG: this should probably insert access date too so as to
// create a proper item, but at the moment this is only called by
// translate.js, which sets the metadata fields itself
var itemID = yield attachmentItem.save();
itemID = yield attachmentItem.save();
attachmentItem = yield Zotero.Items.getAsync(itemID)
destDir = this.getStorageDirectory(attachmentItem);
@ -172,15 +172,15 @@ Zotero.Attachments = new function(){
file.parent.copyTo(storageDir, destDir.leafName);
// Point to copied file
var newFile = destDir.clone();
newFile = destDir.clone();
newFile.append(file.leafName);
attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_URL);
yield attachmentItem.save();
// Determine charset and build fulltext index
yield _postProcessFile(itemID, newFile, contentType);
}.bind(this))
.then(function () {
return _postProcessFile(itemID, newFile, contentType);
})
.catch(function (e) {
Zotero.debug(e, 1);
@ -1130,7 +1130,7 @@ Zotero.Attachments = new function(){
if (parentItemID) {
newAttachment.parentID = parentItemID;
}
yield newAttachment.save();
yield newAttachment.saveTx();
// Copy over files if they exist
if (newAttachment.isImportedAttachment() && attachment.getFile()) {
@ -1362,7 +1362,7 @@ Zotero.Attachments = new function(){
var item = yield Zotero.Items.getAsync(itemID);
charset = yield Zotero.CharacterSets.add(charset);
item.attachmentCharset = charset;
yield item.save();
yield item.saveTx();
if (disabled) {
Zotero.Notifier.enable();

View file

@ -70,13 +70,14 @@ Zotero.Creators = new function() {
/**
* Returns the creatorID matching given fields, or creates a new creator and returns its id
*
* @requireTransaction
* @param {Object} data Creator data in API JSON format
* @param {Boolean} [create=false] If no matching creator, create one
* @return {Promise<Integer>} creatorID
*/
this.getIDFromData = Zotero.Promise.method(function (data, create) {
this.getIDFromData = Zotero.Promise.coroutine(function* (data, create) {
Zotero.DB.requireTransaction();
data = this.cleanData(data);
return Zotero.DB.executeTransaction(function* () {
var sql = "SELECT creatorID FROM creators WHERE "
+ "firstName=? AND lastName=? AND fieldMode=?";
var id = yield Zotero.DB.valueQueryAsync(
@ -95,7 +96,6 @@ Zotero.Creators = new function() {
}
return id;
});
});
this.updateCreator = Zotero.Promise.coroutine(function* (creatorID, creatorData) {

View file

@ -547,11 +547,19 @@ Zotero.DataObject.prototype.editCheck = function () {
* TRUE on item update, or FALSE if item was unchanged
*/
Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options) {
options = options || {};
var env = {
options: options || {},
options: options,
transactionOptions: {}
};
if (!env.options.tx && !Zotero.DB.inTransaction()) {
Zotero.logError("save() called on Zotero." + this._ObjectType + " without a wrapping "
+ "transaction -- use saveTx() instead");
Zotero.debug((new Error).stack, 2);
env.options.tx = true;
}
var proceed = yield this._initSave(env);
if (!proceed) return false;
@ -562,7 +570,7 @@ Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options)
Zotero.debug('Updating database with new ' + this._objectType + ' data', 4);
}
return Zotero.DB.executeTransaction(function* () {
try {
if (Zotero.DataObject.prototype._finalizeSave == this._finalizeSave) {
throw new Error("_finalizeSave not implement for Zotero." + this._ObjectType);
}
@ -574,27 +582,49 @@ Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options)
if (!env.isNew) {
env.changed = this._previousData;
}
// Create transaction
if (env.options.tx) {
let result = yield Zotero.DB.executeTransaction(function* () {
yield this._saveData(env);
yield Zotero.DataObject.prototype._finalizeSave.call(this, env);
return this._finalizeSave(env);
}.bind(this), env.transactionOptions)
.catch(e => {
}.bind(this), env.transactionOptions);
return result;
}
// Use existing transaction
else {
Zotero.DB.requireTransaction();
yield this._saveData(env);
yield Zotero.DataObject.prototype._finalizeSave.call(this, env);
return this._finalizeSave(env);
}
}
catch(e) {
return this._recoverFromSaveError(env, e)
.catch(function(e2) {
Zotero.debug(e2, 1);
})
.then(function() {
if (options.errorHandler) {
options.errorHandler(e);
if (env.options.errorHandler) {
env.options.errorHandler(e);
}
else {
Zotero.debug(e, 1);
}
throw e;
})
});
}
});
Zotero.DataObject.prototype.saveTx = function (options) {
options = options || {};
options.tx = true;
return this.save(options);
}
Zotero.DataObject.prototype.hasChanged = function() {
Zotero.debug(this._changed);
return !!Object.keys(this._changed).filter(dataType => this._changed[dataType]).length
@ -618,9 +648,15 @@ Zotero.DataObject.prototype._initSave = Zotero.Promise.coroutine(function* (env)
}
// Undo registerIdentifiers() on failure
env.transactionOptions.onRollback = function () {
var func = function () {
this.ObjectsClass.unload(env.id);
}.bind(this);
if (env.options.tx) {
env.transactionOptions.onRollback = func;
}
else {
Zotero.DB.addCurrentCallback("rollback", func);
}
return true;
});
@ -653,11 +689,9 @@ Zotero.DataObject.prototype.erase = Zotero.Promise.coroutine(function* () {
Zotero.debug('Deleting ' + this.objectType + ' ' + this.id);
yield Zotero.DB.executeTransaction(function* () {
Zotero.DB.requireTransaction();
yield this._eraseData(env);
yield this._erasePreCommit(env);
}.bind(this));
return this._erasePostCommit(env);
});

View file

@ -1173,6 +1173,11 @@ Zotero.Item.prototype.isEditable = function() {
}
Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
// Sanity check
if (!Zotero.DB.inTransaction()) {
throw new Error("Not in transaction saving item " + this.libraryKey);
}
var isNew = env.isNew;
var options = env.options;
@ -1740,6 +1745,11 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
parentItem.clearBestAttachmentState();
}
}
// Sanity check
if (!Zotero.DB.inTransaction()) {
throw new Error("Not in transaction saving item " + this.libraryKey);
}
});
Zotero.Item.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {

View file

@ -169,8 +169,8 @@ Zotero.Relations = function () {
* @param {String} prefix
* @param {String[]} ignorePredicates
*/
this.eraseByURIPrefix = function (prefix, ignorePredicates) {
return Zotero.DB.executeTransaction(function* () {
this.eraseByURIPrefix = Zotero.Promise.coroutine(function* (prefix, ignorePredicates) {
Zotero.DB.requireTransaction();
prefix = prefix + '%';
var sql = "SELECT ROWID FROM relations WHERE (subject LIKE ? OR object LIKE ?)";
var params = [prefix, prefix];
@ -187,15 +187,14 @@ Zotero.Relations = function () {
yield relation.load();
yield relation.erase();
}
}.bind(this));
}
});
/**
* @return {Promise}
*/
this.eraseByURI = function (uri, ignorePredicates) {
return Zotero.DB.executeTransaction(function* () {
this.eraseByURI = Zotero.Promise.coroutine(function* (uri, ignorePredicates) {
Zotero.DB.requireTransaction();
var sql = "SELECT ROWID FROM relations WHERE (subject=? OR object=?)";
var params = [uri, uri];
if (ignorePredicates) {
@ -211,8 +210,7 @@ Zotero.Relations = function () {
yield relation.load();
yield relation.erase();
}
}.bind(this));
}
});
this.purge = Zotero.Promise.coroutine(function* () {

View file

@ -76,16 +76,17 @@ Zotero.Tags = new function() {
/**
* Returns the tagID matching given fields, or creates a new tag and returns its id
*
* @requireTransaction
* @param {Number} libraryID
* @param {String} name - Tag data in API JSON format
* @param {Boolean} [create=false] - If no matching tag, create one
* @return {Promise<Integer>} tagID
*/
this.getIDFromName = Zotero.Promise.method(function (libraryID, name, create) {
this.getIDFromName = Zotero.Promise.coroutine(function* (libraryID, name, create) {
Zotero.DB.requireTransaction();
data = this.cleanData({
tag: name
});
return Zotero.DB.executeTransaction(function* () {
var sql = "SELECT tagID FROM tags WHERE libraryID=? AND name=?";
var id = yield Zotero.DB.valueQueryAsync(sql, [libraryID, data.tag]);
if (!id && create) {
@ -99,7 +100,6 @@ Zotero.Tags = new function() {
}
return id;
});
});
/*

View file

@ -297,6 +297,12 @@ Zotero.DBConnection.prototype.addCallback = function (type, cb) {
}
Zotero.DBConnection.prototype.addCurrentCallback = function (type, cb) {
this.requireTransaction();
this._callbacks.current[type].push(cb);
}
Zotero.DBConnection.prototype.removeCallback = function (type, id) {
switch (type) {
case 'begin':
@ -453,41 +459,16 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
}
}
if ((options.exclusive && this._inTransaction) || this._inExclusiveTransaction) {
yield Zotero.DB.waitForTransaction();
}
var startedTransaction = false;
try {
if (this._inTransaction) {
Zotero.debug("Async DB transaction in progress -- increasing level to "
+ ++this._asyncTransactionNestingLevel, 5);
while (this._inTransaction) {
yield Zotero.DB.waitForTransaction().timeout(options.waitTimeout || 10000);
}
this._inTransaction = startedTransaction = true;
if (options.onCommit) {
this._callbacks.current.commit.push(options.onCommit);
}
if (options.onRollback) {
this._callbacks.current.rollback.push(options.onRollback);
}
try {
var result = yield Zotero.Promise.coroutine(func)();
}
catch (e) {
Zotero.debug("Rolling back nested async DB transaction", 5);
this._asyncTransactionNestingLevel = 0;
throw e;
}
Zotero.debug("Decreasing async DB transaction level to "
+ --this._asyncTransactionNestingLevel, 5);
return result;
}
else {
Zotero.debug("Beginning async DB transaction", 5);
this._inTransaction = true;
this._inExclusiveTransaction = options.exclusive;
this._transactionPromise = new Zotero.Promise(function () {
resolve = arguments[0];
});
@ -504,14 +485,19 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
var conn = this._getConnection(options) || (yield this._getConnectionAsync(options));
var result = yield conn.executeTransaction(func);
Zotero.debug("Committed async DB transaction", 5);
this._inTransaction = false;
// Clear transaction time
if (this._transactionDate) {
this._transactionDate = null;
}
if (options) {
if (options.vacuumOnCommit) {
Zotero.debug('Vacuuming database');
yield Zotero.DB.queryAsync('VACUUM');
}
this._inTransaction = false;
// Function to run once transaction has been committed but before any
// permanent callbacks
if (options.onCommit) {
@ -519,12 +505,6 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
}
this._callbacks.current.rollback = [];
if (options.vacuumOnCommit) {
Zotero.debug('Vacuuming database');
yield Zotero.DB.queryAsync('VACUUM');
}
}
// Run temporary commit callbacks
var f;
while (f = this._callbacks.current.commit.shift()) {
@ -540,20 +520,23 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
return result;
}
}
catch (e) {
if (e.name == "TimeoutError") {
Zotero.debug("Timed out waiting for transaction", 1);
}
else {
Zotero.debug("Rolled back async DB transaction", 5);
Zotero.debug(e, 1);
}
if (startedTransaction) {
this._inTransaction = false;
this._inExclusiveTransaction = false;
}
if (options) {
// Function to run once transaction has been committed but before any
// permanent callbacks
if (options.onRollback) {
this._callbacks.current.rollback.push(options.onRollback);
}
}
// Run temporary commit callbacks
var f;
@ -586,15 +569,28 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
});
Zotero.DBConnection.prototype.inTransaction = function () {
return this._inTransaction;
}
Zotero.DBConnection.prototype.waitForTransaction = function () {
if (!this._inTransaction) {
return Zotero.Promise.resolve();
return Zotero.Promise.resolve().cancellable();
}
Zotero.debug("Waiting for transaction to finish");
Zotero.debug((new Error).stack);
return this._transactionPromise;
};
Zotero.DBConnection.prototype.requireTransaction = function () {
if (!this._inTransaction) {
throw new Error("Not in transaction");
}
};
/**
* @param {String} sql SQL statement to run
* @param {Array|String|Integer} [params] SQL parameters to bind
@ -818,7 +814,7 @@ Zotero.DBConnection.prototype.tableExists = function (table) {
*
* @return {Promise}
*/
Zotero.DBConnection.prototype.executeSQLFile = function (sql) {
Zotero.DBConnection.prototype.executeSQLFile = Zotero.Promise.coroutine(function* (sql) {
var nonCommentRE = /^[^-]/;
var trailingCommentRE = /^(.*?)(?:--.+)?$/;
@ -836,13 +832,13 @@ Zotero.DBConnection.prototype.executeSQLFile = function (sql) {
var statements = sql.split(";")
.map(function (x) x.replace(/TEMPSEMI/g, ";"));
return this.executeTransaction(function* () {
this.requireTransaction();
var statement;
while (statement = statements.shift()) {
yield Zotero.DB.queryAsync(statement);
}
});
}
});
/*

View file

@ -403,13 +403,14 @@ Zotero.Fulltext = new function(){
/**
* Index multiple words at once
*
* @requireTransaction
* @param {Number} itemID
* @param {Array<string>} words
* @return {Promise}
*/
function indexWords(itemID, words) {
var indexWords = Zotero.Promise.coroutine(function* (itemID, words) {
Zotero.DB.requireTransaction();
let chunk;
return Zotero.DB.executeTransaction(function* () {
yield Zotero.DB.queryAsync("DELETE FROM indexing.fulltextWords");
while (words.length > 0) {
chunk = words.splice(0, 100);
@ -420,8 +421,7 @@ Zotero.Fulltext = new function(){
yield Zotero.DB.queryAsync('INSERT OR IGNORE INTO fulltextItemWords (wordID, itemID) SELECT wordID, ? FROM fulltextWords JOIN indexing.fulltextWords USING(word)', [itemID]);
yield Zotero.DB.queryAsync("REPLACE INTO fulltextItems (itemID, version) VALUES (?,?)", [itemID, 0]);
yield Zotero.DB.queryAsync("DELETE FROM indexing.fulltextWords");
}.bind(this));
}
});
/**
@ -559,7 +559,6 @@ Zotero.Fulltext = new function(){
}
}
yield Zotero.DB.executeTransaction(function* () {
yield indexString(text, charset, itemID);
// Record the number of characters indexed (unless we're indexing a (PDF) cache file,
@ -567,7 +566,6 @@ Zotero.Fulltext = new function(){
if (!isCacheFile) {
yield setChars(itemID, { indexed: text.length, total: totalChars });
}
});
return true;
}.bind(this));
@ -690,10 +688,8 @@ Zotero.Fulltext = new function(){
return false;
}
yield Zotero.DB.executeTransaction(function* () {
yield indexFile(cacheFilePath, 'text/plain', 'utf-8', itemID, true, true);
yield setPages(itemID, { indexed: pagesIndexed, total: totalPages });
});
return true;
});
@ -827,7 +823,9 @@ Zotero.Fulltext = new function(){
+ libraryKey, 2);
// Delete rows for items that weren't supposed to be indexed
yield Zotero.DB.executeTransaction(function* () {
yield this.clearItemWords(itemID);
}.bind(this));
continue;
}
@ -1291,16 +1289,18 @@ Zotero.Fulltext = new function(){
});
/**
* @requireTransaction
*/
this.clearItemWords = Zotero.Promise.coroutine(function* (itemID, skipCacheClear) {
var indexed = yield Zotero.DB.executeTransaction(function* () {
Zotero.DB.requireTransaction();
var sql = "SELECT rowid FROM fulltextItems WHERE itemID=? LIMIT 1";
var indexed = yield Zotero.DB.valueQueryAsync(sql, itemID);
if (indexed) {
yield Zotero.DB.queryAsync("DELETE FROM fulltextItemWords WHERE itemID=?", itemID);
yield Zotero.DB.queryAsync("DELETE FROM fulltextItems WHERE itemID=?", itemID);
}
return indexed;
}.bind(this));
if (indexed) {
Zotero.Prefs.set('purge.fulltext', true);

View file

@ -123,21 +123,19 @@ Zotero.Schema = new function(){
// Update custom tables if they exist so that changes are in
// place before user data migration
if (Zotero.DB.tableExists('customItemTypes')) {
yield Zotero.Schema.updateCustomTables(updated);
yield _updateCustomTables(updated);
}
updated = yield _migrateUserDataSchema(userdata);
yield _updateSchema('triggers');
// Populate combined tables for custom types and fields -- this is likely temporary
//
// We do this again in case custom fields were changed during user data migration
yield _updateCustomTables()
return updated;
}.bind(this));
// Populate combined tables for custom types and fields
// -- this is likely temporary
//
// We do this even if updated in case custom fields were
// changed during user data migration
yield Zotero.Schema.updateCustomTables()
if (updated) {
// Upgrade seems to have been a success -- delete any previous backups
var maxPrevious = userdata - 1;
@ -318,7 +316,7 @@ Zotero.Schema = new function(){
});
var _reloadSchema = Zotero.Promise.coroutine(function* () {
yield Zotero.Schema.updateCustomTables();
yield _updateCustomTables();
yield Zotero.ItemTypes.load();
yield Zotero.ItemFields.load();
yield Zotero.SearchConditions.init();
@ -335,10 +333,11 @@ Zotero.Schema = new function(){
});
this.updateCustomTables = function (skipDelete, skipSystem) {
return Zotero.DB.executeTransaction(function* (conn) {
var _updateCustomTables = Zotero.Promise.coroutine(function* (skipDelete, skipSystem) {
Zotero.debug("Updating custom tables");
Zotero.DB.requireTransaction();
if (!skipDelete) {
yield Zotero.DB.queryAsync("DELETE FROM itemTypesCombined");
yield Zotero.DB.queryAsync("DELETE FROM fieldsCombined WHERE fieldID NOT IN (SELECT fieldID FROM itemData)");
@ -386,7 +385,6 @@ Zotero.Schema = new function(){
+ "customFieldID + " + offset + " AS fieldID FROM customBaseFieldMappings"
);
});
}
/**
@ -1454,7 +1452,7 @@ Zotero.Schema = new function(){
yield _getSchemaSQL('triggers').then(function (sql) {
return Zotero.DB.executeSQLFile(sql);
});
yield Zotero.Schema.updateCustomTables(true);
yield _updateCustomTables(true);
yield _getSchemaSQLVersion('system').then(function (version) {
return _updateDBVersion('system', version);
@ -1918,16 +1916,17 @@ Zotero.Schema = new function(){
//
// If libraryID set, make sure no relations still use a local user key, and then remove on-error code in sync.js
function _migrateUserDataSchema(fromVersion) {
return _getSchemaSQLVersion('userdata')
.then(function (toVersion) {
var _migrateUserDataSchema = Zotero.Promise.coroutine(function* (fromVersion) {
var toVersion = yield _getSchemaSQLVersion('userdata');
if (fromVersion >= toVersion) {
return false;
}
Zotero.debug('Updating user data tables from version ' + fromVersion + ' to ' + toVersion);
return Zotero.DB.executeTransaction(function* (conn) {
Zotero.DB.requireTransaction();
// Step through version changes until we reach the current version
//
// Each block performs the changes necessary to move from the
@ -2270,11 +2269,10 @@ Zotero.Schema = new function(){
yield Zotero.DB.queryAsync("DROP TABLE itemsOld");
yield Zotero.DB.queryAsync("DROP TABLE tagsOld");
}
}
yield _updateDBVersion('userdata', toVersion);
})
.return(true);
})
return true;
}
});
}

View file

@ -947,7 +947,7 @@ Zotero.Search.idsToTempTable = function (ids) {
yield Zotero.DB.queryAsync(sql);
return tmpTable;
}, { exclusive: true });
});
}

View file

@ -740,8 +740,6 @@ var ZoteroPane = new function()
*/
this.newItem = Zotero.Promise.coroutine(function* (typeID, data, row, manual)
{
yield Zotero.DB.waitForTransaction();
if ((row === undefined || row === null) && this.collectionsView.selection) {
row = this.collectionsView.selection.currentIndex;
@ -810,8 +808,6 @@ var ZoteroPane = new function()
this.newCollection = Zotero.Promise.coroutine(function* (parentKey) {
yield Zotero.DB.waitForTransaction();
if (!this.canEditLibrary()) {
this.displayCannotEditLibraryMessage();
return;
@ -847,7 +843,7 @@ var ZoteroPane = new function()
collection.libraryID = libraryID;
collection.name = newName.value;
collection.parentKey = parentKey;
return collection.save();
return collection.saveTx();
});
@ -1234,6 +1230,15 @@ var ZoteroPane = new function()
return Zotero.spawn(function* () {
yield Zotero.DB.waitForTransaction();
// Don't select item until items list has loaded
//
// This avoids an error if New Item is used while the pane is first loading.
var deferred = Zotero.Promise.defer();
this.itemsView.addEventListener('load', function () {
deferred.resolve();
});
yield deferred.promise;
var selectedItems = this.itemsView.getSelectedItems();
// Check if selection has actually changed. The onselect event that calls this
@ -1863,7 +1868,7 @@ var ZoteroPane = new function()
if (result && newName.value) {
row.ref.name = newName.value;
row.ref.save();
row.ref.saveTx();
}
}
else {
@ -3055,8 +3060,6 @@ var ZoteroPane = new function()
* @return {Promise}
*/
this.newNote = Zotero.Promise.coroutine(function* (popup, parentKey, text, citeURI) {
yield Zotero.DB.waitForTransaction();
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
@ -3081,7 +3084,7 @@ var ZoteroPane = new function()
if (parentKey) {
item.parentKey = parentKey;
}
var itemID = yield item.save();
var itemID = yield item.saveTx();
if (!parentKey && this.itemsView && this.collectionsView.selectedTreeRow.isCollection()) {
yield this.collectionsView.selectedTreeRow.ref.addItem(itemID);
@ -3146,7 +3149,7 @@ var ZoteroPane = new function()
var note = items[0].getNote()
items[0].setNote(note + text);
yield items[0].save();
yield items[0].saveTx();
var noteElem = document.getElementById('zotero-note-editor')
noteElem.focus();
@ -3537,7 +3540,7 @@ var ZoteroPane = new function()
item.setField('title', attachmentItem.getField('title'));
item.setField('url', attachmentItem.getField('url'));
item.setField('accessDate', attachmentItem.getField('accessDate'));
yield item.save();
yield item.saveTx();
}
}
}
@ -4106,7 +4109,7 @@ var ZoteroPane = new function()
}
item.setField('title', newName);
yield item.save();
yield item.saveTx();
}
return true;

View file

@ -24,7 +24,7 @@ describe("Zotero.Attachments", function() {
// Create parent item
var item = new Zotero.Item('book');
var parentItemID = yield item.save();
var parentItemID = yield item.saveTx();
// Create attachment and compare content
var itemID = yield Zotero.Attachments.importFromFile(tmpFile, parentItemID);
@ -43,7 +43,7 @@ describe("Zotero.Attachments", function() {
// Create parent item
var item = new Zotero.Item('book');
var parentItemID = yield item.save();
var parentItemID = yield item.saveTx();
// Create attachment and compare content
var itemID = yield Zotero.Attachments.importFromFile(file, parentItemID);

View file

@ -6,7 +6,7 @@ describe("Zotero.Collection", function() {
var name = "Test";
var collection = new Zotero.Collection;
collection.name = name;
var id = yield collection.save();
var id = yield collection.saveTx();
collection = yield Zotero.Collections.getAsync(id);
assert.equal(collection.name, name);
});
@ -18,7 +18,7 @@ describe("Zotero.Collection", function() {
var collection = new Zotero.Collection
collection.version = version;
collection.name = "Test";
var id = yield collection.save();
var id = yield collection.saveTx();
collection = yield Zotero.Collections.getAsync(id);
assert.equal(collection.version, version);
});
@ -28,13 +28,13 @@ describe("Zotero.Collection", function() {
it("should set parent collection for new collections", function* () {
var parentCol = new Zotero.Collection
parentCol.name = "Parent";
var parentID = yield parentCol.save();
var parentID = yield parentCol.saveTx();
var {libraryID, key: parentKey} = Zotero.Collections.getLibraryAndKeyFromID(parentID);
var col = new Zotero.Collection
col.name = "Child";
col.parentKey = parentKey;
var id = yield col.save();
var id = yield col.saveTx();
col = yield Zotero.Collections.getAsync(id);
assert.equal(col.parentKey, parentKey);
});
@ -43,25 +43,25 @@ describe("Zotero.Collection", function() {
// Create initial parent collection
var parentCol = new Zotero.Collection
parentCol.name = "Parent";
var parentID = yield parentCol.save();
var parentID = yield parentCol.saveTx();
var {libraryID, key: parentKey} = Zotero.Collections.getLibraryAndKeyFromID(parentID);
// Create subcollection
var col = new Zotero.Collection
col.name = "Child";
col.parentKey = parentKey;
var id = yield col.save();
var id = yield col.saveTx();
col = yield Zotero.Collections.getAsync(id);
// Create new parent collection
var newParentCol = new Zotero.Collection
newParentCol.name = "New Parent";
var newParentID = yield newParentCol.save();
var newParentID = yield newParentCol.saveTx();
var {libraryID, key: newParentKey} = Zotero.Collections.getLibraryAndKeyFromID(newParentID);
// Change parent collection
col.parentKey = newParentKey;
yield col.save();
yield col.saveTx();
col = yield Zotero.Collections.getAsync(id);
assert.equal(col.parentKey, newParentKey);
});
@ -70,14 +70,14 @@ describe("Zotero.Collection", function() {
// Create initial parent collection
var parentCol = new Zotero.Collection
parentCol.name = "Parent";
var parentID = yield parentCol.save();
var parentID = yield parentCol.saveTx();
var {libraryID, key: parentKey} = Zotero.Collections.getLibraryAndKeyFromID(parentID);
// Create subcollection
var col = new Zotero.Collection
col.name = "Child";
col.parentKey = parentKey;
var id = yield col.save();
var id = yield col.saveTx();
col = yield Zotero.Collections.getAsync(id);
// Set to existing parent
@ -88,11 +88,11 @@ describe("Zotero.Collection", function() {
it("should not resave a collection with no parent if set to false", function* () {
var col = new Zotero.Collection
col.name = "Test";
var id = yield col.save();
var id = yield col.saveTx();
col = yield Zotero.Collections.getAsync(id);
col.parentKey = false;
var ret = yield col.save();
var ret = yield col.saveTx();
assert.isFalse(ret);
});
})

View file

@ -26,7 +26,7 @@ describe("Zotero.CollectionTreeView", function() {
// Create collection
var collection = new Zotero.Collection;
collection.name = "Select new collection";
var id = yield collection.save();
var id = yield collection.saveTx();
// New collection should be selected
var selected = collectionsView.getSelectedCollection(true);
@ -39,7 +39,7 @@ describe("Zotero.CollectionTreeView", function() {
// Create collection with skipNotifier flag
var collection = new Zotero.Collection;
collection.name = "No select on skipNotifier";
var id = yield collection.save({
var id = yield collection.saveTx({
skipNotifier: true
});
@ -53,7 +53,7 @@ describe("Zotero.CollectionTreeView", function() {
// Create collection with skipSelect flag
var collection = new Zotero.Collection;
collection.name = "No select on skipSelect";
var id = yield collection.save({
var id = yield collection.saveTx({
skipSelect: true
});
@ -65,13 +65,13 @@ describe("Zotero.CollectionTreeView", function() {
// Create collection
var collection = new Zotero.Collection;
collection.name = "No select on modify";
var id = yield collection.save();
var id = yield collection.saveTx();
collection = yield Zotero.Collections.getAsync(id);
resetSelection();
collection.name = "No select on modify 2";
yield collection.save();
yield collection.saveTx();
// Modified collection should not be selected
assert.equal(collectionsView.getSelectedLibraryID(), Zotero.Libraries.userLibraryID);
@ -81,14 +81,14 @@ describe("Zotero.CollectionTreeView", function() {
// Create collection
var collection = new Zotero.Collection;
collection.name = "Reselect on modify";
var id = yield collection.save();
var id = yield collection.saveTx();
collection = yield Zotero.Collections.getAsync(id);
var selected = collectionsView.getSelectedCollection(true);
assert.equal(selected, id);
collection.name = "Reselect on modify 2";
yield collection.save();
yield collection.saveTx();
// Modified collection should still be selected
selected = collectionsView.getSelectedCollection(true);
@ -98,13 +98,13 @@ describe("Zotero.CollectionTreeView", function() {
it("should add a saved search after collections", function* () {
var collection = new Zotero.Collection;
collection.name = "Test";
var collectionID = yield collection.save();
var collectionID = yield collection.saveTx();
var cv = win.ZoteroPane.collectionsView;
var search = new Zotero.Search;
search.name = "A Test Search";
search.addCondition('title', 'contains', 'test');
var searchID = yield search.save();
var searchID = yield search.saveTx();
var collectionRow = cv._rowMap["C" + collectionID];
var searchRow = cv._rowMap["S" + searchID];

View file

@ -6,7 +6,7 @@ describe("Zotero.DataObject", function() {
describe("#loadAllData()", function () {
it("should load data on a regular item", function* () {
var item = new Zotero.Item('book');
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadAllData();
assert.throws(item.getNote.bind(item), 'getNote() can only be called on notes and attachments');
@ -14,7 +14,7 @@ describe("Zotero.DataObject", function() {
it("should load data on an attachment item", function* () {
var item = new Zotero.Item('attachment');
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadAllData();
assert.equal(item.getNote(), '');
@ -22,7 +22,7 @@ describe("Zotero.DataObject", function() {
it("should load data on a note item", function* () {
var item = new Zotero.Item('note');
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadAllData();
assert.equal(item.getNote(), '');
@ -35,7 +35,7 @@ describe("Zotero.DataObject", function() {
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType('collection');
var obj = new Zotero.Collection;
obj.name = "Test";
var id = yield obj.save();
var id = yield obj.saveTx();
var { libraryID, key } = objectsClass.getLibraryAndKeyFromID(id);
assert.typeOf(key, 'string');
assert.equal(objectsClass.getIDFromLibraryAndKey(libraryID, key), id);
@ -44,7 +44,7 @@ describe("Zotero.DataObject", function() {
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType('search');
var obj = new Zotero.Search;
obj.name = "Test";
var id = yield obj.save();
var id = yield obj.saveTx();
var { libraryID, key } = objectsClass.getLibraryAndKeyFromID(id);
assert.typeOf(key, 'string');
assert.equal(objectsClass.getIDFromLibraryAndKey(libraryID, key), id);
@ -52,7 +52,7 @@ describe("Zotero.DataObject", function() {
// Item
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType('item');
var obj = new Zotero.Item('book');
var id = yield obj.save();
var id = yield obj.saveTx();
var { libraryID, key } = objectsClass.getLibraryAndKeyFromID(id);
assert.typeOf(key, 'string');
assert.equal(objectsClass.getIDFromLibraryAndKey(libraryID, key), id);

View file

@ -9,14 +9,15 @@ describe("Zotero.DB", function() {
});
beforeEach(function* () {
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS " + tmpTable);
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
});
after(function* () {
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS " + tmpTable);
});
describe("#executeTransaction()", function () {
it("should nest concurrent transactions", Zotero.Promise.coroutine(function* () {
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
it("should serialize concurrent transactions", Zotero.Promise.coroutine(function* () {
this.timeout(1000);
var resolve1, resolve2, reject1, reject2;
var promise1 = new Promise(function (resolve, reject) {
@ -29,18 +30,21 @@ describe("Zotero.DB", function() {
});
Zotero.DB.executeTransaction(function* () {
yield Zotero.Promise.delay(100);
assert.equal(Zotero.DB._asyncTransactionNestingLevel, 0);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)");
Zotero.DB.transactionDate; // Make sure we're still in a transaction
yield Zotero.Promise.delay(250);
var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable);
assert.equal(num, 0);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
assert.ok(Zotero.DB.inTransaction());
})
.then(resolve1)
.catch(reject1);
Zotero.DB.executeTransaction(function* () {
assert.equal(Zotero.DB._asyncTransactionNestingLevel, 1);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
Zotero.DB.transactionDate; // Make sure we're still in a transaction
var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable);
assert.equal(num, 1);
yield Zotero.Promise.delay(500);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)");
assert.ok(Zotero.DB.inTransaction());
})
.then(resolve2)
.catch(reject2);
@ -48,10 +52,8 @@ describe("Zotero.DB", function() {
yield Zotero.Promise.all([promise1, promise2]);
}));
it("shouldn't nest transactions if an exclusive transaction is open", Zotero.Promise.coroutine(function* () {
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
var resolve1, resolve2, reject1, reject2;
it("should serialize queued transactions", function* () {
var resolve1, resolve2, reject1, reject2, resolve3, reject3;
var promise1 = new Promise(function (resolve, reject) {
resolve1 = resolve;
reject1 = reject;
@ -60,66 +62,48 @@ describe("Zotero.DB", function() {
resolve2 = resolve;
reject2 = reject;
});
var promise3 = new Promise(function (resolve, reject) {
resolve3 = resolve;
reject3 = reject;
});
// Start a transaction and have it delay
Zotero.DB.executeTransaction(function* () {
yield Zotero.Promise.delay(100);
assert.equal(Zotero.DB._asyncTransactionNestingLevel, 0);
var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable);
assert.equal(num, 0);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
Zotero.DB.transactionDate; // Make sure we're still in a transaction
}, { exclusive: true })
assert.ok(Zotero.DB.inTransaction());
})
.then(resolve1)
.catch(reject1);
// Start two more transactions, which should wait on the first
Zotero.DB.executeTransaction(function* () {
assert.equal(Zotero.DB._asyncTransactionNestingLevel, 0);
var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable);
assert.equal(num, 1);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)");
Zotero.DB.transactionDate; // Make sure we're still in a transaction
assert.ok(Zotero.DB.inTransaction());
})
.then(resolve2)
.catch(reject2);
yield Zotero.Promise.all([promise1, promise2]);
}));
it("shouldn't nest an exclusive transaction if another transaction is open", Zotero.Promise.coroutine(function* () {
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
var resolve1, resolve2, reject1, reject2;
var promise1 = new Promise(function (resolve, reject) {
resolve1 = resolve;
reject1 = reject;
});
var promise2 = new Promise(function (resolve, reject) {
resolve2 = resolve;
reject2 = reject;
});
Zotero.DB.executeTransaction(function* () {
yield Zotero.Promise.delay(100);
assert.equal(Zotero.DB._asyncTransactionNestingLevel, 0);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
Zotero.DB.transactionDate; // Make sure we're still in a transaction
})
.then(resolve1)
.catch(reject1);
Zotero.DB.executeTransaction(function* () {
assert.equal(Zotero.DB._asyncTransactionNestingLevel, 0);
var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable);
assert.equal(num, 1);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)");
Zotero.DB.transactionDate; // Make sure we're still in a transaction
}, { exclusive: true })
.then(resolve2)
.catch(reject2);
assert.equal(num, 2);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (3)");
// But make sure the second queued transaction doesn't start at the same time,
// such that the first queued transaction gets closed while the second is still
// running
assert.ok(Zotero.DB.inTransaction());
})
.then(resolve3)
.catch(reject3);
yield Zotero.Promise.all([promise1, promise2]);
}));
yield Zotero.Promise.all([promise1, promise2, promise3]);
})
it("should roll back on error", function* () {
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
try {
yield Zotero.DB.executeTransaction(function* () {
@ -141,7 +125,6 @@ describe("Zotero.DB", function() {
it("should run onRollback callbacks", function* () {
var callbackRan = false;
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
try {
yield Zotero.DB.executeTransaction(
function* () {
@ -163,25 +146,31 @@ describe("Zotero.DB", function() {
yield Zotero.DB.queryAsync("DROP TABLE " + tmpTable);
});
it("should run onRollback callbacks for nested transactions", function* () {
it("should time out on nested transactions", function* () {
var e;
yield Zotero.DB.executeTransaction(function* () {
e = yield getPromiseError(
Zotero.DB.executeTransaction(function* () {}).timeout(250)
);
});
assert.ok(e);
assert.equal(e.name, "TimeoutError");
});
it("should run onRollback callbacks for timed-out nested transactions", function* () {
var callback1Ran = false;
var callback2Ran = false;
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
try {
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
yield Zotero.DB.executeTransaction(
function* () {
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)");
throw 'Aborting transaction -- ignore';
},
function* () {},
{
waitTimeout: 100,
onRollback: function () {
callback1Ran = true;
}
}
);
)
},
{
onRollback: function () {
@ -190,32 +179,10 @@ describe("Zotero.DB", function() {
});
}
catch (e) {
if (typeof e != 'string' || !e.startsWith('Aborting transaction')) throw e;
if (e.name != "TimeoutError") throw e;
}
assert.ok(callback1Ran);
assert.ok(callback2Ran);
yield Zotero.DB.queryAsync("DROP TABLE " + tmpTable);
});
it("should not commit nested transactions", function* () {
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
try {
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)");
throw 'Aborting transaction -- ignore';
});
});
}
catch (e) {
if (typeof e != 'string' || !e.startsWith('Aborting transaction')) throw e;
}
var count = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable);
assert.equal(count, 0);
yield Zotero.DB.queryAsync("DROP TABLE " + tmpTable);
});
})
});

View file

@ -36,7 +36,7 @@ describe("Zotero.Item", function () {
var fieldID = Zotero.ItemFields.getID(field);
var item = new Zotero.Item('book');
item.setField(field, 'Foo');
id = yield item.save();
id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
item.setField(field, "");
@ -57,7 +57,7 @@ describe("Zotero.Item", function () {
assert.ok(item._changed.itemData[fieldID]);
assert.ok(item.hasChanged());
yield item.save();
yield item.saveTx();
assert.isFalse(item.getField(fieldID));
})
@ -70,7 +70,7 @@ describe("Zotero.Item", function () {
it("should save version as object version", function* () {
var item = new Zotero.Item('book');
item.setField("version", 1);
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
assert.equal(item.getField("version"), 1);
});
@ -78,7 +78,7 @@ describe("Zotero.Item", function () {
it("should save versionNumber for computerProgram", function () {
var item = new Zotero.Item('computerProgram');
item.setField("versionNumber", "1.0");
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
assert.equal(item.getField("versionNumber"), "1.0");
});
@ -89,7 +89,7 @@ describe("Zotero.Item", function () {
var dateModified = "2015-05-05 17:18:12";
var item = new Zotero.Item('book');
item.dateModified = dateModified;
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
assert.equal(item.dateModified, dateModified);
})
@ -98,7 +98,7 @@ describe("Zotero.Item", function () {
var dateModified = "2015-05-05 17:18:12";
var item = new Zotero.Item('book');
item.dateModified = dateModified;
var id = yield item.save({
var id = yield item.saveTx({
skipDateModifiedUpdate: true
});
item = yield Zotero.Items.getAsync(id);
@ -109,13 +109,13 @@ describe("Zotero.Item", function () {
var dateModified = "2015-05-05 17:18:12";
var item = new Zotero.Item('book');
item.dateModified = dateModified;
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
// Save again without changing Date Modified
yield item.loadItemData();
item.setField('title', 'Test');
yield item.save()
yield item.saveTx()
assert.closeTo(Zotero.Date.sqlToDate(item.dateModified, true).getTime(), Date.now(), 1000);
})
@ -124,20 +124,20 @@ describe("Zotero.Item", function () {
var dateModified = "2015-05-05 17:18:12";
var item = new Zotero.Item('book');
item.dateModified = dateModified;
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
// Set Date Modified to existing value
yield item.loadItemData();
item.setField('title', 'Test');
item.dateModified = dateModified;
yield item.save()
yield item.saveTx()
assert.closeTo(Zotero.Date.sqlToDate(item.dateModified, true).getTime(), Date.now(), 1000);
})
it("should use current time if Date Modified is not given when skipDateModifiedUpdate is set for a new item", function* () {
var item = new Zotero.Item('book');
var id = yield item.save({
var id = yield item.saveTx({
skipDateModifiedUpdate: true
});
item = yield Zotero.Items.getAsync(id);
@ -148,13 +148,13 @@ describe("Zotero.Item", function () {
var dateModified = "2015-05-05 17:18:12";
var item = new Zotero.Item('book');
item.dateModified = dateModified;
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
// Resave with skipDateModifiedUpdate
yield item.loadItemData();
item.setField('title', 'Test');
yield item.save({
yield item.saveTx({
skipDateModifiedUpdate: true
})
assert.equal(item.dateModified, dateModified);
@ -164,11 +164,11 @@ describe("Zotero.Item", function () {
describe("#parentID", function () {
it("should create a child note", function* () {
var item = new Zotero.Item('book');
var parentItemID = yield item.save();
var parentItemID = yield item.saveTx();
item = new Zotero.Item('note');
item.parentID = parentItemID;
var childItemID = yield item.save();
var childItemID = yield item.saveTx();
item = yield Zotero.Items.getAsync(childItemID);
assert.ok(item.parentID);
@ -198,7 +198,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item('attachment');
item.attachmentLinkMode = 'linked_url';
item.url = "https://www.zotero.org/";
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
item.parentKey = false;
@ -207,15 +207,15 @@ describe("Zotero.Item", function () {
it("should move a top-level note under another item", function* () {
var noteItem = new Zotero.Item('note');
var id = yield noteItem.save()
var id = yield noteItem.saveTx()
noteItem = yield Zotero.Items.getAsync(id);
var item = new Zotero.Item('book');
id = yield item.save();
id = yield item.saveTx();
var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(id);
noteItem.parentKey = key;
yield noteItem.save();
yield noteItem.saveTx();
assert.isFalse(noteItem.isTopLevelItem());
})
@ -224,19 +224,19 @@ describe("Zotero.Item", function () {
// Create a collection
var collection = new Zotero.Collection;
collection.name = "Test";
var collectionID = yield collection.save();
var collectionID = yield collection.saveTx();
// Create a top-level note and add it to a collection
var noteItem = new Zotero.Item('note');
noteItem.addToCollection(collectionID);
var id = yield noteItem.save()
var id = yield noteItem.saveTx()
noteItem = yield Zotero.Items.getAsync(id);
var item = new Zotero.Item('book');
id = yield item.save();
id = yield item.saveTx();
var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(id);
noteItem.parentKey = key;
yield noteItem.save();
yield noteItem.saveTx();
assert.isFalse(noteItem.isTopLevelItem());
})
@ -258,7 +258,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item("journalArticle");
item.setCreators(creators);
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadCreators();
assert.sameDeepMembers(item.getCreatorsJSON(), creators);
@ -282,7 +282,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item("journalArticle");
item.setCreators(creators);
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadCreators();
assert.sameDeepMembers(item.getCreators(), creators);
@ -295,7 +295,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item("attachment");
item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
item.attachmentCharset = charset;
var itemID = yield item.save();
var itemID = yield item.saveTx();
item = yield Zotero.Items.getAsync(itemID);
assert.equal(item.attachmentCharset, charset);
})
@ -305,7 +305,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item("attachment");
item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
item.attachmentCharset = charset;
var itemID = yield item.save();
var itemID = yield item.saveTx();
item = yield Zotero.Items.getAsync(itemID);
// Set charset to same value
@ -320,20 +320,20 @@ describe("Zotero.Item", function () {
// Create parent item
var item = new Zotero.Item("book");
var parentItemID = yield item.save();
var parentItemID = yield item.saveTx();
// Create attachment item
var item = new Zotero.Item("attachment");
item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
item.parentID = parentItemID;
var itemID = yield item.save();
var itemID = yield item.saveTx();
// Should be empty when unset
assert.equal(item.attachmentFilename, '');
// Set filename
item.attachmentFilename = filename;
yield item.save();
yield item.saveTx();
item = yield Zotero.Items.getAsync(itemID);
// Check filename
@ -358,7 +358,7 @@ describe("Zotero.Item", function () {
];
var item = new Zotero.Item('journalArticle');
item.setTags(tags);
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadTags();
assert.sameDeepMembers(item.getTags(tags), tags);
@ -375,7 +375,7 @@ describe("Zotero.Item", function () {
];
var item = new Zotero.Item('journalArticle');
item.setTags(tags);
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadTags();
item.setTags(tags);
@ -393,11 +393,11 @@ describe("Zotero.Item", function () {
];
var item = new Zotero.Item('journalArticle');
item.setTags(tags);
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadTags();
item.setTags(tags.slice(0));
yield item.save();
yield item.saveTx();
assert.sameDeepMembers(item.getTags(tags), tags.slice(0));
})
})

View file

@ -7,7 +7,7 @@ describe("Zotero.ItemTreeView", function() {
itemsView = win.ZoteroPane.itemsView;
var item = new Zotero.Item('book');
existingItemID = yield item.save();
existingItemID = yield item.saveTx();
});
after(function () {
win.close();
@ -47,7 +47,7 @@ describe("Zotero.ItemTreeView", function() {
// Create item
var item = new Zotero.Item('book');
var id = yield item.save();
var id = yield item.saveTx();
// New item should be selected
var selected = itemsView.getSelectedItems();
@ -71,7 +71,7 @@ describe("Zotero.ItemTreeView", function() {
// Create item with skipNotifier flag
var item = new Zotero.Item('book');
var id = yield item.save({
var id = yield item.saveTx({
skipNotifier: true
});
@ -96,7 +96,7 @@ describe("Zotero.ItemTreeView", function() {
// Create item with skipSelect flag
var item = new Zotero.Item('book');
var id = yield item.save({
var id = yield item.saveTx({
skipSelect: true
});
@ -114,7 +114,7 @@ describe("Zotero.ItemTreeView", function() {
it("shouldn't select a modified item", function* () {
// Create item
var item = new Zotero.Item('book');
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
itemsView.selection.clearSelection();
@ -124,7 +124,7 @@ describe("Zotero.ItemTreeView", function() {
// Modify item
item.setField('title', 'no select on modify');
yield item.save();
yield item.saveTx();
// itemSelected should have been called once (from 'selectEventsSuppressed = false'
// in notify()) as a no-op
@ -138,7 +138,7 @@ describe("Zotero.ItemTreeView", function() {
it("should maintain selection on a selected modified item", function* () {
// Create item
var item = new Zotero.Item('book');
var id = yield item.save();
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield itemsView.selectItem(id);
@ -151,7 +151,7 @@ describe("Zotero.ItemTreeView", function() {
// Modify item
item.setField('title', 'maintain selection on modify');
yield item.save();
yield item.saveTx();
// itemSelected should have been called once (from 'selectEventsSuppressed = false'
// in notify()) as a no-op

View file

@ -3,7 +3,7 @@ describe("Zotero.Search", function() {
it("should fail without a name", function* () {
var s = new Zotero.Search;
s.addCondition('title', 'is', 'test');
var e = yield getPromiseError(s.save());
var e = yield getPromiseError(s.saveTx());
assert.ok(e);
assert.equal(e.constructor.name, Error.prototype.constructor.name); // TEMP: Error mismatch
assert.equal(e.message, "Name not provided for saved search");
@ -14,7 +14,7 @@ describe("Zotero.Search", function() {
var s = new Zotero.Search;
s.name = "Test";
s.addCondition('title', 'is', 'test');
var id = yield s.save();
var id = yield s.saveTx();
assert.typeOf(id, 'number');
// Check saved search
@ -40,14 +40,14 @@ describe("Zotero.Search", function() {
s.libraryID = Zotero.Libraries.userLibraryID;
s.name = "Test";
s.addCondition('title', 'is', 'test');
var id = yield s.save();
var id = yield s.saveTx();
assert.typeOf(id, 'number');
// Add condition
s = yield Zotero.Searches.getAsync(id);
yield s.loadConditions();
s.addCondition('title', 'contains', 'foo');
var saved = yield s.save();
var saved = yield s.saveTx();
assert.isTrue(saved);
// Check saved search
@ -64,14 +64,14 @@ describe("Zotero.Search", function() {
s.name = "Test";
s.addCondition('title', 'is', 'test');
s.addCondition('title', 'contains', 'foo');
var id = yield s.save();
var id = yield s.saveTx();
assert.typeOf(id, 'number');
// Remove condition
s = yield Zotero.Searches.getAsync(id);
yield s.loadConditions();
s.removeCondition(0);
var saved = yield s.save();
var saved = yield s.saveTx();
assert.isTrue(saved);
// Check saved search

View file

@ -39,7 +39,7 @@ describe("ZoteroPane", function() {
it("should update the item count", function* () {
var collection = new Zotero.Collection;
collection.name = "Count Test";
var id = yield collection.save();
var id = yield collection.saveTx();
yield waitForItemsLoad(win);
// Unselected, with no items in view
@ -51,7 +51,7 @@ describe("ZoteroPane", function() {
// Unselected, with one item in view
var item = new Zotero.Item('newspaperArticle');
item.setCollections([id]);
var itemID1 = yield item.save({
var itemID1 = yield item.saveTx({
skipSelect: true
});
assert.equal(
@ -62,7 +62,7 @@ describe("ZoteroPane", function() {
// Unselected, with multiple items in view
var item = new Zotero.Item('audioRecording');
item.setCollections([id]);
var itemID2 = yield item.save({
var itemID2 = yield item.saveTx({
skipSelect: true
});
assert.equal(