diff --git a/lib/datastore.js b/lib/datastore.js index 4d3ddb3..c72696f 100644 --- a/lib/datastore.js +++ b/lib/datastore.js @@ -493,61 +493,6 @@ db.insert({ tag: ['a', 'b', 'c'] }, function () { }); */ -/* -var db = new Datastore({ inMemoryOnly: true }); - -db.ensureIndex({ fieldName: 'a', unique: true }); - -db.insert({ a: 4 }, function () { -db.insert({ a: 5 }, function () { -db.insert({ a: 6 }, function () { - -db.find({}, function (err, docs) { - console.log(docs); - - db.update({}, { $set: { a: 42 } }, { multi: true }, function (err) { - console.log(err); - -db.find({}, function (err, docs) { - // FAIL - // The first document was modified and the modification was not roll back when the - // unique constraint was triggered on the second modif - console.log(docs); - - }); - }); - -}); -}); -}); -}); -*/ - -var db = new Datastore({ inMemoryOnly: true }); - - -db.insert({ a: [4] }, function () { -db.insert({ a: [5] }, function () { -db.insert({ a: 6 }, function () { - -db.find({}, function (err, docs) { - console.log(docs); - - db.update({}, { $push: { a: 42 } }, { multi: true }, function (err) { - console.log(err); - -db.find({}, function (err, docs) { - console.log(docs); - - }); - }); - -}); -}); -}); -}); - - module.exports = Datastore; \ No newline at end of file diff --git a/lib/model.js b/lib/model.js index cd5de03..257e8de 100644 --- a/lib/model.js +++ b/lib/model.js @@ -160,7 +160,7 @@ function compareArrays (a, b) { * Compare { things U undefined } * Things are defined as any native types (string, number, boolean, null, date) and objects * We need to compare with undefined as it will be used in indexes - * In the case of objects and arrays, we compare the serialized versions + * In the case of objects and arrays, we deep-compare * If two objects dont have the same type, the (arbitrary) type hierarchy is: undefined, null, number, strings, boolean, dates, arrays, objects * Return -1 if a < b, 1 if a > b and 0 if a = b (note that equality here is NOT the same as defined in areThingsEqual!) */ diff --git a/test/db.test.js b/test/db.test.js index 6f11d31..9621160 100644 --- a/test/db.test.js +++ b/test/db.test.js @@ -562,14 +562,14 @@ describe('Database', function () { assert.isDefined(err); assert.isUndefined(docs); - done(); + done(); }); }); }); }); - describe.only('Update', function () { + describe('Update', function () { it("If the query doesn't match anything, database is not modified", function (done) { async.waterfall([ @@ -950,41 +950,83 @@ describe('Database', function () { }); }); }); - - it('Can update without the options arg (will use defaults then)', function (done) { + + it('Can update without the options arg (will use defaults then)', function (done) { d.insert({ a:1, hello: 'world' }, function (err, doc1) { d.insert({ a:2, hello: 'earth' }, function (err, doc2) { d.insert({ a:5, hello: 'pluton' }, function (err, doc3) { - d.update({ a: 2 }, { $inc: { a: 10 } }, function (err, nr) { - assert.isNull(err); - nr.should.equal(1); - d.find({}, function (err, docs) { - var d1 = _.find(docs, function (doc) { return doc._id === doc1._id }) - , d2 = _.find(docs, function (doc) { return doc._id === doc2._id }) - , d3 = _.find(docs, function (doc) { return doc._id === doc3._id }) - ; - - d1.a.should.equal(1); - d2.a.should.equal(12); - d3.a.should.equal(5); + d.update({ a: 2 }, { $inc: { a: 10 } }, function (err, nr) { + assert.isNull(err); + nr.should.equal(1); + d.find({}, function (err, docs) { + var d1 = _.find(docs, function (doc) { return doc._id === doc1._id }) + , d2 = _.find(docs, function (doc) { return doc._id === doc2._id }) + , d3 = _.find(docs, function (doc) { return doc._id === doc3._id }) + ; + + d1.a.should.equal(1); + d2.a.should.equal(12); + d3.a.should.equal(5); + + done(); + }); + }); + }); + }); + }); + }); + + it('If a multi update fails on one document, previous updates should be rolled back', function (done) { + d.ensureIndex({ fieldName: 'a' }); + d.insert({ a: 4 }, function (err, doc1) { + d.insert({ a: 5 }, function (err, doc2) { + d.insert({ a: 'abc' }, function (err, doc3) { + // With this query, candidates are always returned in the order 4, 5, 'abc' so it's always the last one which fails + d.update({ a: { $in: [4, 5, 'abc'] } }, { $inc: { a: 10 } }, { multi: true }, function (err) { + assert.isDefined(err); + + d.find({}, function (err, docs) { + var d1 = _.find(docs, function (doc) { return doc._id === doc1._id }) + , d2 = _.find(docs, function (doc) { return doc._id === doc2._id }) + , d3 = _.find(docs, function (doc) { return doc._id === doc3._id }) + ; + + // All changes rollbacked, including those that didn't trigger an error + d1.a.should.equal(4); + d2.a.should.equal(5); + d3.a.should.equal('abc'); + + done(); + }); + }); + }); + }); + }); + }); + + it.only('If an index constraint is violated by an update, all changes should be rolled back', function (done) { + d.ensureIndex({ fieldName: 'a', unique: true }); + d.insert({ a: 4 }, function (err, doc1) { + d.insert({ a: 5 }, function (err, doc2) { + // With this query, candidates are always returned in the order 4, 5, 'abc' so it's always the last one which fails + d.update({ a: { $in: [4, 5, 'abc'] } }, { $set: { a: 10 } }, { multi: true }, function (err) { + assert.isDefined(err); + + d.find({}, function (err, docs) { + var d1 = _.find(docs, function (doc) { return doc._id === doc1._id }) + , d2 = _.find(docs, function (doc) { return doc._id === doc2._id }) + ; - done(); - }); - }); + // All changes rollbacked, including those that didn't trigger an error + d1.a.should.equal(4); + d2.a.should.equal(5); + + done(); + }); }); }); }); - }); - - it('If a multi update fails on one document, previous updates should be rolled back', function (done) { - throw 'TODO'; - done(); - }); - - it('If an index constraint is violated by an update, all changes should be rolled back', function (done) { - throw 'TODO'; - done(); - }); + }); }); // ==== End of 'Update' ==== // @@ -1134,31 +1176,31 @@ describe('Database', function () { }); }); }); - - it('Can remove without the options arg (will use defaults then)', function (done) { + + it('Can remove without the options arg (will use defaults then)', function (done) { d.insert({ a:1, hello: 'world' }, function (err, doc1) { d.insert({ a:2, hello: 'earth' }, function (err, doc2) { d.insert({ a:5, hello: 'pluton' }, function (err, doc3) { - d.remove({ a: 2 }, function (err, nr) { - assert.isNull(err); - nr.should.equal(1); - d.find({}, function (err, docs) { - var d1 = _.find(docs, function (doc) { return doc._id === doc1._id }) - , d2 = _.find(docs, function (doc) { return doc._id === doc2._id }) - , d3 = _.find(docs, function (doc) { return doc._id === doc3._id }) - ; - - d1.a.should.equal(1); - assert.isUndefined(d2); - d3.a.should.equal(5); - - done(); - }); - }); + d.remove({ a: 2 }, function (err, nr) { + assert.isNull(err); + nr.should.equal(1); + d.find({}, function (err, docs) { + var d1 = _.find(docs, function (doc) { return doc._id === doc1._id }) + , d2 = _.find(docs, function (doc) { return doc._id === doc2._id }) + , d3 = _.find(docs, function (doc) { return doc._id === doc3._id }) + ; + + d1.a.should.equal(1); + assert.isUndefined(d2); + d3.a.should.equal(5); + + done(); + }); + }); }); }); }); - }); + }); }); // ==== End of 'Remove' ==== //