version 0 of optimistic upsert simulation

This commit is contained in:
David Greenspan
2013-09-24 17:59:59 -07:00
parent 99b9ec2da5
commit 41247305db
3 changed files with 126 additions and 4 deletions

View File

@@ -374,6 +374,20 @@ _.each(["insert", "update", "remove"], function (name) {
}
} else {
args[0] = Meteor.Collection._rewriteSelector(args[0]);
if (name === "update") {
var options = args[2];
if (options && options.upsert) {
// set `insertedId` if absent. `insertedId` is a Meteor extension.
if (options.insertedId) {
if (!(typeof options.insertedId === 'string'
|| options.insertedId instanceof Meteor.Collection.ObjectID))
throw new Error("insertedId must be string or ObjectID");
} else {
options.insertedId = self._makeNewID();
}
}
}
}
var wrappedCallback;

View File

@@ -335,15 +335,89 @@ MongoConnection.prototype._update = function (collection_name, selector, mod,
// explictly enumerate options that minimongo supports
if (options.upsert) mongoOpts.upsert = true;
if (options.multi) mongoOpts.multi = true;
collection.update(replaceTypes(selector, replaceMeteorAtomWithMongo),
replaceTypes(mod, replaceMeteorAtomWithMongo),
mongoOpts, numberAffectedCallback(callback));
var mongoSelector = replaceTypes(selector, replaceMeteorAtomWithMongo);
var mongoMod = replaceTypes(mod, replaceMeteorAtomWithMongo);
var isModify = isModificationMod(mongoMod);
if (options.upsert &&
(isModify ? (! mongoSelector._id) : (! mongoMod._id)) &&
options.insertedId) {
mongoOpts.insertedId = options.insertedId;
simulateUpsertWithInsertedId(collection, mongoSelector, mongoMod,
isModify, mongoOpts, callback);
} else {
collection.update(mongoSelector, mongoMod, mongoOpts,
numberAffectedCallback(callback));
}
} catch (e) {
write.committed();
throw e;
}
};
var isModificationMod = function (mod) {
for (var k in mod)
if (k.substr(0, 1) === '$')
return true;
return false;
};
var simulateUpsertWithInsertedId = function (collection, selector, mod,
isModify, options, callback) {
var insertedId = options.insertedId; // must exist
var mongoOpts = _.extend({}, options);
delete mongoOpts.insertedId;
delete mongoOpts.upsert;
var doUpdate = function () {
mongoOpts.upsert = false;
collection.update(selector, mod, mongoOpts,
numberAffectedCallback(function (err, result) {
if (err) {
callback(err);
} else if (result.numberAffected) {
callback(null, result);
} else {
doConditionalInsert();
}
}));
};
var doConditionalInsert = function () {
mongoOpts.upsert = true;
var replacementWithId = _.extend(
replaceTypes({_id: insertedId}, replaceMeteorAtomWithMongo),
mod);
collection.update(selector, replacementWithId, mongoOpts,
numberAffectedCallback(function (err, result) {
if (err) {
// XXX figure out if this is a
// "cannot change _id of document" error, and
// if so, try doUpdate() again, up to 3 times.
// Otherwise, pass err to callback.
Meteor._debug(err);
} else {
callback(null, _.extend(result,
{ insertedId: insertedId }));
}
}));
};
if (isModify) {
// XXX TODO
} else {
doUpdate();
}
};
var modifyDocument = function (doc, mod) {
// XXX use LocalCollection._modify
return mod;
};
_.each(["insert", "update", "remove"], function (method) {
MongoConnection.prototype[method] = function (/* arguments */) {
var self = this;

View File

@@ -901,8 +901,42 @@ if (Meteor.isServer) {
_.each(handlesToStop, function (h) {h.stop();});
onComplete();
});
}
Tinytest.addAsync("mongo-livedata - upsert, " + idGeneration, function (test, onComplete) {
var run = test.runId();
var coll = new Meteor.Collection("livedata_upsert_collection_"+run, collectionOptions);
var result1 = coll.update({foo: 'bar'}, {foo: 'bar'}, {upsert: true});
test.equal(result1.numberAffected, 1);
test.isTrue(result1.insertedId);
test.equal(coll.find().fetch(), [{foo: 'bar', _id: result1.insertedId}]);
var result2 = coll.update({foo: 'bar'}, {foo: 'baz'}, {upsert: true});
test.equal(result2.numberAffected, 1);
test.isFalse(result2.insertedId);
test.equal(coll.find().fetch(), [{foo: 'baz', _id: result1.insertedId}]);
coll.remove({});
// Test values that require transformation to go into Mongo:
var t1 = new Meteor.Collection.ObjectID();
var t2 = new Meteor.Collection.ObjectID();
var result3 = coll.update({foo: t1}, {foo: t1}, {upsert: true});
test.equal(result3.numberAffected, 1);
test.isTrue(result3.insertedId);
test.equal(coll.find().fetch(), [{foo: t1, _id: result3.insertedId}]);
var result4 = coll.update({foo: t1}, {foo: t2}, {upsert: true});
test.equal(result2.numberAffected, 1);
test.isFalse(result2.insertedId);
test.equal(coll.find().fetch(), [{foo: t2, _id: result3.insertedId}]);
onComplete();
});
} // end Meteor.isServer
}); // end idGeneration parametrization