Can batch-update an index, no change if an error was thrown

pull/2/head
Louis Chatriot 12 years ago
parent 82e4be9b1a
commit 9feeb26c4f
  1. 47
      lib/indexes.js
  2. 2
      test/db.test.js
  3. 92
      test/indexes.test.js

@ -71,8 +71,8 @@ Index.prototype.insert = function (doc) {
/** /**
* When inserting an array of documents, we need to rollback all insertions * Insert an array of documents in the index
* if an error is thrown * If a constraint is violated, an error should be thrown and the changes rolled back
*/ */
Index.prototype.insertMultipleDocs = function (docs) { Index.prototype.insertMultipleDocs = function (docs) {
var i, error, failingI; var i, error, failingI;
@ -100,6 +100,7 @@ Index.prototype.insertMultipleDocs = function (docs) {
/** /**
* Remove a document from the index * Remove a document from the index
* If an array is passed, we remove all its elements * If an array is passed, we remove all its elements
* The remove operation is safe with regards to the 'unique' constraint
* O(log(n)) * O(log(n))
*/ */
Index.prototype.remove = function (doc) { Index.prototype.remove = function (doc) {
@ -120,14 +121,54 @@ Index.prototype.remove = function (doc) {
/** /**
* Update a document in the index * Update a document in the index
* O(log(n)) * Naive implementation, still in O(log(n))
*/ */
Index.prototype.update = function (oldDoc, newDoc) { Index.prototype.update = function (oldDoc, newDoc) {
if (util.isArray(oldDoc)) { this.updateMultipleDocs(oldDoc); }
this.remove(oldDoc); this.remove(oldDoc);
this.insert(newDoc); this.insert(newDoc);
}; };
/**
* Update multiple documents in the index
* If a constraint is violated, the changes need to be rolled back
* and an error thrown
* @param {Array of oldDoc, newDoc pairs} pairs
*/
Index.prototype.updateMultipleDocs = function (pairs) {
var i, failingI, error;
for (i = 0; i < pairs.length; i += 1) {
this.remove(pairs[i].oldDoc);
}
for (i = 0; i < pairs.length; i += 1) {
try {
this.insert(pairs[i].newDoc);
} catch (e) {
error = e;
failingI = i;
break;
}
}
// If an error was raised, roll back changes in the inverse order
if (error) {
for (i = 0; i < failingI; i += 1) {
this.remove(pairs[i].newDoc);
}
for (i = 0; i < pairs.length; i += 1) {
this.insert(pairs[i].oldDoc);
}
throw error;
}
};
/** /**
* Get all documents in index that match the query on fieldName * Get all documents in index that match the query on fieldName
* For now only works with field equality (i.e. can't use the index for $lt query for example) * For now only works with field equality (i.e. can't use the index for $lt query for example)

@ -1491,7 +1491,7 @@ describe('Database', function () {
}); // ==== End of 'Indexing newly inserted documents' ==== // }); // ==== End of 'Indexing newly inserted documents' ==== //
describe.only('Updating indexes upon document update', function () { describe.skip('Updating indexes upon document update', function () {
it('Updating docs still works as before with an index', function (done) { it('Updating docs still works as before with an index', function (done) {
d.ensureIndex({ fieldName: 'a' }); d.ensureIndex({ fieldName: 'a' });

@ -239,6 +239,98 @@ describe('Indexes', function () {
assert.deepEqual(idx.tree.search('changed'), [doc5]); assert.deepEqual(idx.tree.search('changed'), [doc5]);
}); });
it('Can update an array of documents', function () {
var idx = new Index({ fieldName: 'tf' })
, doc1 = { a: 5, tf: 'hello' }
, doc2 = { a: 8, tf: 'world' }
, doc3 = { a: 2, tf: 'bloup' }
, doc1b = { a: 23, tf: 'world' }
, doc2b = { a: 1, tf: 'changed' }
, doc3b = { a: 44, tf: 'bloup' }
;
idx.insert(doc1);
idx.insert(doc2);
idx.insert(doc3);
idx.tree.getNumberOfKeys().should.equal(3);
idx.update([{ oldDoc: doc1, newDoc: doc1b }, { oldDoc: doc2, newDoc: doc2b }, { oldDoc: doc3, newDoc: doc3b }]);
idx.tree.getNumberOfKeys().should.equal(3);
idx.getMatching('world').length.should.equal(1);
idx.getMatching('world')[0].should.equal(doc1b);
idx.getMatching('changed').length.should.equal(1);
idx.getMatching('changed')[0].should.equal(doc2b);
idx.getMatching('bloup').length.should.equal(1);
idx.getMatching('bloup')[0].should.equal(doc3b);
});
it('If a unique constraint is violated during an array-update, all changes are rolled back and an error thrown', function () {
var idx = new Index({ fieldName: 'tf', unique: true })
, doc0 = { a: 432, tf: 'notthistoo' }
, doc1 = { a: 5, tf: 'hello' }
, doc2 = { a: 8, tf: 'world' }
, doc3 = { a: 2, tf: 'bloup' }
, doc1b = { a: 23, tf: 'changed' }
, doc2b = { a: 1, tf: 'changed' } // Will violate the constraint (first try)
, doc2c = { a: 1, tf: 'notthistoo' } // Will violate the constraint (second try)
, doc3b = { a: 44, tf: 'alsochanged' }
;
idx.insert(doc1);
idx.insert(doc2);
idx.insert(doc3);
idx.tree.getNumberOfKeys().should.equal(3);
try {
idx.update([{ oldDoc: doc1, newDoc: doc1b }, { oldDoc: doc2, newDoc: doc2b }, { oldDoc: doc3, newDoc: doc3b }]);
} catch (e) {
e.errorType.should.equal('uniqueViolated');
}
idx.tree.getNumberOfKeys().should.equal(3);
idx.getMatching('hello').length.should.equal(1);
idx.getMatching('hello')[0].should.equal(doc1);
idx.getMatching('world').length.should.equal(1);
idx.getMatching('world')[0].should.equal(doc2);
idx.getMatching('bloup').length.should.equal(1);
idx.getMatching('bloup')[0].should.equal(doc3);
try {
idx.update([{ oldDoc: doc1, newDoc: doc1b }, { oldDoc: doc2, newDoc: doc2b }, { oldDoc: doc3, newDoc: doc3b }]);
} catch (e) {
e.errorType.should.equal('uniqueViolated');
}
idx.tree.getNumberOfKeys().should.equal(3);
idx.getMatching('hello').length.should.equal(1);
idx.getMatching('hello')[0].should.equal(doc1);
idx.getMatching('world').length.should.equal(1);
idx.getMatching('world')[0].should.equal(doc2);
idx.getMatching('bloup').length.should.equal(1);
idx.getMatching('bloup')[0].should.equal(doc3);
});
it('If an update doesnt change a document, the unique constraint is not violated', function () {
var idx = new Index({ fieldName: 'tf', unique: true })
, doc1 = { a: 5, tf: 'hello' }
, doc2 = { a: 8, tf: 'world' }
, doc3 = { a: 2, tf: 'bloup' }
, noChange = { a: 8, tf: 'world' }
;
idx.insert(doc1);
idx.insert(doc2);
idx.insert(doc3);
idx.tree.getNumberOfKeys().should.equal(3);
assert.deepEqual(idx.tree.search('world'), [doc2]);
idx.update(doc2, noChange); // No error thrown
idx.tree.getNumberOfKeys().should.equal(3);
assert.deepEqual(idx.tree.search('world'), [noChange]);
});
}); // ==== End of 'Update' ==== // }); // ==== End of 'Update' ==== //

Loading…
Cancel
Save