mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
version 0 of optimistic upsert simulation
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user