From c9229ace268379da61d18fac192f96f440a65fe4 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Tue, 18 Jan 2022 16:40:28 -0800 Subject: [PATCH] PYTHON-3061 Add 'let' option to ReplaceOptions (#832) --- doc/changelog.rst | 8 +- pymongo/collection.py | 13 +- test/crud/unified/replaceOne-let.json | 207 ++++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 6 deletions(-) create mode 100644 test/crud/unified/replaceOne-let.json diff --git a/doc/changelog.rst b/doc/changelog.rst index 4ff9cd781..de38f188e 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -14,9 +14,11 @@ PyMongo 4.1 brings a number of improvements including: :meth:`pymongo.collection.Collection.find_one_and_delete`, :meth:`pymongo.collection.Collection.find_one_and_replace`, :meth:`pymongo.collection.Collection.find_one_and_update`, - and :meth:`pymongo.collection.Collection.find` all support a new keyword - argument ``let`` which is a map of parameter names and values. Parameters - can then be accessed as variables in an aggregate expression context. + :meth:`pymongo.collection.Collection.find`, + and :meth:`pymongo.collection.Collection.replace_one `all support a new + keyword argument ``let`` which is a map of parameter names and values. + Parameters can then be accessed as variables in an aggregate expression + context. - :meth:`~pymongo.collection.Collection.aggregate` now supports $merge and $out executing on secondaries on MongoDB >=5.0. aggregate() now always obeys the collection's :attr:`read_preference` on diff --git a/pymongo/collection.py b/pymongo/collection.py index 82e29f406..0a8d01121 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -673,7 +673,7 @@ class Collection(common.BaseObject): def replace_one(self, filter, replacement, upsert=False, bypass_document_validation=False, collation=None, - hint=None, session=None): + hint=None, session=None, let=None): """Replace a single document matching the filter. >>> for doc in db.test.find({}): @@ -721,10 +721,16 @@ class Collection(common.BaseObject): MongoDB 4.2 and above. - `session` (optional): a :class:`~pymongo.client_session.ClientSession`. + - `let` (optional): Map of parameter names and values. Values must be + constant or closed expressions that do not reference document + fields. Parameters can then be accessed as variables in an + aggregate expression context (e.g. "$$var"). :Returns: - An instance of :class:`~pymongo.results.UpdateResult`. + .. versionchanged:: 4.1 + Added ``let`` parameter. .. versionchanged:: 3.11 Added ``hint`` parameter. .. versionchanged:: 3.6 @@ -738,14 +744,15 @@ class Collection(common.BaseObject): """ common.validate_is_mapping("filter", filter) common.validate_ok_for_replace(replacement) - + if let: + common.validate_is_mapping("let", let) write_concern = self._write_concern_for(session) return UpdateResult( self._update_retryable( filter, replacement, upsert, write_concern=write_concern, bypass_doc_val=bypass_document_validation, - collation=collation, hint=hint, session=session), + collation=collation, hint=hint, session=session, let=let), write_concern.acknowledged) def update_one(self, filter, update, upsert=False, diff --git a/test/crud/unified/replaceOne-let.json b/test/crud/unified/replaceOne-let.json new file mode 100644 index 000000000..6cf8e1567 --- /dev/null +++ b/test/crud/unified/replaceOne-let.json @@ -0,0 +1,207 @@ +{ + "description": "replaceOne-let", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "ReplaceOne with let option", + "runOnRequirements": [ + { + "minServerVersion": "5.0" + } + ], + "operations": [ + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + }, + "replacement": { + "x": "foo" + }, + "let": { + "id": 1 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + }, + "u": { + "x": "foo" + } + } + ], + "let": { + "id": 1 + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": "foo" + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "ReplaceOne with let option unsupported (server-side error)", + "runOnRequirements": [ + { + "minServerVersion": "3.6.0", + "maxServerVersion": "4.4.99" + } + ], + "operations": [ + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + }, + "replacement": { + "x": "foo" + }, + "let": { + "id": 1 + } + }, + "expectError": { + "errorContains": "'update.let' is an unknown field", + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "$expr": { + "$eq": [ + "$_id", + "$$id" + ] + } + }, + "u": { + "x": "foo" + } + } + ], + "let": { + "id": 1 + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + } + ] +}