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

View file

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

View file

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

View file

@ -45,7 +45,7 @@ Zotero.Attachments = new function(){
throw ("'" + file.leafName + "' must be a file in Zotero.Attachments.importFromFile()"); throw ("'" + file.leafName + "' must be a file in Zotero.Attachments.importFromFile()");
} }
var itemID, newFile; var itemID, newFile, contentType;
yield Zotero.DB.executeTransaction(function* () { yield Zotero.DB.executeTransaction(function* () {
// Create a new attachment // Create a new attachment
var attachmentItem = new Zotero.Item('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 // Copy file to unique filename, which automatically shortens long filenames
newFile = Zotero.File.copyToUnique(file, newFile); newFile = Zotero.File.copyToUnique(file, newFile);
var contentType = yield Zotero.MIME.getMIMETypeFromFile(newFile); contentType = yield Zotero.MIME.getMIMETypeFromFile(newFile);
attachmentItem.attachmentContentType = contentType; attachmentItem.attachmentContentType = contentType;
attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_FILE); attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_FILE);
yield attachmentItem.save(); yield attachmentItem.save();
// Determine charset and build fulltext index
yield _postProcessFile(itemID, newFile, contentType);
}.bind(this)) }.bind(this))
.then(function () {
return _postProcessFile(itemID, newFile, contentType);
})
.catch(function (e) { .catch(function (e) {
Zotero.debug(e, 1); Zotero.debug(e, 1);
var msg = "Failed importing file " + file.path; var msg = "Failed importing file " + file.path;
@ -112,21 +112,21 @@ Zotero.Attachments = new function(){
var title = file.leafName; var title = file.leafName;
var contentType = yield Zotero.MIME.getMIMETypeFromFile(file); var contentType = yield Zotero.MIME.getMIMETypeFromFile(file);
var itemID;
return Zotero.DB.executeTransaction(function* () { return Zotero.DB.executeTransaction(function* () {
var itemID = yield _addToDB({ itemID = yield _addToDB({
file: file, file: file,
title: title, title: title,
linkMode: this.LINK_MODE_LINKED_FILE, linkMode: this.LINK_MODE_LINKED_FILE,
contentType: contentType, contentType: contentType,
parentItemID: parentItemID parentItemID: parentItemID
}); });
}.bind(this))
// Determine charset and build fulltext index .then(function () {
yield _postProcessFile(itemID, file, contentType); return _postProcessFile(itemID, file, contentType);
})
return itemID; .then(() => itemID);
}.bind(this));
}); });
@ -148,7 +148,7 @@ Zotero.Attachments = new function(){
throw new Error("parentItemID not provided"); throw new Error("parentItemID not provided");
} }
var destDir; var itemID, destDir, newFile;
yield Zotero.DB.executeTransaction(function* () { yield Zotero.DB.executeTransaction(function* () {
// Create a new attachment // Create a new attachment
var attachmentItem = new Zotero.Item('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 // DEBUG: this should probably insert access date too so as to
// create a proper item, but at the moment this is only called by // create a proper item, but at the moment this is only called by
// translate.js, which sets the metadata fields itself // translate.js, which sets the metadata fields itself
var itemID = yield attachmentItem.save(); itemID = yield attachmentItem.save();
attachmentItem = yield Zotero.Items.getAsync(itemID) attachmentItem = yield Zotero.Items.getAsync(itemID)
destDir = this.getStorageDirectory(attachmentItem); destDir = this.getStorageDirectory(attachmentItem);
@ -172,15 +172,15 @@ Zotero.Attachments = new function(){
file.parent.copyTo(storageDir, destDir.leafName); file.parent.copyTo(storageDir, destDir.leafName);
// Point to copied file // Point to copied file
var newFile = destDir.clone(); newFile = destDir.clone();
newFile.append(file.leafName); newFile.append(file.leafName);
attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_URL); attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_URL);
yield attachmentItem.save(); yield attachmentItem.save();
// Determine charset and build fulltext index
yield _postProcessFile(itemID, newFile, contentType);
}.bind(this)) }.bind(this))
.then(function () {
return _postProcessFile(itemID, newFile, contentType);
})
.catch(function (e) { .catch(function (e) {
Zotero.debug(e, 1); Zotero.debug(e, 1);
@ -1130,7 +1130,7 @@ Zotero.Attachments = new function(){
if (parentItemID) { if (parentItemID) {
newAttachment.parentID = parentItemID; newAttachment.parentID = parentItemID;
} }
yield newAttachment.save(); yield newAttachment.saveTx();
// Copy over files if they exist // Copy over files if they exist
if (newAttachment.isImportedAttachment() && attachment.getFile()) { if (newAttachment.isImportedAttachment() && attachment.getFile()) {
@ -1362,7 +1362,7 @@ Zotero.Attachments = new function(){
var item = yield Zotero.Items.getAsync(itemID); var item = yield Zotero.Items.getAsync(itemID);
charset = yield Zotero.CharacterSets.add(charset); charset = yield Zotero.CharacterSets.add(charset);
item.attachmentCharset = charset; item.attachmentCharset = charset;
yield item.save(); yield item.saveTx();
if (disabled) { if (disabled) {
Zotero.Notifier.enable(); 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 * 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 {Object} data Creator data in API JSON format
* @param {Boolean} [create=false] If no matching creator, create one * @param {Boolean} [create=false] If no matching creator, create one
* @return {Promise<Integer>} creatorID * @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); data = this.cleanData(data);
return Zotero.DB.executeTransaction(function* () {
var sql = "SELECT creatorID FROM creators WHERE " var sql = "SELECT creatorID FROM creators WHERE "
+ "firstName=? AND lastName=? AND fieldMode=?"; + "firstName=? AND lastName=? AND fieldMode=?";
var id = yield Zotero.DB.valueQueryAsync( var id = yield Zotero.DB.valueQueryAsync(
@ -95,7 +96,6 @@ Zotero.Creators = new function() {
} }
return id; return id;
}); });
});
this.updateCreator = Zotero.Promise.coroutine(function* (creatorID, creatorData) { 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 * TRUE on item update, or FALSE if item was unchanged
*/ */
Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options) { Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options) {
options = options || {};
var env = { var env = {
options: options || {}, options: options,
transactionOptions: {} 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); var proceed = yield this._initSave(env);
if (!proceed) return false; 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); Zotero.debug('Updating database with new ' + this._objectType + ' data', 4);
} }
return Zotero.DB.executeTransaction(function* () { try {
if (Zotero.DataObject.prototype._finalizeSave == this._finalizeSave) { if (Zotero.DataObject.prototype._finalizeSave == this._finalizeSave) {
throw new Error("_finalizeSave not implement for Zotero." + this._ObjectType); throw new Error("_finalizeSave not implement for Zotero." + this._ObjectType);
} }
@ -574,26 +582,48 @@ Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options)
if (!env.isNew) { if (!env.isNew) {
env.changed = this._previousData; env.changed = this._previousData;
} }
// Create transaction
if (env.options.tx) {
let result = yield Zotero.DB.executeTransaction(function* () {
yield this._saveData(env); yield this._saveData(env);
yield Zotero.DataObject.prototype._finalizeSave.call(this, env); yield Zotero.DataObject.prototype._finalizeSave.call(this, env);
return this._finalizeSave(env); return this._finalizeSave(env);
}.bind(this), env.transactionOptions) }.bind(this), env.transactionOptions);
.catch(e => { 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) return this._recoverFromSaveError(env, e)
.catch(function(e2) { .catch(function(e2) {
Zotero.debug(e2, 1); Zotero.debug(e2, 1);
}) })
.then(function() { .then(function() {
if (options.errorHandler) { if (env.options.errorHandler) {
options.errorHandler(e); env.options.errorHandler(e);
} }
else { else {
Zotero.debug(e, 1); Zotero.debug(e, 1);
} }
throw e; throw e;
}) })
}
}); });
});
Zotero.DataObject.prototype.saveTx = function (options) {
options = options || {};
options.tx = true;
return this.save(options);
}
Zotero.DataObject.prototype.hasChanged = function() { Zotero.DataObject.prototype.hasChanged = function() {
Zotero.debug(this._changed); Zotero.debug(this._changed);
@ -618,9 +648,15 @@ Zotero.DataObject.prototype._initSave = Zotero.Promise.coroutine(function* (env)
} }
// Undo registerIdentifiers() on failure // Undo registerIdentifiers() on failure
env.transactionOptions.onRollback = function () { var func = function () {
this.ObjectsClass.unload(env.id); this.ObjectsClass.unload(env.id);
}.bind(this); }.bind(this);
if (env.options.tx) {
env.transactionOptions.onRollback = func;
}
else {
Zotero.DB.addCurrentCallback("rollback", func);
}
return true; return true;
}); });
@ -653,11 +689,9 @@ Zotero.DataObject.prototype.erase = Zotero.Promise.coroutine(function* () {
Zotero.debug('Deleting ' + this.objectType + ' ' + this.id); Zotero.debug('Deleting ' + this.objectType + ' ' + this.id);
yield Zotero.DB.executeTransaction(function* () { Zotero.DB.requireTransaction();
yield this._eraseData(env); yield this._eraseData(env);
yield this._erasePreCommit(env); yield this._erasePreCommit(env);
}.bind(this));
return this._erasePostCommit(env); return this._erasePostCommit(env);
}); });

