Skip to content

Commit a41d503

Browse files
authored
feat(bulk)!: add collation to FindOperators (#2679)
The primary purpose of this PR was to add a fluent builder method for `collation` for bulk writes with a filter. As part of this work, legacy support for raw operations was removed from `collection.bulkWrite`. A TypeError will now be thrown if attempting to bulkWrite with raw operations. NODE-2757
1 parent b7f2385 commit a41d503

File tree

8 files changed

+251
-240
lines changed

8 files changed

+251
-240
lines changed

src/bulk/common.ts

+89-215
Large diffs are not rendered by default.

src/collection.ts

+3
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ export class Collection {
333333
* ```js
334334
* { insertOne: { document: { a: 1 } } }
335335
*
336+
* { insertMany: [{ g: 1 }, { g: 2 }]}
337+
*
336338
* { updateOne: { filter: {a:2}, update: {$set: {a:2}}, upsert:true } }
337339
*
338340
* { updateMany: { filter: {a:2}, update: {$set: {a:2}}, upsert:true } }
@@ -345,6 +347,7 @@ export class Collection {
345347
*
346348
* { replaceOne: { filter: {c:3}, replacement: {c:4}, upsert:true}}
347349
*```
350+
* Please note that raw operations are no longer accepted as of driver version 4.0.
348351
*
349352
* If documents passed in do not contain the **_id** field,
350353
* one will be added to each of the documents missing it by the driver, mutating the document. This behavior

src/operations/delete.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { defineAspects, Aspect, Hint } from './operation';
22
import { CommandOperation, CommandOperationOptions, CollationOptions } from './command';
3-
import { Callback, maxWireVersion, MongoDBNamespace } from '../utils';
3+
import { Callback, maxWireVersion, MongoDBNamespace, collationNotSupported } from '../utils';
44
import type { Document } from '../bson';
55
import type { Server } from '../sdam/server';
66
import type { Collection } from '../collection';
@@ -88,6 +88,12 @@ export class DeleteOperation extends CommandOperation<Document> {
8888
}
8989
}
9090

91+
const statementWithCollation = this.statements.find(statement => !!statement.collation);
92+
if (statementWithCollation && collationNotSupported(server, statementWithCollation)) {
93+
callback(new MongoError(`server ${server.name} does not support collation`));
94+
return;
95+
}
96+
9197
super.executeCommand(server, session, command, callback);
9298
}
9399
}
@@ -132,7 +138,7 @@ export class DeleteManyOperation extends DeleteOperation {
132138
}
133139
}
134140

135-
function makeDeleteStatement(
141+
export function makeDeleteStatement(
136142
filter: Document,
137143
options: DeleteOptions & { limit?: number }
138144
): DeleteStatement {

src/operations/update.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ export class UpdateOperation extends CommandOperation<Document> {
9797
command.bypassDocumentValidation = options.bypassDocumentValidation;
9898
}
9999

100-
if (collationNotSupported(server, options)) {
100+
const statementWithCollation = this.statements.find(statement => !!statement.collation);
101+
if (
102+
collationNotSupported(server, options) ||
103+
(statementWithCollation && collationNotSupported(server, statementWithCollation))
104+
) {
101105
callback(new MongoError(`server ${server.name} does not support collation`));
102106
return;
103107
}
@@ -115,6 +119,11 @@ export class UpdateOperation extends CommandOperation<Document> {
115119
return;
116120
}
117121

122+
if (this.statements.some(statement => !!statement.arrayFilters) && maxWireVersion(server) < 6) {
123+
callback(new MongoError('arrayFilters are only supported on MongoDB 3.6+'));
124+
return;
125+
}
126+
118127
super.executeCommand(server, session, command, callback);
119128
}
120129
}
@@ -247,7 +256,7 @@ export class ReplaceOneOperation extends UpdateOperation {
247256
}
248257
}
249258

