From 902ea922181418e6a251b62c8fbb3529fb30af69 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 13 Sep 2022 17:22:44 +0200 Subject: [PATCH 1/4] feat(NODE-4634): add support for bulk FindOperators.hint() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … and fix `.update()`/`.updateOne()` to also accept pipeline-style updates as other update APIs do as well. --- src/bulk/common.ts | 14 +++++- src/operations/update.ts | 4 +- test/integration/crud/bulk.test.ts | 71 ++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/bulk/common.ts b/src/bulk/common.ts index 6361584066..ffcad308bb 100644 --- a/src/bulk/common.ts +++ b/src/bulk/common.ts @@ -784,7 +784,7 @@ export class FindOperators { } /** Add a multiple update operation to the bulk operation */ - update(updateDocument: Document): BulkOperationBase { + update(updateDocument: Document | Document[]): BulkOperationBase { const currentOp = buildCurrentOp(this.bulkOperation); return this.bulkOperation.addToOperationsList( BatchType.UPDATE, @@ -796,7 +796,7 @@ export class FindOperators { } /** Add a single update operation to the bulk operation */ - updateOne(updateDocument: Document): BulkOperationBase { + updateOne(updateDocument: Document | Document[]): BulkOperationBase { if (!hasAtomicOperators(updateDocument)) { throw new MongoInvalidArgumentError('Update document requires atomic operators'); } @@ -868,6 +868,16 @@ export class FindOperators { this.bulkOperation.s.currentOp.arrayFilters = arrayFilters; return this; } + + /** Specifies hint for UpdateOne or UpdateMany bulk operations. */ + hint(hint: Hint): this { + if (!this.bulkOperation.s.currentOp) { + this.bulkOperation.s.currentOp = {}; + } + + this.bulkOperation.s.currentOp.hint = hint; + return this; + } } /** @internal */ diff --git a/src/operations/update.ts b/src/operations/update.ts index a389878a14..43760c1431 100644 --- a/src/operations/update.ts +++ b/src/operations/update.ts @@ -22,7 +22,7 @@ export interface UpdateOptions extends CommandOperationOptions { /** Specifies a collation */ collation?: CollationOptions; /** Specify that the update query should only consider plans using the hinted index */ - hint?: string | Document; + hint?: Hint; /** When true, creates a new document if no document matches the query */ upsert?: boolean; /** Map of parameter names and values that can be accessed using $$var (requires MongoDB 5.0). */ @@ -280,7 +280,7 @@ export class ReplaceOneOperation extends UpdateOperation { export function makeUpdateStatement( filter: Document, - update: Document, + update: Document | Document[], options: UpdateOptions & { multi?: boolean } ): UpdateStatement { if (filter == null || typeof filter !== 'object') { diff --git a/test/integration/crud/bulk.test.ts b/test/integration/crud/bulk.test.ts index 786a99fb6f..fb4bbedcb5 100644 --- a/test/integration/crud/bulk.test.ts +++ b/test/integration/crud/bulk.test.ts @@ -1688,6 +1688,55 @@ describe('Bulk', function () { } }); + it('should apply hint via FindOperators', { + metadata: { requires: { mongodb: '>= 4.2' } }, + async test() { + const bulk = client.db().collection('coll').initializeOrderedBulkOp(); + + const events = []; + client.on('commandStarted', event => { + if (['update', 'delete'].includes(event.commandName)) { + events.push(event); + } + }); + + // updates + bulk + .find({ b: 1 }) + .hint({ b: 1 }) + .updateOne({ $set: { b: 2 } }); + bulk + .find({ b: 2 }) + .hint({ b: 1 }) + .update({ $set: { b: 3 } }); + bulk.find({ b: 3 }).hint({ b: 1 }).replaceOne({ b: 2 }); + + // deletes + bulk.find({ b: 2 }).hint({ b: 1 }).deleteOne(); + bulk.find({ b: 1 }).hint({ b: 1 }).delete(); + + await bulk.execute(); + + try { + expect(events).to.be.an('array').with.length.at.least(1); + expect(events[0]).property('commandName').to.equal('update'); + const updateCommand = events[0].command; + expect(updateCommand).property('updates').to.be.an('array').with.length(3); + updateCommand.updates.forEach(statement => { + expect(statement).property('hint').to.eql({ b: 1 }); + }); + expect(events[1]).property('commandName').to.equal('delete'); + const deleteCommand = events[1].command; + expect(deleteCommand).property('deletes').to.be.an('array').with.length(2); + deleteCommand.deletes.forEach(statement => { + expect(statement).property('hint').to.eql({ b: 1 }); + }); + } finally { + await client.close(); + } + } + }); + it('should apply arrayFilters to bulk updates via FindOperators', { metadata: { requires: { mongodb: '>= 3.6' } }, test: function (done) { @@ -1737,6 +1786,28 @@ describe('Bulk', function () { } }); + it('should accept pipeline-style updates', { + metadata: { requires: { mongodb: '>= 4.2' } }, + async test() { + const coll = client.db().collection('coll'); + const bulk = coll.initializeOrderedBulkOp(); + + coll.insertMany([{ a: 1 }, { a: 2 }]); + + bulk.find({ a: 1 }).updateOne([{ $project: { a: { $add: ['$a', 10] } } }]); + bulk.find({ a: 2 }).update([{ $project: { a: { $add: ['$a', 100] } } }]); + + await bulk.execute(); + + try { + const contents = await coll.find().project({ _id: 0 }).toArray(); + expect(contents).to.deep.equal([{ a: 11 }, { a: 102 }]); + } finally { + await client.close(); + } + } + }); + it('should throw an error if raw operations are passed to bulkWrite', function () { const coll = client.db().collection('single_bulk_write_error'); return coll From 6d724f98a461c694867bb40de0f034032cba4e50 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 13 Sep 2022 20:16:10 +0200 Subject: [PATCH 2/4] fixup: review comments, remove extraneous client.close() --- test/integration/crud/bulk.test.ts | 38 ++++++++++++------------------ 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/test/integration/crud/bulk.test.ts b/test/integration/crud/bulk.test.ts index fb4bbedcb5..f37cf1e80d 100644 --- a/test/integration/crud/bulk.test.ts +++ b/test/integration/crud/bulk.test.ts @@ -1717,23 +1717,19 @@ describe('Bulk', function () { await bulk.execute(); - try { - expect(events).to.be.an('array').with.length.at.least(1); - expect(events[0]).property('commandName').to.equal('update'); - const updateCommand = events[0].command; - expect(updateCommand).property('updates').to.be.an('array').with.length(3); - updateCommand.updates.forEach(statement => { - expect(statement).property('hint').to.eql({ b: 1 }); - }); - expect(events[1]).property('commandName').to.equal('delete'); - const deleteCommand = events[1].command; - expect(deleteCommand).property('deletes').to.be.an('array').with.length(2); - deleteCommand.deletes.forEach(statement => { - expect(statement).property('hint').to.eql({ b: 1 }); - }); - } finally { - await client.close(); - } + expect(events).to.be.an('array').with.length.at.least(1); + expect(events[0]).property('commandName').to.equal('update'); + const updateCommand = events[0].command; + expect(updateCommand).property('updates').to.be.an('array').with.length(3); + updateCommand.updates.forEach(statement => { + expect(statement).property('hint').to.eql({ b: 1 }); + }); + expect(events[1]).property('commandName').to.equal('delete'); + const deleteCommand = events[1].command; + expect(deleteCommand).property('deletes').to.be.an('array').with.length(2); + deleteCommand.deletes.forEach(statement => { + expect(statement).property('hint').to.eql({ b: 1 }); + }); } }); @@ -1799,12 +1795,8 @@ describe('Bulk', function () { await bulk.execute(); - try { - const contents = await coll.find().project({ _id: 0 }).toArray(); - expect(contents).to.deep.equal([{ a: 11 }, { a: 102 }]); - } finally { - await client.close(); - } + const contents = await coll.find().project({ _id: 0 }).toArray(); + expect(contents).to.deep.equal([{ a: 11 }, { a: 102 }]); } }); From 743c20ff5f32845a2f2f460a35fb904fc357a6b3 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 13 Sep 2022 20:17:56 +0200 Subject: [PATCH 3/4] fixup: review comments, bump minimum version for hint to 4.4 --- test/integration/crud/bulk.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/crud/bulk.test.ts b/test/integration/crud/bulk.test.ts index f37cf1e80d..70ea459b72 100644 --- a/test/integration/crud/bulk.test.ts +++ b/test/integration/crud/bulk.test.ts @@ -1689,7 +1689,7 @@ describe('Bulk', function () { }); it('should apply hint via FindOperators', { - metadata: { requires: { mongodb: '>= 4.2' } }, + metadata: { requires: { mongodb: '>= 4.4' } }, async test() { const bulk = client.db().collection('coll').initializeOrderedBulkOp(); From c749925935fa54dc86d5d705fedea48417336fae Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 13 Sep 2022 20:29:02 +0200 Subject: [PATCH 4/4] fixup: review comments, reword comment --- src/bulk/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bulk/common.ts b/src/bulk/common.ts index ffcad308bb..e0444787eb 100644 --- a/src/bulk/common.ts +++ b/src/bulk/common.ts @@ -869,7 +869,7 @@ export class FindOperators { return this; } - /** Specifies hint for UpdateOne or UpdateMany bulk operations. */ + /** Specifies hint for the bulk operation. */ hint(hint: Hint): this { if (!this.bulkOperation.s.currentOp) { this.bulkOperation.s.currentOp = {};