diff --git a/source/crud/bulk-write.md b/source/crud/bulk-write.md index 25256320fc..6125254370 100644 --- a/source/crud/bulk-write.md +++ b/source/crud/bulk-write.md @@ -897,19 +897,6 @@ recording the summary counts. We expect that most users are not interested in th and that most users will rely on defaults, so `verboseResults` defaults to `false` to improve performance in the common case. -### Why should drivers send `bypassDocumentValidation: false` for `bulkWrite`? - -[DRIVERS-450](https://jira.mongodb.org/browse/DRIVERS-450) introduced a requirement that drivers only send a value for -`bypassDocumentValidation` on write commands if it was specified as true. The original motivation for this change is not -documented. This specification requires that drivers send `bypassDocumentValidation` in the `bulkWrite` command if it is -set by the user in `BulkWriteOptions`, regardless of its value. - -Explicitly defining `bypassDocumentValidation: false` aligns with the server's default to perform schema validation and -thus has no effect. However, checking the value of an option that the user specified and omitting it from the command -document if it matches the server's default creates unnecessary work for drivers. Always sending the user's specified -value also safeguards against the unlikely event that the server changes the default value for -`bypassDocumentValidation` in the future. - ### Why is providing access to the raw server response when a command error occurs required? This allows users to access new error fields that the server may add in the future without needing to upgrade their diff --git a/source/crud/crud.md b/source/crud/crud.md index 87d21ff053..2b1fb19b88 100644 --- a/source/crud/crud.md +++ b/source/crud/crud.md @@ -924,7 +924,7 @@ class BulkWriteOptions { /** * If true, allows the write to opt-out of document level validation. * - * This option is sent only if the caller explicitly provides a true value. The default is to not send a value. + * This option is sent only if the caller explicitly provides a value. The default is to not send a value. * For servers < 3.2, this option is ignored and not sent as document validation is not available. * For unacknowledged writes using OP_INSERT, OP_UPDATE, or OP_DELETE, the driver MUST raise an error if the caller explicitly provides a value. */ @@ -959,7 +959,7 @@ class InsertOneOptions { /** * If true, allows the write to opt-out of document level validation. * - * This option is sent only if the caller explicitly provides a true value. The default is to not send a value. + * This option is sent only if the caller explicitly provides a value. The default is to not send a value. * For servers < 3.2, this option is ignored and not sent as document validation is not available. * For unacknowledged writes using OP_INSERT, the driver MUST raise an error if the caller explicitly provides a value. */ @@ -981,7 +981,7 @@ class InsertManyOptions { /** * If true, allows the write to opt-out of document level validation. * - * This option is sent only if the caller explicitly provides a true value. The default is to not send a value. + * This option is sent only if the caller explicitly provides a value. The default is to not send a value. * For servers < 3.2, this option is ignored and not sent as document validation is not available. * For unacknowledged writes using OP_INSERT, the driver MUST raise an error if the caller explicitly provides a value. */ @@ -1021,7 +1021,7 @@ class UpdateOptions { /** * If true, allows the write to opt-out of document level validation. * - * This option is sent only if the caller explicitly provides a true value. The default is to not send a value. + * This option is sent only if the caller explicitly provides a value. The default is to not send a value. * For servers < 3.2, this option is ignored and not sent as document validation is not available. * For unacknowledged writes using OP_UPDATE, the driver MUST raise an error if the caller explicitly provides a value. */ @@ -1101,7 +1101,7 @@ class ReplaceOptions { /** * If true, allows the write to opt-out of document level validation. * - * This option is sent only if the caller explicitly provides a true value. The default is to not send a value. + * This option is sent only if the caller explicitly provides a value. The default is to not send a value. * For servers < 3.2, this option is ignored and not sent as document validation is not available. * For unacknowledged writes using OP_UPDATE, the driver MUST raise an error if the caller explicitly provides a value. */ @@ -1983,7 +1983,7 @@ class FindOneAndReplaceOptions { /** * If true, allows the write to opt-out of document level validation. * - * This option is sent only if the caller explicitly provides a true value. The default is to not send a value. + * This option is sent only if the caller explicitly provides a value. The default is to not send a value. * For servers < 3.2, this option is ignored and not sent as document validation is not available. */ bypassDocumentValidation: Optional; @@ -2101,7 +2101,7 @@ class FindOneAndUpdateOptions { /** * If true, allows the write to opt-out of document level validation. * - * This option is sent only if the caller explicitly provides a true value. The default is to not send a value. + * This option is sent only if the caller explicitly provides a value. The default is to not send a value. * For servers < 3.2, this option is ignored and not sent as document validation is not available. */ bypassDocumentValidation: Optional; @@ -2486,6 +2486,8 @@ aforementioned allowance in the SemVer spec. ## Changelog +- 2024-11-04: Always send a value for `bypassDocumentValidation` if it was specified. + - 2024-10-30: Document query limitations in `countDocuments`. - 2024-10-28: Clarified that generated identifiers should be prepended to documents. diff --git a/source/crud/tests/unified/bypassDocumentValidation.json b/source/crud/tests/unified/bypassDocumentValidation.json new file mode 100644 index 0000000000..aff2d37f81 --- /dev/null +++ b/source/crud/tests/unified/bypassDocumentValidation.json @@ -0,0 +1,493 @@ +{ + "description": "bypassDocumentValidation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.2", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Aggregate with $out passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "bypassDocumentValidation": false + }, + "commandName": "aggregate", + "databaseName": "crud" + } + } + ] + } + ] + }, + { + "description": "BulkWrite passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "FindOneAndReplace passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 32 + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "coll", + "query": { + "_id": { + "$gt": 1 + } + }, + "update": { + "x": 32 + }, + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "coll", + "query": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "InsertMany passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "InsertOne passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 4, + "x": 44 + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "ReplaceOne passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 32 + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 32 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "UpdateMany passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "UpdateOne passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + } + ] +} diff --git a/source/crud/tests/unified/bypassDocumentValidation.yml b/source/crud/tests/unified/bypassDocumentValidation.yml new file mode 100644 index 0000000000..5082564752 --- /dev/null +++ b/source/crud/tests/unified/bypassDocumentValidation.yml @@ -0,0 +1,222 @@ +description: bypassDocumentValidation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.2' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'Aggregate with $out passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: aggregate + arguments: + pipeline: &pipeline + - { $sort: { x: 1 } } + - { $match: { _id: { $gt: 1 } } } + - { $out: other_test_collection } + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection_name + pipeline: *pipeline + bypassDocumentValidation: false + commandName: aggregate + databaseName: *database_name + - + description: 'BulkWrite passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: &inserted_document { _id: 4, x: 44 } + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - *inserted_document + bypassDocumentValidation: false + - + description: 'FindOneAndReplace passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: &filter { _id: { $gt: 1 } } + replacement: &replacement { x: 32 } + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + findAndModify: *collection_name + query: *filter + update: *replacement + bypassDocumentValidation: false + - + description: 'FindOneAndUpdate passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: *filter + update: &update { $inc: { x: 1 } } + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + findAndModify: *collection_name + query: *filter + update: *update + bypassDocumentValidation: false + - + description: 'InsertMany passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: insertMany + arguments: + documents: + - *inserted_document + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - *inserted_document + bypassDocumentValidation: false + - + description: 'InsertOne passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: insertOne + arguments: + document: *inserted_document + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - *inserted_document + bypassDocumentValidation: false + - + description: 'ReplaceOne passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: replaceOne + arguments: + filter: *filter + replacement: *replacement + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection_name + updates: + - + q: *filter + u: *replacement + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + bypassDocumentValidation: false + - + description: 'UpdateMany passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: *filter + update: *update + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection_name + updates: + - + q: *filter + u: *update + multi: true + upsert: { $$unsetOrMatches: false } + bypassDocumentValidation: false + - + description: 'UpdateOne passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: *filter + update: *update + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection_name + updates: + - + q: *filter + u: *update + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + bypassDocumentValidation: false