250-
function makeUpdateStatement(
259+
export function makeUpdateStatement(
251260
filter: Document,
252261
update: Document,
253262
options: UpdateOptions & { multi?: boolean }

test/functional/apm.test.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -483,9 +483,9 @@ describe('APM', function () {
483483
.collection('apm_test_3')
484484
.bulkWrite(
485485
[
486-
{ insertOne: { a: 1 } },
487-
{ updateOne: { q: { a: 2 }, u: { $set: { a: 2 } }, upsert: true } },
488-
{ deleteOne: { q: { c: 1 } } }
486+
{ insertOne: { document: { a: 1 } } },
487+
{ updateOne: { filter: { a: 2 }, update: { $set: { a: 2 } }, upsert: true } },
488+
{ deleteOne: { filter: { c: 1 } } }
489489
],
490490
{ ordered: true }
491491
)

test/functional/bulk.test.js

+70-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
'use strict';
2-
const { withClient, withClientV2, setupDatabase, ignoreNsNotFound } = require('./shared');
2+
const {
3+
withClient,
4+
withClientV2,
5+
withMonitoredClient,
6+
setupDatabase,
7+
ignoreNsNotFound
8+
} = require('./shared');
39
const test = require('./shared').assert;
410
const { MongoError } = require('../../src/error');
511
const { Long } = require('../../src');
@@ -1841,4 +1847,67 @@ describe('Bulk', function () {
18411847
});
18421848
})
18431849
);
1850+
1851+
it('should apply collation via FindOperators', {
1852+
metadata: { requires: { mongodb: '>= 3.4' } },
1853+
test: withMonitoredClient(['update', 'delete'], function (client, events, done) {
1854+
const locales = ['fr', 'de', 'es'];
1855+
const bulk = client.db().collection('coll').initializeOrderedBulkOp();
1856+
1857+
// updates
1858+
bulk
1859+
.find({ b: 1 })
1860+
.collation({ locale: locales[0] })
1861+
.updateOne({ $set: { b: 2 } });
1862+
bulk
1863+
.find({ b: 2 })
1864+
.collation({ locale: locales[1] })
1865+
.update({ $set: { b: 3 } });
1866+
bulk.find({ b: 3 }).collation({ locale: locales[2] }).replaceOne({ b: 2 });
1867+
1868+
// deletes
1869+
bulk.find({ b: 2 }).collation({ locale: locales[0] }).removeOne();
1870+
bulk.find({ b: 1 }).collation({ locale: locales[1] }).remove();
1871+
1872+
bulk.execute(err => {
1873+
expect(err).to.not.exist;
1874+
expect(events).to.be.an('array').with.length.at.least(1);
1875+
expect(events[0]).property('commandName').to.equal('update');
1876+
const updateCommand = events[0].command;
1877+
expect(updateCommand).property('updates').to.be.an('array').with.length(3);
1878+
updateCommand.updates.forEach((statement, idx) => {
1879+
expect(statement).property('collation').to.eql({ locale: locales[idx] });
1880+
});
1881+
expect(events[1]).property('commandName').to.equal('delete');
1882+
const deleteCommand = events[1].command;
1883+
expect(deleteCommand).property('deletes').to.be.an('array').with.length(2);
1884+
deleteCommand.deletes.forEach((statement, idx) => {
1885+
expect(statement).property('collation').to.eql({ locale: locales[idx] });
1886+
});
1887+
client.close(done);
1888+
});
1889+
})
1890+
});
1891+
1892+
it('should throw an error if raw operations are passed to bulkWrite', function () {
1893+
const client = this.configuration.newClient();
1894+
return client.connect().then(() => {
1895+
this.defer(() => client.close());
1896+
1897+
const coll = client.db().collection('single_bulk_write_error');
1898+
return coll
1899+
.bulkWrite([
1900+
{ updateOne: { q: { a: 2 }, u: { $set: { a: 2 } }, upsert: true } },
1901+
{ deleteOne: { q: { c: 1 } } }
1902+
])
1903+
.then(
1904+
() => {
1905+
throw new Error('expected a bulk error');
1906+
},
1907+
err => {
1908+
expect(err).to.match(/Raw operations are not allowed/);
1909+
}
1910+
);
1911+
});
1912+
});
18441913
});

test/functional/collations.test.js

+57-7
Original file line numberDiff line numberDiff line change
@@ -536,13 +536,13 @@ describe('Collation', function () {
536536
[
537537
{
538538
updateOne: {
539-
q: { a: 2 },
540-
u: { $set: { a: 2 } },
539+
filter: { a: 2 },
540+
update: { $set: { a: 2 } },
541541
upsert: true,
542542
collation: { caseLevel: true }
543543
}
544544
},
545-
{ deleteOne: { q: { c: 1 } } }
545+
{ deleteOne: { filter: { c: 1 } } }
546546
],
547547
{ ordered: true }
548548
)
@@ -559,7 +559,7 @@ describe('Collation', function () {
559559
}
560560
});
561561

