Functions to modify 'version'/'synced' efficiently, plus some other fixes

This commit is contained in:
Dan Stillman 2015-05-21 21:56:04 -04:00
parent cf3eed5f14
commit ada657fcb8
6 changed files with 184 additions and 25 deletions

View File

@ -771,6 +771,74 @@ Zotero.DataObject.prototype._recoverFromSaveError = Zotero.Promise.coroutine(fun
});
/**
* Update object version, efficiently
*
* Used by sync code
*
* @param {Integer} version
* @param {Boolean} [skipDB=false]
*/
Zotero.DataObject.prototype.updateVersion = Zotero.Promise.coroutine(function* (version, skipDB) {
if (!this.id) {
throw new Error("Cannot update version of unsaved " + this._objectType);
}
if (version != parseInt(version)) {
throw new Error("'version' must be an integer");
}
this._version = parseInt(version);
if (!skipDB) {
var cl = this.ObjectsClass;
var sql = "UPDATE " + cl.table + " SET version=? WHERE " + cl.idColumn + "=?";
yield Zotero.DB.queryAsync(sql, [parseInt(version), this.id]);
}
if (this._changed.primaryData && this._changed.primaryData.version) {
if (Objects.keys(this._changed.primaryData).length == 1) {
delete this._changed.primaryData;
}
else {
delete this._changed.primaryData.version;
}
}
});
/**
* Update object sync status, efficiently
*
* Used by sync code
*
* @param {Boolean} synced
* @param {Boolean} [skipDB=false]
*/
Zotero.DataObject.prototype.updateSynced = Zotero.Promise.coroutine(function* (synced, skipDB) {
if (!this.id) {
throw new Error("Cannot update sync status of unsaved " + this._objectType);
}
if (typeof synced != 'boolean') {
throw new Error("'synced' must be a boolean");
}
this._synced = synced;
if (!skipDB) {
var cl = this.ObjectsClass;
var sql = "UPDATE " + cl.table + " SET synced=? WHERE " + cl.idColumn + "=?";
yield Zotero.DB.queryAsync(sql, [synced ? 1 : 0, this.id]);
}
if (this._changed.primaryData && this._changed.primaryData.synced) {
if (Objects.keys(this._changed.primaryData).length == 1) {
delete this._changed.primaryData;
}
else {
delete this._changed.primaryData.synced;
}
}
});
/**
* Delete object from database
*/

View File

