Skip to content

Commit 6f4258c

Browse files
authored
PYTHON-4576 Allow update to supply sort option (mongodb#1881)
1 parent 29064f5 commit 6f4258c

18 files changed

+1967
-133
lines changed

doc/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ PyMongo 4.11 brings a number of changes including:
1919
until either connection succeeds or a server selection timeout error is raised.
2020
- Added :func:`repr` support to :class:`pymongo.operations.IndexModel`.
2121
- Added :func:`repr` support to :class:`pymongo.operations.SearchIndexModel`.
22+
- Added ``sort`` parameter to
23+
:meth:`~pymongo.collection.Collection.update_one`, :meth:`~pymongo.collection.Collection.replace_one`,
24+
:class:`~pymongo.operations.UpdateOne`, and
25+
:class:`~pymongo.operations.UpdateMany`,
2226

2327
Issues Resolved
2428
...............

pymongo/asynchronous/bulk.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def __init__(
109109
self.uses_array_filters = False
110110
self.uses_hint_update = False
111111
self.uses_hint_delete = False
112+
self.uses_sort = False
112113
self.is_retryable = True
113114
self.retrying = False
114115
self.started_retryable_write = False
@@ -144,6 +145,7 @@ def add_update(
144145
collation: Optional[Mapping[str, Any]] = None,
145146
array_filters: Optional[list[Mapping[str, Any]]] = None,
146147
hint: Union[str, dict[str, Any], None] = None,
148+
sort: Optional[Mapping[str, Any]] = None,
147149
) -> None:
148150
"""Create an update document and add it to the list of ops."""
149151
validate_ok_for_update(update)
@@ -159,6 +161,9 @@ def add_update(
159161
if hint is not None:
160162
self.uses_hint_update = True
161163
cmd["hint"] = hint
164+
if sort is not None:
165+
self.uses_sort = True
166+
cmd["sort"] = sort
162167
if multi:
163168
# A bulk_write containing an update_many is not retryable.
164169
self.is_retryable = False
@@ -171,6 +176,7 @@ def add_replace(
171176
upsert: bool = False,
172177
collation: Optional[Mapping[str, Any]] = None,
173178
hint: Union[str, dict[str, Any], None] = None,
179+
sort: Optional[Mapping[str, Any]] = None,
174180
) -> None:
175181
"""Create a replace document and add it to the list of ops."""
176182
validate_ok_for_replace(replacement)
@@ -181,6 +187,9 @@ def add_replace(
181187
if hint is not None:
182188
self.uses_hint_update = True
183189
cmd["hint"] = hint
190+
if sort is not None:
191+
self.uses_sort = True
192+
cmd["sort"] = sort
184193
self.ops.append((_UPDATE, cmd))
185194

186195
def add_delete(
@@ -699,6 +708,10 @@ async def execute_no_results(
699708
raise ConfigurationError(
700709
"Must be connected to MongoDB 4.2+ to use hint on unacknowledged update commands."
701710
)
711+
if unack and self.uses_sort and conn.max_wire_version < 25:
712+
raise ConfigurationError(
713+
"Must be connected to MongoDB 8.0+ to use sort on unacknowledged update commands."
714+
)
702715
# Cannot have both unacknowledged writes and bypass document validation.
703716
if self.bypass_doc_val:
704717
raise OperationFailure(

pymongo/asynchronous/client_bulk.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def __init__(
118118
self.uses_array_filters = False
119119
self.uses_hint_update = False
120120
self.uses_hint_delete = False
121+
self.uses_sort = False
121122

122123
self.is_retryable = self.client.options.retry_writes
123124
self.retrying = False
@@ -148,6 +149,7 @@ def add_update(
148149
collation: Optional[Mapping[str, Any]] = None,
149150
array_filters: Optional[list[Mapping[str, Any]]] = None,
150151
hint: Union[str, dict[str, Any], None] = None,
152+
sort: Optional[Mapping[str, Any]] = None,
151153
) -> None:
152154
"""Create an update document and add it to the list of ops."""
153155
validate_ok_for_update(update)
@@ -169,6 +171,9 @@ def add_update(
169171
if collation is not None:
170172
self.uses_collation = True
171173
cmd["collation"] = collation
174+
if sort is not None:
175+
self.uses_sort = True
176+
cmd["sort"] = sort
172177
if multi:
173178
# A bulk_write containing an update_many is not retryable.
174179
self.is_retryable = False
@@ -184,6 +189,7 @@ def add_replace(
184189
upsert: Optional[bool] = None,
185190
collation: Optional[Mapping[str, Any]] = None,
186191
hint: Union[str, dict[str, Any], None] = None,
192+
sort: Optional[Mapping[str, Any]] = None,
187193
) -> None:
188194
"""Create a replace document and add it to the list of ops."""
189195
validate_ok_for_replace(replacement)
@@ -202,6 +208,9 @@ def add_replace(
202208
if collation is not None:
203209
self.uses_collation = True
204210
cmd["collation"] = collation
211+
if sort is not None:
212+
self.uses_sort = True
213+
cmd["sort"] = sort
205214
self.ops.append(("replace", cmd))
206215
self.namespaces.append(namespace)
207216
self.total_ops += 1

pymongo/asynchronous/collection.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,7 @@ async def _update(
993993
session: Optional[AsyncClientSession] = None,
994994
retryable_write: bool = False,
995995
let: Optional[Mapping[str, Any]] = None,
996+
sort: Optional[Mapping[str, Any]] = None,
996997
comment: Optional[Any] = None,
997998
) -> Optional[Mapping[str, Any]]:
998999
"""Internal update / replace helper."""
@@ -1024,6 +1025,14 @@ async def _update(
10241025
if not isinstance(hint, str):
10251026
hint = helpers_shared._index_document(hint)
10261027
update_doc["hint"] = hint
1028+
if sort is not None:
1029+
if not acknowledged and conn.max_wire_version < 25:
1030+
raise ConfigurationError(
1031+
"Must be connected to MongoDB 8.0+ to use sort on unacknowledged update commands."
1032+
)
1033+
common.validate_is_mapping("sort", sort)
1034+
update_doc["sort"] = sort
1035+
10271036
command = {"update": self.name, "ordered": ordered, "updates": [update_doc]}
10281037
if let is not None:
10291038
common.validate_is_mapping("let", let)
@@ -1079,6 +1088,7 @@ async def _update_retryable(
10791088
hint: Optional[_IndexKeyHint] = None,
10801089
session: Optional[AsyncClientSession] = None,
10811090
let: Optional[Mapping[str, Any]] = None,
1091+
sort: Optional[Mapping[str, Any]] = None,
10821092
comment: Optional[Any] = None,
10831093
) -> Optional[Mapping[str, Any]]:
10841094
"""Internal update / replace helper."""
@@ -1102,6 +1112,7 @@ async def _update(
11021112
session=session,
11031113
retryable_write=retryable_write,
11041114
let=let,
1115+
sort=sort,
11051116
comment=comment,
11061117
)
11071118

@@ -1122,6 +1133,7 @@ async def replace_one(
11221133
hint: Optional[_IndexKeyHint] = None,
11231134
session: Optional[AsyncClientSession] = None,
11241135
let: Optional[Mapping[str, Any]] = None,
1136+
sort: Optional[Mapping[str, Any]] = None,
11251137
comment: Optional[Any] = None,
11261138
) -> UpdateResult:
11271139
"""Replace a single document matching the filter.
@@ -1176,8 +1188,13 @@ async def replace_one(
11761188
aggregate expression context (e.g. "$$var").
11771189
:param comment: A user-provided comment to attach to this
11781190
command.
1191+
:param sort: Specify which document the operation updates if the query matches
1192+
multiple documents. The first document matched by the sort order will be updated.
1193+
This option is only supported on MongoDB 8.0 and above.
11791194
:return: - An instance of :class:`~pymongo.results.UpdateResult`.
11801195
1196+
.. versionchanged:: 4.11
1197+
Added ``sort`` parameter.
11811198
.. versionchanged:: 4.1
11821199
Added ``let`` parameter.
11831200
Added ``comment`` parameter.
@@ -1209,6 +1226,7 @@ async def replace_one(
12091226
hint=hint,
12101227
session=session,
12111228
let=let,
1229+
sort=sort,
12121230
comment=comment,
12131231
),
12141232
write_concern.acknowledged,
@@ -1225,6 +1243,7 @@ async def update_one(
12251243
hint: Optional[_IndexKeyHint] = None,
12261244
session: Optional[AsyncClientSession] = None,
12271245
let: Optional[Mapping[str, Any]] = None,
1246+
sort: Optional[Mapping[str, Any]] = None,
12281247
comment: Optional[Any] = None,
12291248
) -> UpdateResult:
12301249
"""Update a single document matching the filter.
@@ -1283,11 +1302,16 @@ async def update_one(
12831302
constant or closed expressions that do not reference document
12841303
fields. Parameters can then be accessed as variables in an
12851304
aggregate expression context (e.g. "$$var").
1305+
:param sort: Specify which document the operation updates if the query matches
1306+
multiple documents. The first document matched by the sort order will be updated.
1307+
This option is only supported on MongoDB 8.0 and above.
12861308
:param comment: A user-provided comment to attach to this
12871309
command.
12881310
12891311
:return: - An instance of :class:`~pymongo.results.UpdateResult`.
12901312
1313+
.. versionchanged:: 4.11
1314+
Added ``sort`` parameter.
12911315
.. versionchanged:: 4.1
12921316
Added ``let`` parameter.
12931317
Added ``comment`` parameter.
@@ -1322,6 +1346,7 @@ async def update_one(
13221346
hint=hint,
13231347
session=session,
13241348
let=let,
1349+
sort=sort,
13251350
comment=comment,
13261351
),
13271352
write_concern.acknowledged,

0 commit comments

Comments
 (0)