562-
it('Successfully fail bulkWrite due to unsupported collation', {
562+
it('Successfully fail bulkWrite due to unsupported collation in update', {
563563
metadata: { requires: { generators: true, topology: 'single' } },
564564
test: function () {
565565
const configuration = this.configuration;
@@ -588,13 +588,63 @@ describe('Collation', function () {
588588
[
589589
{
590590
updateOne: {
591-
q: { a: 2 },
592-
u: { $set: { a: 2 } },
591+
filter: { a: 2 },
592+
update: { $set: { a: 2 } },
593593
upsert: true,
594594
collation: { caseLevel: true }
595595
}
596596
},
597-
{ deleteOne: { q: { c: 1 } } }
597+
{ deleteOne: { filter: { c: 1 } } }
598+
],
599+
{ ordered: true }
600+
)
601+
.then(() => {
602+
throw new Error('should not succeed');
603+
})
604+
.catch(err => {
605+
expect(err).to.exist;
606+
expect(err.message).to.match(/does not support collation/);
607+
})
608+
.then(() => client.close());
609+
});
610+
}
611+
});
612+
613+
it('Successfully fail bulkWrite due to unsupported collation in delete', {
614+
metadata: { requires: { generators: true, topology: 'single' } },
615+
test: function () {
616+
const configuration = this.configuration;
617+
const client = configuration.newClient(`mongodb://${testContext.server.uri()}/test`);
618+
const primary = [Object.assign({}, mock.DEFAULT_ISMASTER, { maxWireVersion: 4 })];
619+
620+
testContext.server.setMessageHandler(request => {
621+
const doc = request.document;
622+
if (doc.ismaster) {
623+
request.reply(primary[0]);
624+
} else if (doc.update) {
625+
request.reply({ ok: 1 });
626+
} else if (doc.delete) {
627+
request.reply({ ok: 1 });
628+
} else if (doc.endSessions) {
629+
request.reply({ ok: 1 });
630+
}
631+
});
632+
633+
return client.connect().then(() => {
634+
const db = client.db(configuration.db);
635+
636+
return db
637+
.collection('test')
638+
.bulkWrite(
639+
[
640+
{
641+
updateOne: {
642+
filter: { a: 2 },
643+
update: { $set: { a: 2 } },
644+
upsert: true
645+
}
646+
},
647+
{ deleteOne: { filter: { c: 1 }, collation: { caseLevel: true } } }
598648
],
599649
{ ordered: true }
600650
)

test/functional/crud_api.test.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -357,12 +357,12 @@ describe('CRUD API', function () {
357357

358358
db.collection('t2_5').bulkWrite(
359359
[
360-
{ insertOne: { a: 1 } },
360+
{ insertOne: { document: { a: 1 } } },
361361
{ insertMany: [{ g: 1 }, { g: 2 }] },
362-
{ updateOne: { q: { a: 2 }, u: { $set: { a: 2 } }, upsert: true } },
363-
{ updateMany: { q: { a: 2 }, u: { $set: { a: 2 } }, upsert: true } },
364-
{ deleteOne: { q: { c: 1 } } },
365-
{ deleteMany: { q: { c: 1 } } }
362+
{ updateOne: { filter: { a: 2 }, update: { $set: { a: 2 } }, upsert: true } },
363+
{ updateMany: { filter: { a: 2 }, update: { $set: { a: 2 } }, upsert: true } },
364+
{ deleteOne: { filter: { c: 1 } } },
365+
{ deleteMany: { filter: { c: 1 } } }
366366
],
367367
{ ordered: false, writeConcern: { w: 1 } },
368368
function (err, r) {
@@ -442,12 +442,12 @@ describe('CRUD API', function () {
442442

443443
db.collection('t2_7').bulkWrite(
444444
[
445-
{ insertOne: { a: 1 } },
445+
{ insertOne: { document: { a: 1 } } },
446446
{ insertMany: [{ g: 1 }, { g: 2 }] },
447-
{ updateOne: { q: { a: 2 }, u: { $set: { a: 2 } }, upsert: true } },
448-
{ updateMany: { q: { a: 2 }, u: { $set: { a: 2 } }, upsert: true } },
449-
{ deleteOne: { q: { c: 1 } } },
450-
{ deleteMany: { q: { c: 1 } } }
447+
{ updateOne: { filter: { a: 2 }, update: { $set: { a: 2 } }, upsert: true } },
448+
{ updateMany: { filter: { a: 2 }, update: { $set: { a: 2 } }, upsert: true } },
449+
{ deleteOne: { filter: { c: 1 } } },
450+
{ deleteMany: { filter: { c: 1 } } }
451451
],
452452
{ ordered: true, writeConcern: { w: 1 } },
453453
function (err, r) {

0 commit comments

Comments
 (0)