PYTHON-1798 Support pipelines in update commands
This commit is contained in:
parent
694a4a5d85
commit
aefd02a801
@ -51,6 +51,12 @@ Version 3.9 adds support for MongoDB 4.2. Highlights include:
|
||||
:mod:`~pymongo.monitoring` for an example.
|
||||
- :meth:`pymongo.collection.Collection.aggregate` and
|
||||
:meth:`pymongo.database.Database.aggregate` now support the ``$merge`` pipeline
|
||||
- Support for specifying a pipeline or document in
|
||||
:meth:`~pymongo.collection.Collection.update_one`,
|
||||
:meth:`~pymongo.collection.Collection.update_many`,
|
||||
:meth:`~pymongo.collection.Collection.find_one_and_update`,
|
||||
:meth:`~pymongo.operations.UpdateOne`, and
|
||||
:meth:`~pymongo.operations.UpdateMany`.
|
||||
|
||||
.. _URI options specification: https://github.com/mongodb/specifications/blob/master/source/uri-options/uri-options.rst
|
||||
|
||||
|
||||
@ -974,6 +974,9 @@ class Collection(common.BaseObject):
|
||||
.. note:: `bypass_document_validation` requires server version
|
||||
**>= 3.2**
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
Added the ability to accept a pipeline as the `update`.
|
||||
|
||||
.. versionchanged:: 3.6
|
||||
Added the `array_filters` and ``session`` parameters.
|
||||
|
||||
@ -1044,6 +1047,9 @@ class Collection(common.BaseObject):
|
||||
.. note:: `bypass_document_validation` requires server version
|
||||
**>= 3.2**
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
Added the ability to accept a pipeline as the `update`.
|
||||
|
||||
.. versionchanged:: 3.6
|
||||
Added ``array_filters`` and ``session`` parameters.
|
||||
|
||||
@ -3087,6 +3093,8 @@ class Collection(common.BaseObject):
|
||||
as keyword arguments (for example maxTimeMS can be used with
|
||||
recent server versions).
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
Added the ability to accept a pipeline as the `update`.
|
||||
.. versionchanged:: 3.6
|
||||
Added the `array_filters` and `session` options.
|
||||
.. versionchanged:: 3.4
|
||||
|
||||
@ -457,11 +457,19 @@ def validate_list_or_none(option, value):
|
||||
return validate_list(option, value)
|
||||
|
||||
|
||||
def validate_list_or_mapping(option, value):
|
||||
"""Validates that 'value' is a list or a document."""
|
||||
if not isinstance(value, (abc.Mapping, list)):
|
||||
raise TypeError("%s must either be a list or an instance of dict, "
|
||||
"bson.son.SON, or any other type that inherits from "
|
||||
"collections.Mapping" % (option,))
|
||||
|
||||
|
||||
def validate_is_mapping(option, value):
|
||||
"""Validate the type of method arguments that expect a document."""
|
||||
if not isinstance(value, abc.Mapping):
|
||||
raise TypeError("%s must be an instance of dict, bson.son.SON, or "
|
||||
"other type that inherits from "
|
||||
"any other type that inherits from "
|
||||
"collections.Mapping" % (option,))
|
||||
|
||||
|
||||
@ -515,12 +523,14 @@ def validate_ok_for_replace(replacement):
|
||||
|
||||
def validate_ok_for_update(update):
|
||||
"""Validate an update document."""
|
||||
validate_is_mapping("update", update)
|
||||
# Update can not be {}
|
||||
validate_list_or_mapping("update", update)
|
||||
# Update cannot be {}.
|
||||
if not update:
|
||||
raise ValueError('update only works with $ operators')
|
||||
raise ValueError('update cannot be empty')
|
||||
|
||||
is_document = not isinstance(update, list)
|
||||
first = next(iter(update))
|
||||
if not first.startswith('$'):
|
||||
if is_document and not first.startswith('$'):
|
||||
raise ValueError('update only works with $ operators')
|
||||
|
||||
|
||||
|
||||
@ -243,6 +243,8 @@ class UpdateOne(_UpdateOp):
|
||||
- `array_filters` (optional): A list of filters specifying which
|
||||
array elements an update should apply. Requires MongoDB 3.6+.
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
Added the ability to accept a pipeline as the `update`.
|
||||
.. versionchanged:: 3.6
|
||||
Added the `array_filters` option.
|
||||
.. versionchanged:: 3.5
|
||||
@ -280,6 +282,8 @@ class UpdateMany(_UpdateOp):
|
||||
- `array_filters` (optional): A list of filters specifying which
|
||||
array elements an update should apply. Requires MongoDB 3.6+.
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
Added the ability to accept a pipeline as the `update`.
|
||||
.. versionchanged:: 3.6
|
||||
Added the `array_filters` option.
|
||||
.. versionchanged:: 3.5
|
||||
|
||||
239
test/crud/v2/updateWithPipelines.json
Normal file
239
test/crud/v2/updateWithPipelines.json
Normal file
@ -0,0 +1,239 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"t": {
|
||||
"u": {
|
||||
"v": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 2,
|
||||
"y": 1
|
||||
}
|
||||
],
|
||||
"minServerVersion": "4.1.11",
|
||||
"collection_name": "test",
|
||||
"database_name": "crud-tests",
|
||||
"tests": [
|
||||
{
|
||||
"description": "UpdateOne using pipelines",
|
||||
"operations": [
|
||||
{
|
||||
"name": "updateOne",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 1
|
||||
},
|
||||
"update": [
|
||||
{
|
||||
"$replaceRoot": {
|
||||
"newRoot": "$t"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$addFields": {
|
||||
"foo": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"matchedCount": 1,
|
||||
"modifiedCount": 1,
|
||||
"upsertedCount": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"command_started_event": {
|
||||
"command": {
|
||||
"update": "test",
|
||||
"updates": [
|
||||
{
|
||||
"q": {
|
||||
"_id": 1
|
||||
},
|
||||
"u": [
|
||||
{
|
||||
"$replaceRoot": {
|
||||
"newRoot": "$t"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$addFields": {
|
||||
"foo": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"command_name": "update",
|
||||
"database_name": "crud-tests"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outcome": {
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"u": {
|
||||
"v": 1
|
||||
},
|
||||
"foo": 1
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 2,
|
||||
"y": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "UpdateMany using pipelines",
|
||||
"operations": [
|
||||
{
|
||||
"name": "updateMany",
|
||||
"arguments": {
|
||||
"filter": {},
|
||||
"update": [
|
||||
{
|
||||
"$project": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$addFields": {
|
||||
"foo": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"matchedCount": 2,
|
||||
"modifiedCount": 2,
|
||||
"upsertedCount": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"command_started_event": {
|
||||
"command": {
|
||||
"update": "test",
|
||||
"updates": [
|
||||
{
|
||||
"q": {},
|
||||
"u": [
|
||||
{
|
||||
"$project": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$addFields": {
|
||||
"foo": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"multi": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"command_name": "update",
|
||||
"database_name": "crud-tests"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outcome": {
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 1,
|
||||
"foo": 1
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 2,
|
||||
"foo": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "FindOneAndUpdate using pipelines",
|
||||
"operations": [
|
||||
{
|
||||
"name": "findOneAndUpdate",
|
||||
"arguments": {
|
||||
"filter": {
|
||||
"_id": 1
|
||||
},
|
||||
"update": [
|
||||
{
|
||||
"$project": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$addFields": {
|
||||
"foo": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"command_started_event": {
|
||||
"command": {
|
||||
"findAndModify": "test",
|
||||
"update": [
|
||||
{
|
||||
"$project": {
|
||||
"x": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"$addFields": {
|
||||
"foo": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"command_name": "findAndModify",
|
||||
"database_name": "crud-tests"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outcome": {
|
||||
"collection": {
|
||||
"data": [
|
||||
{
|
||||
"_id": 1,
|
||||
"x": 1,
|
||||
"foo": 1
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"x": 2,
|
||||
"y": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -139,7 +139,7 @@ class TestBulk(BulkTestBase):
|
||||
self.assertEqual(1, result.inserted_count)
|
||||
self.assertEqual(1, self.coll.count_documents({}))
|
||||
|
||||
def test_update_many(self):
|
||||
def _test_update_many(self, update):
|
||||
|
||||
expected = {
|
||||
'nMatched': 2,
|
||||
@ -153,12 +153,18 @@ class TestBulk(BulkTestBase):
|
||||
}
|
||||
self.coll.insert_many([{}, {}])
|
||||
|
||||
result = self.coll.bulk_write([UpdateMany({},
|
||||
{'$set': {'foo': 'bar'}})])
|
||||
result = self.coll.bulk_write([UpdateMany({}, update)])
|
||||
self.assertEqualResponse(expected, result.bulk_api_result)
|
||||
self.assertEqual(2, result.matched_count)
|
||||
self.assertTrue(result.modified_count in (2, None))
|
||||
|
||||
def test_update_many(self):
|
||||
self._test_update_many({'$set': {'foo': 'bar'}})
|
||||
|
||||
@client_context.require_version_min(4, 1, 11)
|
||||
def test_update_many_pipeline(self):
|
||||
self._test_update_many([{'$set': {'foo': 'bar'}}])
|
||||
|
||||
@client_context.require_version_max(3, 5, 5)
|
||||
def test_array_filters_unsupported(self):
|
||||
requests = [
|
||||
@ -184,8 +190,7 @@ class TestBulk(BulkTestBase):
|
||||
self.assertRaises(ConfigurationError, coll.bulk_write, [update_one])
|
||||
self.assertRaises(ConfigurationError, coll.bulk_write, [update_many])
|
||||
|
||||
def test_update_one(self):
|
||||
|
||||
def _test_update_one(self, update):
|
||||
expected = {
|
||||
'nMatched': 1,
|
||||
'nModified': 1,
|
||||
@ -199,12 +204,18 @@ class TestBulk(BulkTestBase):
|
||||
|
||||
self.coll.insert_many([{}, {}])
|
||||
|
||||
result = self.coll.bulk_write([UpdateOne({},
|
||||
{'$set': {'foo': 'bar'}})])
|
||||
result = self.coll.bulk_write([UpdateOne({}, update)])
|
||||
self.assertEqualResponse(expected, result.bulk_api_result)
|
||||
self.assertEqual(1, result.matched_count)
|
||||
self.assertTrue(result.modified_count in (1, None))
|
||||
|
||||
def test_update_one(self):
|
||||
self._test_update_one({'$set': {'foo': 'bar'}})
|
||||
|
||||
@client_context.require_version_min(4, 1, 11)
|
||||
def test_update_one_pipeline(self):
|
||||
self._test_update_one([{'$set': {'foo': 'bar'}}])
|
||||
|
||||
def test_replace_one(self):
|
||||
|
||||
expected = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user