@ -127,9 +127,10 @@ Zotero.DataObjects.prototype.get = function (ids) {
* Retrieves (and loads, if necessary) one or more items
*
* @param {Array|Integer} ids An individual object id or an array of object ids
* @param {Object} options 'noCache': Don't cache loaded objects
* @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed;
* otherwise, an array of Zotero.[Object]
* @param {Object} [options]
* @param {Boolean} [options.noCache=false] - Don't add object to cache after loading
* @return {Zotero.DataObject|Zotero.DataObject[]} - A data object, if a scalar id was passed;
* otherwise, an array of data objects
*/
Zotero.DataObjects.prototype.getAsync = Zotero.Promise.coroutine(function* (ids, options) {
var toLoad = [];
@ -253,6 +254,8 @@ Zotero.DataObjects.prototype.getByLibraryAndKey = function (libraryID, key, opti
*
* @param {Integer} - libraryID
* @param {String} - key
* @param {Object} [options]
* @param {Boolean} [options.noCache=false] - Don't add object to cache after loading
* @return {Promise<Zotero.DataObject>} - Promise for a data object, or FALSE if not found
*/
Zotero.DataObjects.prototype.getByLibraryAndKeyAsync = Zotero.Promise.coroutine(function* (libraryID, key, options) {
@ -287,7 +290,7 @@ Zotero.DataObjects.prototype.getIDFromLibraryAndKey = function (libraryID, key)
}
Zotero.DataObjects.prototype.getOlder = function (libraryID, date) {
Zotero.DataObjects.prototype.getOlder = Zotero.Promise.method(function (libraryID, date) {
if (!date || date.constructor.name != 'Date') {
throw ("date must be a JS Date in "
+ "Zotero." + this._ZDO_Objects + ".getOlder()")
@ -295,11 +298,11 @@ Zotero.DataObjects.prototype.getOlder = function (libraryID, date) {
var sql = "SELECT ROWID FROM " + this._ZDO_table
+ " WHERE libraryID=? AND clientDateModified<?";
return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
}
return Zotero.DB.columnQueryAsync(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
});
Zotero.DataObjects.prototype.getNewer = function (libraryID, date, ignoreFutureDates) {
Zotero.DataObjects.prototype.getNewer = Zotero.Promise.method(function (libraryID, date, ignoreFutureDates) {
if (!date || date.constructor.name != 'Date') {
throw ("date must be a JS Date in "
+ "Zotero." + this._ZDO_Objects + ".getNewer()")
@ -310,8 +313,8 @@ Zotero.DataObjects.prototype.getNewer = function (libraryID, date, ignoreFutureD
if (ignoreFutureDates) {
sql += " AND clientDateModified<=CURRENT_TIMESTAMP";
}
return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
}
return Zotero.DB.columnQueryAsync(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
});
/**
@ -430,6 +433,61 @@ Zotero.DataObjects.prototype.unload = function () {
}
/**
* Set the version of objects, efficiently
*
* @param {Integer[]} ids - Ids of objects to update
* @param {Boolean} synced
*/
Zotero.DataObjects.prototype.updateVersion = Zotero.Promise.method(function (ids, version) {
if (version != parseInt(version)) {
throw new Error("'version' must be an integer");
}
version = parseInt(version);
let sql = "UPDATE " + this.table + " SET version=" + version + " "
+ "WHERE " + this.idColumn + " IN (";
return Zotero.Utilities.Internal.forEachChunkAsync(
ids, Zotero.DB.MAX_BOUND_PARAMETERS, Zotero.Promise.coroutine(function* (chunk) {
yield Zotero.DB.queryAsync(sql + chunk.map(() => '?').join(', ') + ')', chunk);
// Update the internal 'version' property of any loaded objects
for (let i = 0; i < chunk.length; i++) {
let id = chunk[i];
let obj = this._objectCache[id];
if (obj) {
obj.updateVersion(version, true);
}
}
}.bind(this))
);
});
/**
* Set the sync state of objects, efficiently
*
* @param {Integer[]} ids - Ids of objects to update
* @param {Boolean} synced
*/
Zotero.DataObjects.prototype.updateSynced = Zotero.Promise.method(function (ids, synced) {
let sql = "UPDATE " + this.table + " SET synced=" + (synced ? 1 : 0) + " "
+ "WHERE " + this.idColumn + " IN (";
return Zotero.Utilities.Internal.forEachChunkAsync(
ids, Zotero.DB.MAX_BOUND_PARAMETERS, Zotero.Promise.coroutine(function* (chunk) {
yield Zotero.DB.queryAsync(sql + chunk.map(() => '?').join(', ') + ')', chunk);
// Update the internal 'synced' property of any loaded objects
for (let i = 0; i < chunk.length; i++) {
let id = chunk[i];
let obj = this._objectCache[id];
if (obj) {
obj.updateSynced(!!synced, true);
}
}
}.bind(this))
);
});
Zotero.DataObjects.prototype.isEditable = function (obj) {
var libraryID = obj.libraryID;
if (!libraryID) {

View File

@ -1742,17 +1742,6 @@ Zotero.Item.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
return env.isNew ? this.id : true;
});
/**
* Used by sync code
*/
Zotero.Item.prototype.updateClientDateModified = function () {
if (!this.id) {
throw ("Cannot update clientDateModified of unsaved item in Zotero.Item.updateClientDateModified()");
}
var sql = "UPDATE items SET clientDateModified=? WHERE itemID=?";
Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, this.id]);
}
Zotero.Item.prototype.isRegularItem = function() {
return !(this.isNote() || this.isAttachment());

View File

@ -35,7 +35,7 @@ Zotero.Utilities.Internal = {
*
* @param {Array} arr
* @param {Integer} chunkSize
* @param {Function} func
* @param {Function} func - A promise-returning function
* @return {Array} The return values from the successive runs
*/
"forEachChunkAsync": Zotero.Promise.coroutine(function* (arr, chunkSize, func) {

View File

@ -171,9 +171,8 @@ function createUnsavedDataObject(objectType, params) {
var createDataObject = Zotero.Promise.coroutine(function* (objectType, params, saveOptions) {
var obj = createUnsavedDataObject(objectType, params);
var id = yield obj.saveTx(saveOptions);
var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
return objectsClass.getAsync(id);
yield obj.saveTx(saveOptions);
return obj;
});
/**

View File

@ -37,7 +37,6 @@ describe("Zotero.DataObject", function() {
it("should be set after creating object", function* () {
for (let type of types) {
Zotero.logError(type);
let obj = yield createDataObject(type, { version: 1234 });
assert.equal(obj.version, 1234, type + " version mismatch");
yield obj.eraseTx();
@ -126,4 +125,50 @@ describe("Zotero.DataObject", function() {
assert.equal(objectsClass.getIDFromLibraryAndKey(libraryID, key), id);
})
})
describe("#updateVersion()", function() {
it("should update the object version", function* () {
for (let type of types) {
let obj = yield createDataObject(type);
assert.equal(obj.version, 0);
yield obj.updateVersion(1234);
assert.equal(obj.version, 1234);
assert.isFalse(obj.hasChanged());
obj.synced = true;
assert.ok(obj.hasChanged());
yield obj.updateVersion(1235);
assert.equal(obj.version, 1235);
assert.ok(obj.hasChanged());
yield obj.eraseTx();
}
})
})
describe("#updateSynced()", function() {
it("should update the object sync status", function* () {
for (let type of types) {
let obj = yield createDataObject(type);
assert.isFalse(obj.synced);
yield obj.updateSynced(false);
assert.isFalse(obj.synced);
assert.isFalse(obj.hasChanged());
yield obj.updateSynced(true);
assert.ok(obj.synced);
assert.isFalse(obj.hasChanged());
obj.version = 1234;
assert.ok(obj.hasChanged());
yield obj.updateSynced(false);
assert.isFalse(obj.synced);
assert.ok(obj.hasChanged());
yield obj.eraseTx();
}
})
})
})