View file

@ -1173,6 +1173,11 @@ Zotero.Item.prototype.isEditable = function() {
} }
Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { 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 isNew = env.isNew;
var options = env.options; var options = env.options;
@ -1740,6 +1745,11 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
parentItem.clearBestAttachmentState(); 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) { Zotero.Item.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {

View file

@ -169,8 +169,8 @@ Zotero.Relations = function () {
* @param {String} prefix * @param {String} prefix
* @param {String[]} ignorePredicates * @param {String[]} ignorePredicates
*/ */
this.eraseByURIPrefix = function (prefix, ignorePredicates) { this.eraseByURIPrefix = Zotero.Promise.coroutine(function* (prefix, ignorePredicates) {
return Zotero.DB.executeTransaction(function* () { Zotero.DB.requireTransaction();
prefix = prefix + '%'; prefix = prefix + '%';
var sql = "SELECT ROWID FROM relations WHERE (subject LIKE ? OR object LIKE ?)"; var sql = "SELECT ROWID FROM relations WHERE (subject LIKE ? OR object LIKE ?)";
var params = [prefix, prefix]; var params = [prefix, prefix];
@ -187,15 +187,14 @@ Zotero.Relations = function () {
yield relation.load(); yield relation.load();
yield relation.erase(); yield relation.erase();
} }
}.bind(this)); });
}
/** /**
* @return {Promise} * @return {Promise}
*/ */
this.eraseByURI = function (uri, ignorePredicates) { this.eraseByURI = Zotero.Promise.coroutine(function* (uri, ignorePredicates) {
return Zotero.DB.executeTransaction(function* () { Zotero.DB.requireTransaction();
var sql = "SELECT ROWID FROM relations WHERE (subject=? OR object=?)"; var sql = "SELECT ROWID FROM relations WHERE (subject=? OR object=?)";
var params = [uri, uri]; var params = [uri, uri];
if (ignorePredicates) { if (ignorePredicates) {
@ -211,8 +210,7 @@ Zotero.Relations = function () {
yield relation.load(); yield relation.load();
yield relation.erase(); yield relation.erase();
} }
}.bind(this)); });
}
this.purge = Zotero.Promise.coroutine(function* () { 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 * Returns the tagID matching given fields, or creates a new tag and returns its id
* *
* @requireTransaction
* @param {Number} libraryID * @param {Number} libraryID
* @param {String} name - Tag data in API JSON format * @param {String} name - Tag data in API JSON format
* @param {Boolean} [create=false] - If no matching tag, create one * @param {Boolean} [create=false] - If no matching tag, create one
* @return {Promise<Integer>} tagID * @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({ data = this.cleanData({
tag: name tag: name
}); });
return Zotero.DB.executeTransaction(function* () {
var sql = "SELECT tagID FROM tags WHERE libraryID=? AND name=?"; var sql = "SELECT tagID FROM tags WHERE libraryID=? AND name=?";
var id = yield Zotero.DB.valueQueryAsync(sql, [libraryID, data.tag]); var id = yield Zotero.DB.valueQueryAsync(sql, [libraryID, data.tag]);
if (!id && create) { if (!id && create) {
@ -99,7 +100,6 @@ Zotero.Tags = new function() {
} }
return id; 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) { Zotero.DBConnection.prototype.removeCallback = function (type, id) {
switch (type) { switch (type) {
case 'begin': case 'begin':
@ -453,41 +459,16 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
} }
} }
if ((options.exclusive && this._inTransaction) || this._inExclusiveTransaction) { var startedTransaction = false;
yield Zotero.DB.waitForTransaction();
}
try { try {
if (this._inTransaction) { while (this._inTransaction) {
Zotero.debug("Async DB transaction in progress -- increasing level to " yield Zotero.DB.waitForTransaction().timeout(options.waitTimeout || 10000);
+ ++this._asyncTransactionNestingLevel, 5); }
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); Zotero.debug("Beginning async DB transaction", 5);
this._inTransaction = true;
this._inExclusiveTransaction = options.exclusive;
this._transactionPromise = new Zotero.Promise(function () { this._transactionPromise = new Zotero.Promise(function () {
resolve = arguments[0]; 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 conn = this._getConnection(options) || (yield this._getConnectionAsync(options));
var result = yield conn.executeTransaction(func); var result = yield conn.executeTransaction(func);
Zotero.debug("Committed async DB transaction", 5); Zotero.debug("Committed async DB transaction", 5);
this._inTransaction = false;
// Clear transaction time // Clear transaction time
if (this._transactionDate) { if (this._transactionDate) {
this._transactionDate = null; 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 // Function to run once transaction has been committed but before any
// permanent callbacks // permanent callbacks
if (options.onCommit) { if (options.onCommit) {
@ -519,12 +505,6 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
} }
this._callbacks.current.rollback = []; this._callbacks.current.rollback = [];
if (options.vacuumOnCommit) {
Zotero.debug('Vacuuming database');
yield Zotero.DB.queryAsync('VACUUM');
}
}
// Run temporary commit callbacks // Run temporary commit callbacks
var f; var f;
while (f = this._callbacks.current.commit.shift()) { while (f = this._callbacks.current.commit.shift()) {
@ -540,20 +520,23 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
return result; return result;
} }
}
catch (e) { 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("Rolled back async DB transaction", 5);
Zotero.debug(e, 1); Zotero.debug(e, 1);
}
if (startedTransaction) {
this._inTransaction = false; this._inTransaction = false;
this._inExclusiveTransaction = false; }
if (options) {
// Function to run once transaction has been committed but before any // Function to run once transaction has been committed but before any
// permanent callbacks // permanent callbacks
if (options.onRollback) { if (options.onRollback) {
this._callbacks.current.rollback.push(options.onRollback); this._callbacks.current.rollback.push(options.onRollback);
} }
}
// Run temporary commit callbacks // Run temporary commit callbacks
var f; 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 () { Zotero.DBConnection.prototype.waitForTransaction = function () {
if (!this._inTransaction) { if (!this._inTransaction) {
return Zotero.Promise.resolve(); return Zotero.Promise.resolve().cancellable();
} }
Zotero.debug("Waiting for transaction to finish"); Zotero.debug("Waiting for transaction to finish");
Zotero.debug((new Error).stack);
return this._transactionPromise; 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 {String} sql SQL statement to run
* @param {Array|String|Integer} [params] SQL parameters to bind * @param {Array|String|Integer} [params] SQL parameters to bind
@ -818,7 +814,7 @@ Zotero.DBConnection.prototype.tableExists = function (table) {
* *
* @return {Promise} * @return {Promise}
*/ */
Zotero.DBConnection.prototype.executeSQLFile = function (sql) { Zotero.DBConnection.prototype.executeSQLFile = Zotero.Promise.coroutine(function* (sql) {
var nonCommentRE = /^[^-]/; var nonCommentRE = /^[^-]/;
var trailingCommentRE = /^(.*?)(?:--.+)?$/; var trailingCommentRE = /^(.*?)(?:--.+)?$/;
@ -836,13 +832,13 @@ Zotero.DBConnection.prototype.executeSQLFile = function (sql) {
var statements = sql.split(";") var statements = sql.split(";")
.map(function (x) x.replace(/TEMPSEMI/g, ";")); .map(function (x) x.replace(/TEMPSEMI/g, ";"));
return this.executeTransaction(function* () { this.requireTransaction();
var statement; var statement;
while (statement = statements.shift()) { while (statement = statements.shift()) {
yield Zotero.DB.queryAsync(statement); yield Zotero.DB.queryAsync(statement);
} }
}); });
}
/* /*

View file

@ -403,13 +403,14 @@ Zotero.Fulltext = new function(){
/** /**
* Index multiple words at once * Index multiple words at once
* *
* @requireTransaction
* @param {Number} itemID * @param {Number} itemID
* @param {Array<string>} words * @param {Array<string>} words
* @return {Promise} * @return {Promise}
*/ */
function indexWords(itemID, words) { var indexWords = Zotero.Promise.coroutine(function* (itemID, words) {
Zotero.DB.requireTransaction();
let chunk; let chunk;
return Zotero.DB.executeTransaction(function* () {
yield Zotero.DB.queryAsync("DELETE FROM indexing.fulltextWords"); yield Zotero.DB.queryAsync("DELETE FROM indexing.fulltextWords");
while (words.length > 0) { while (words.length > 0) {
chunk = words.splice(0, 100); 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('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("REPLACE INTO fulltextItems (itemID, version) VALUES (?,?)", [itemID, 0]);
yield Zotero.DB.queryAsync("DELETE FROM indexing.fulltextWords"); 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); yield indexString(text, charset, itemID);
// Record the number of characters indexed (unless we're indexing a (PDF) cache file, // Record the number of characters indexed (unless we're indexing a (PDF) cache file,
@ -567,7 +566,6 @@ Zotero.Fulltext = new function(){
if (!isCacheFile) { if (!isCacheFile) {
yield setChars(itemID, { indexed: text.length, total: totalChars }); yield setChars(itemID, { indexed: text.length, total: totalChars });
} }
});
return true; return true;
}.bind(this)); }.bind(this));
@ -690,10 +688,8 @@ Zotero.Fulltext = new function(){
return false; return false;
} }
yield Zotero.DB.executeTransaction(function* () {
yield indexFile(cacheFilePath, 'text/plain', 'utf-8', itemID, true, true); yield indexFile(cacheFilePath, 'text/plain', 'utf-8', itemID, true, true);
yield setPages(itemID, { indexed: pagesIndexed, total: totalPages }); yield setPages(itemID, { indexed: pagesIndexed, total: totalPages });
});
return true; return true;
}); });
@ -827,7 +823,9 @@ Zotero.Fulltext = new function(){
+ libraryKey, 2); + libraryKey, 2);
// Delete rows for items that weren't supposed to be indexed // Delete rows for items that weren't supposed to be indexed
yield Zotero.DB.executeTransaction(function* () {
yield this.clearItemWords(itemID); yield this.clearItemWords(itemID);
}.bind(this));
continue; continue;
} }
@ -1291,16 +1289,18 @@ Zotero.Fulltext = new function(){
}); });
/**
* @requireTransaction
*/
this.clearItemWords = Zotero.Promise.coroutine(function* (itemID, skipCacheClear) { 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 sql = "SELECT rowid FROM fulltextItems WHERE itemID=? LIMIT 1";
var indexed = yield Zotero.DB.valueQueryAsync(sql, itemID); var indexed = yield Zotero.DB.valueQueryAsync(sql, itemID);
if (indexed) { if (indexed) {
yield Zotero.DB.queryAsync("DELETE FROM fulltextItemWords WHERE itemID=?", itemID); yield Zotero.DB.queryAsync("DELETE FROM fulltextItemWords WHERE itemID=?", itemID);
yield Zotero.DB.queryAsync("DELETE FROM fulltextItems WHERE itemID=?", itemID); yield Zotero.DB.queryAsync("DELETE FROM fulltextItems WHERE itemID=?", itemID);
} }
return indexed;
}.bind(this));
if (indexed) { if (indexed) {
Zotero.Prefs.set('purge.fulltext', true); 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 // Update custom tables if they exist so that changes are in
// place before user data migration // place before user data migration
if (Zotero.DB.tableExists('customItemTypes')) { if (Zotero.DB.tableExists('customItemTypes')) {
yield Zotero.Schema.updateCustomTables(updated); yield _updateCustomTables(updated);
} }
updated = yield _migrateUserDataSchema(userdata); updated = yield _migrateUserDataSchema(userdata);
yield _updateSchema('triggers'); 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; return updated;
}.bind(this)); }.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) { if (updated) {
// Upgrade seems to have been a success -- delete any previous backups // Upgrade seems to have been a success -- delete any previous backups
var maxPrevious = userdata - 1; var maxPrevious = userdata - 1;
@ -318,7 +316,7 @@ Zotero.Schema = new function(){
}); });
var _reloadSchema = Zotero.Promise.coroutine(function* () { var _reloadSchema = Zotero.Promise.coroutine(function* () {
yield Zotero.Schema.updateCustomTables(); yield _updateCustomTables();
yield Zotero.ItemTypes.load(); yield Zotero.ItemTypes.load();
yield Zotero.ItemFields.load(); yield Zotero.ItemFields.load();
yield Zotero.SearchConditions.init(); yield Zotero.SearchConditions.init();
@ -335,10 +333,11 @@ Zotero.Schema = new function(){
}); });
this.updateCustomTables = function (skipDelete, skipSystem) { var _updateCustomTables = Zotero.Promise.coroutine(function* (skipDelete, skipSystem) {
return Zotero.DB.executeTransaction(function* (conn) {
Zotero.debug("Updating custom tables"); Zotero.debug("Updating custom tables");
Zotero.DB.requireTransaction();
if (!skipDelete) { if (!skipDelete) {
yield Zotero.DB.queryAsync("DELETE FROM itemTypesCombined"); yield Zotero.DB.queryAsync("DELETE FROM itemTypesCombined");
yield Zotero.DB.queryAsync("DELETE FROM fieldsCombined WHERE fieldID NOT IN (SELECT fieldID FROM itemData)"); 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" + "customFieldID + " + offset + " AS fieldID FROM customBaseFieldMappings"
); );
}); });
}
/** /**
@ -1454,7 +1452,7 @@ Zotero.Schema = new function(){
yield _getSchemaSQL('triggers').then(function (sql) { yield _getSchemaSQL('triggers').then(function (sql) {
return Zotero.DB.executeSQLFile(sql); return Zotero.DB.executeSQLFile(sql);
}); });
yield Zotero.Schema.updateCustomTables(true); yield _updateCustomTables(true);
yield _getSchemaSQLVersion('system').then(function (version) { yield _getSchemaSQLVersion('system').then(function (version) {
return _updateDBVersion('system', 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 // 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) { var _migrateUserDataSchema = Zotero.Promise.coroutine(function* (fromVersion) {
return _getSchemaSQLVersion('userdata') var toVersion = yield _getSchemaSQLVersion('userdata');
.then(function (toVersion) {
if (fromVersion >= toVersion) { if (fromVersion >= toVersion) {
return false; return false;
} }
Zotero.debug('Updating user data tables from version ' + fromVersion + ' to ' + toVersion); 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 // Step through version changes until we reach the current version
// //
// Each block performs the changes necessary to move from the // 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 itemsOld");
yield Zotero.DB.queryAsync("DROP TABLE tagsOld"); yield Zotero.DB.queryAsync("DROP TABLE tagsOld");
} }
}
yield _updateDBVersion('userdata', toVersion); yield _updateDBVersion('userdata', toVersion);
})
.return(true); return true;
})
} }
});
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,14 +9,15 @@ describe("Zotero.DB", function() {
}); });
beforeEach(function* () { beforeEach(function* () {
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS " + tmpTable); yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS " + tmpTable);
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
}); });
after(function* () { after(function* () {
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS " + tmpTable); yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS " + tmpTable);
}); });
describe("#executeTransaction()", function () { describe("#executeTransaction()", function () {
it("should nest concurrent transactions", Zotero.Promise.coroutine(function* () { it("should serialize concurrent transactions", Zotero.Promise.coroutine(function* () {
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)"); this.timeout(1000);
var resolve1, resolve2, reject1, reject2; var resolve1, resolve2, reject1, reject2;
var promise1 = new Promise(function (resolve, reject) { var promise1 = new Promise(function (resolve, reject) {
@ -29,18 +30,21 @@ describe("Zotero.DB", function() {
}); });
Zotero.DB.executeTransaction(function* () { Zotero.DB.executeTransaction(function* () {
yield Zotero.Promise.delay(100); yield Zotero.Promise.delay(250);
assert.equal(Zotero.DB._asyncTransactionNestingLevel, 0); var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)"); assert.equal(num, 0);
Zotero.DB.transactionDate; // Make sure we're still in a transaction yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
assert.ok(Zotero.DB.inTransaction());
}) })
.then(resolve1) .then(resolve1)
.catch(reject1); .catch(reject1);
Zotero.DB.executeTransaction(function* () { Zotero.DB.executeTransaction(function* () {
assert.equal(Zotero.DB._asyncTransactionNestingLevel, 1); var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)"); assert.equal(num, 1);
Zotero.DB.transactionDate; // Make sure we're still in a transaction yield Zotero.Promise.delay(500);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)");
assert.ok(Zotero.DB.inTransaction());
}) })
.then(resolve2) .then(resolve2)
.catch(reject2); .catch(reject2);
@ -48,10 +52,8 @@ describe("Zotero.DB", function() {
yield Zotero.Promise.all([promise1, promise2]); yield Zotero.Promise.all([promise1, promise2]);
})); }));
it("shouldn't nest transactions if an exclusive transaction is open", Zotero.Promise.coroutine(function* () { it("should serialize queued transactions", function* () {
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)"); var resolve1, resolve2, reject1, reject2, resolve3, reject3;
var resolve1, resolve2, reject1, reject2;
var promise1 = new Promise(function (resolve, reject) { var promise1 = new Promise(function (resolve, reject) {
resolve1 = resolve; resolve1 = resolve;
reject1 = reject; reject1 = reject;
@ -60,66 +62,48 @@ describe("Zotero.DB", function() {
resolve2 = resolve; resolve2 = resolve;
reject2 = reject; reject2 = reject;
}); });
var promise3 = new Promise(function (resolve, reject) {
resolve3 = resolve;
reject3 = reject;
});
// Start a transaction and have it delay
Zotero.DB.executeTransaction(function* () { Zotero.DB.executeTransaction(function* () {
yield Zotero.Promise.delay(100); 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)"); yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
Zotero.DB.transactionDate; // Make sure we're still in a transaction assert.ok(Zotero.DB.inTransaction());
}, { exclusive: true }) })
.then(resolve1) .then(resolve1)
.catch(reject1); .catch(reject1);
// Start two more transactions, which should wait on the first
Zotero.DB.executeTransaction(function* () { Zotero.DB.executeTransaction(function* () {
assert.equal(Zotero.DB._asyncTransactionNestingLevel, 0);
var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable); var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable);
assert.equal(num, 1); assert.equal(num, 1);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)"); 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) .then(resolve2)
.catch(reject2); .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* () { 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); var num = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM " + tmpTable);
assert.equal(num, 1); assert.equal(num, 2);
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)"); yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (3)");
Zotero.DB.transactionDate; // Make sure we're still in a transaction // But make sure the second queued transaction doesn't start at the same time,
}, { exclusive: true }) // such that the first queued transaction gets closed while the second is still
.then(resolve2) // running
.catch(reject2); 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* () { 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)"); yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
try { try {
yield Zotero.DB.executeTransaction(function* () { yield Zotero.DB.executeTransaction(function* () {
@ -141,7 +125,6 @@ describe("Zotero.DB", function() {
it("should run onRollback callbacks", function* () { it("should run onRollback callbacks", function* () {
var callbackRan = false; var callbackRan = false;
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
try { try {
yield Zotero.DB.executeTransaction( yield Zotero.DB.executeTransaction(
function* () { function* () {
@ -163,25 +146,31 @@ describe("Zotero.DB", function() {
yield Zotero.DB.queryAsync("DROP TABLE " + tmpTable); 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 callback1Ran = false;
var callback2Ran = false; var callback2Ran = false;
yield Zotero.DB.queryAsync("CREATE TABLE " + tmpTable + " (foo INT)");
try { try {
yield Zotero.DB.executeTransaction(function* () { yield Zotero.DB.executeTransaction(function* () {
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (1)");
yield Zotero.DB.executeTransaction( yield Zotero.DB.executeTransaction(
function* () { function* () {},
yield Zotero.DB.queryAsync("INSERT INTO " + tmpTable + " VALUES (2)");
throw 'Aborting transaction -- ignore';
},
{ {
waitTimeout: 100,
onRollback: function () { onRollback: function () {
callback1Ran = true; callback1Ran = true;
} }
} }
); )
}, },
{ {
onRollback: function () { onRollback: function () {
@ -190,32 +179,10 @@ describe("Zotero.DB", function() {
}); });
} }
catch (e) { catch (e) {
if (typeof e != 'string' || !e.startsWith('Aborting transaction')) throw e; if (e.name != "TimeoutError") throw e;
} }
assert.ok(callback1Ran); assert.ok(callback1Ran);
assert.ok(callback2Ran); 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 fieldID = Zotero.ItemFields.getID(field);
var item = new Zotero.Item('book'); var item = new Zotero.Item('book');
item.setField(field, 'Foo'); item.setField(field, 'Foo');
id = yield item.save(); id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
item.setField(field, ""); item.setField(field, "");
@ -57,7 +57,7 @@ describe("Zotero.Item", function () {
assert.ok(item._changed.itemData[fieldID]); assert.ok(item._changed.itemData[fieldID]);
assert.ok(item.hasChanged()); assert.ok(item.hasChanged());
yield item.save(); yield item.saveTx();
assert.isFalse(item.getField(fieldID)); assert.isFalse(item.getField(fieldID));
}) })
@ -70,7 +70,7 @@ describe("Zotero.Item", function () {
it("should save version as object version", function* () { it("should save version as object version", function* () {
var item = new Zotero.Item('book'); var item = new Zotero.Item('book');
item.setField("version", 1); item.setField("version", 1);
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
assert.equal(item.getField("version"), 1); assert.equal(item.getField("version"), 1);
}); });
@ -78,7 +78,7 @@ describe("Zotero.Item", function () {
it("should save versionNumber for computerProgram", function () { it("should save versionNumber for computerProgram", function () {
var item = new Zotero.Item('computerProgram'); var item = new Zotero.Item('computerProgram');
item.setField("versionNumber", "1.0"); item.setField("versionNumber", "1.0");
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
assert.equal(item.getField("versionNumber"), "1.0"); assert.equal(item.getField("versionNumber"), "1.0");
}); });
@ -89,7 +89,7 @@ describe("Zotero.Item", function () {
var dateModified = "2015-05-05 17:18:12"; var dateModified = "2015-05-05 17:18:12";
var item = new Zotero.Item('book'); var item = new Zotero.Item('book');
item.dateModified = dateModified; item.dateModified = dateModified;
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
assert.equal(item.dateModified, dateModified); assert.equal(item.dateModified, dateModified);
}) })
@ -98,7 +98,7 @@ describe("Zotero.Item", function () {
var dateModified = "2015-05-05 17:18:12"; var dateModified = "2015-05-05 17:18:12";
var item = new Zotero.Item('book'); var item = new Zotero.Item('book');
item.dateModified = dateModified; item.dateModified = dateModified;
var id = yield item.save({ var id = yield item.saveTx({
skipDateModifiedUpdate: true skipDateModifiedUpdate: true
}); });
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
@ -109,13 +109,13 @@ describe("Zotero.Item", function () {
var dateModified = "2015-05-05 17:18:12"; var dateModified = "2015-05-05 17:18:12";
var item = new Zotero.Item('book'); var item = new Zotero.Item('book');
item.dateModified = dateModified; item.dateModified = dateModified;
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
// Save again without changing Date Modified // Save again without changing Date Modified
yield item.loadItemData(); yield item.loadItemData();
item.setField('title', 'Test'); item.setField('title', 'Test');
yield item.save() yield item.saveTx()
assert.closeTo(Zotero.Date.sqlToDate(item.dateModified, true).getTime(), Date.now(), 1000); 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 dateModified = "2015-05-05 17:18:12";
var item = new Zotero.Item('book'); var item = new Zotero.Item('book');
item.dateModified = dateModified; item.dateModified = dateModified;
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
// Set Date Modified to existing value // Set Date Modified to existing value
yield item.loadItemData(); yield item.loadItemData();
item.setField('title', 'Test'); item.setField('title', 'Test');
item.dateModified = dateModified; item.dateModified = dateModified;
yield item.save() yield item.saveTx()
assert.closeTo(Zotero.Date.sqlToDate(item.dateModified, true).getTime(), Date.now(), 1000); 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* () { 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 item = new Zotero.Item('book');
var id = yield item.save({ var id = yield item.saveTx({
skipDateModifiedUpdate: true skipDateModifiedUpdate: true
}); });
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
@ -148,13 +148,13 @@ describe("Zotero.Item", function () {
var dateModified = "2015-05-05 17:18:12"; var dateModified = "2015-05-05 17:18:12";
var item = new Zotero.Item('book'); var item = new Zotero.Item('book');
item.dateModified = dateModified; item.dateModified = dateModified;
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
// Resave with skipDateModifiedUpdate // Resave with skipDateModifiedUpdate
yield item.loadItemData(); yield item.loadItemData();
item.setField('title', 'Test'); item.setField('title', 'Test');
yield item.save({ yield item.saveTx({
skipDateModifiedUpdate: true skipDateModifiedUpdate: true
}) })
assert.equal(item.dateModified, dateModified); assert.equal(item.dateModified, dateModified);
@ -164,11 +164,11 @@ describe("Zotero.Item", function () {
describe("#parentID", function () { describe("#parentID", function () {
it("should create a child note", function* () { it("should create a child note", function* () {
var item = new Zotero.Item('book'); var item = new Zotero.Item('book');
var parentItemID = yield item.save(); var parentItemID = yield item.saveTx();
item = new Zotero.Item('note'); item = new Zotero.Item('note');
item.parentID = parentItemID; item.parentID = parentItemID;
var childItemID = yield item.save(); var childItemID = yield item.saveTx();
item = yield Zotero.Items.getAsync(childItemID); item = yield Zotero.Items.getAsync(childItemID);
assert.ok(item.parentID); assert.ok(item.parentID);
@ -198,7 +198,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item('attachment'); var item = new Zotero.Item('attachment');
item.attachmentLinkMode = 'linked_url'; item.attachmentLinkMode = 'linked_url';
item.url = "https://www.zotero.org/"; item.url = "https://www.zotero.org/";
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
item.parentKey = false; item.parentKey = false;
@ -207,15 +207,15 @@ describe("Zotero.Item", function () {
it("should move a top-level note under another item", function* () { it("should move a top-level note under another item", function* () {
var noteItem = new Zotero.Item('note'); var noteItem = new Zotero.Item('note');
var id = yield noteItem.save() var id = yield noteItem.saveTx()
noteItem = yield Zotero.Items.getAsync(id); noteItem = yield Zotero.Items.getAsync(id);
var item = new Zotero.Item('book'); var item = new Zotero.Item('book');
id = yield item.save(); id = yield item.saveTx();
var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(id); var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(id);
noteItem.parentKey = key; noteItem.parentKey = key;
yield noteItem.save(); yield noteItem.saveTx();
assert.isFalse(noteItem.isTopLevelItem()); assert.isFalse(noteItem.isTopLevelItem());
}) })
@ -224,19 +224,19 @@ describe("Zotero.Item", function () {
// Create a collection // Create a collection
var collection = new Zotero.Collection; var collection = new Zotero.Collection;
collection.name = "Test"; collection.name = "Test";
var collectionID = yield collection.save(); var collectionID = yield collection.saveTx();
// Create a top-level note and add it to a collection // Create a top-level note and add it to a collection
var noteItem = new Zotero.Item('note'); var noteItem = new Zotero.Item('note');
noteItem.addToCollection(collectionID); noteItem.addToCollection(collectionID);
var id = yield noteItem.save() var id = yield noteItem.saveTx()
noteItem = yield Zotero.Items.getAsync(id); noteItem = yield Zotero.Items.getAsync(id);
var item = new Zotero.Item('book'); var item = new Zotero.Item('book');
id = yield item.save(); id = yield item.saveTx();
var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(id); var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(id);
noteItem.parentKey = key; noteItem.parentKey = key;
yield noteItem.save(); yield noteItem.saveTx();
assert.isFalse(noteItem.isTopLevelItem()); assert.isFalse(noteItem.isTopLevelItem());
}) })
@ -258,7 +258,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item("journalArticle"); var item = new Zotero.Item("journalArticle");
item.setCreators(creators); item.setCreators(creators);
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
yield item.loadCreators(); yield item.loadCreators();
assert.sameDeepMembers(item.getCreatorsJSON(), creators); assert.sameDeepMembers(item.getCreatorsJSON(), creators);
@ -282,7 +282,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item("journalArticle"); var item = new Zotero.Item("journalArticle");
item.setCreators(creators); item.setCreators(creators);
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
yield item.loadCreators(); yield item.loadCreators();
assert.sameDeepMembers(item.getCreators(), creators); assert.sameDeepMembers(item.getCreators(), creators);
@ -295,7 +295,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item("attachment"); var item = new Zotero.Item("attachment");
item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE; item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
item.attachmentCharset = charset; item.attachmentCharset = charset;
var itemID = yield item.save(); var itemID = yield item.saveTx();
item = yield Zotero.Items.getAsync(itemID); item = yield Zotero.Items.getAsync(itemID);
assert.equal(item.attachmentCharset, charset); assert.equal(item.attachmentCharset, charset);
}) })
@ -305,7 +305,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item("attachment"); var item = new Zotero.Item("attachment");
item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE; item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
item.attachmentCharset = charset; item.attachmentCharset = charset;
var itemID = yield item.save(); var itemID = yield item.saveTx();
item = yield Zotero.Items.getAsync(itemID); item = yield Zotero.Items.getAsync(itemID);
// Set charset to same value // Set charset to same value
@ -320,20 +320,20 @@ describe("Zotero.Item", function () {
// Create parent item // Create parent item
var item = new Zotero.Item("book"); var item = new Zotero.Item("book");
var parentItemID = yield item.save(); var parentItemID = yield item.saveTx();
// Create attachment item // Create attachment item
var item = new Zotero.Item("attachment"); var item = new Zotero.Item("attachment");
item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE; item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
item.parentID = parentItemID; item.parentID = parentItemID;
var itemID = yield item.save(); var itemID = yield item.saveTx();
// Should be empty when unset // Should be empty when unset
assert.equal(item.attachmentFilename, ''); assert.equal(item.attachmentFilename, '');
// Set filename // Set filename
item.attachmentFilename = filename; item.attachmentFilename = filename;
yield item.save(); yield item.saveTx();
item = yield Zotero.Items.getAsync(itemID); item = yield Zotero.Items.getAsync(itemID);
// Check filename // Check filename
@ -358,7 +358,7 @@ describe("Zotero.Item", function () {
]; ];
var item = new Zotero.Item('journalArticle'); var item = new Zotero.Item('journalArticle');
item.setTags(tags); item.setTags(tags);
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
yield item.loadTags(); yield item.loadTags();
assert.sameDeepMembers(item.getTags(tags), tags); assert.sameDeepMembers(item.getTags(tags), tags);
@ -375,7 +375,7 @@ describe("Zotero.Item", function () {
]; ];
var item = new Zotero.Item('journalArticle'); var item = new Zotero.Item('journalArticle');
item.setTags(tags); item.setTags(tags);
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
yield item.loadTags(); yield item.loadTags();
item.setTags(tags); item.setTags(tags);
@ -393,11 +393,11 @@ describe("Zotero.Item", function () {
]; ];
var item = new Zotero.Item('journalArticle'); var item = new Zotero.Item('journalArticle');
item.setTags(tags); item.setTags(tags);
var id = yield item.save(); var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id); item = yield Zotero.Items.getAsync(id);
yield item.loadTags(); yield item.loadTags();
item.setTags(tags.slice(0)); item.setTags(tags.slice(0));
yield item.save(); yield item.saveTx();
assert.sameDeepMembers(item.getTags(tags), tags.slice(0)); assert.sameDeepMembers(item.getTags(tags), tags.slice(0));
}) })
}) })

View file

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

View file

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

View file

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