Skip to content

Commit 651aa6a

Browse files
committed
PYTHON-2035: support for allowDiskUse in find() commands
1 parent 1323ef1 commit 651aa6a

File tree

6 files changed

+173
-33
lines changed

6 files changed

+173
-33
lines changed

doc/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Changes in Version 3.11.0
66

77
Version 3.11 adds support for MongoDB 4.4. Highlights include:
88

9+
- Added the ``allow_disk_use`` parameters to
10+
:meth:`pymongo.collection.Collection.find`.
911
- Support for :ref:`OCSP` (Online Certificate Status Protocol)
1012
- Support for `PyOpenSSL <https://pypi.org/project/pyOpenSSL/>`_ as an
1113
alternative TLS implementation. PyOpenSSL is required for :ref:`OCSP`

pymongo/collection.py

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,6 +1412,11 @@ def find(self, *args, **kwargs):
14121412
- `modifiers` (optional): **DEPRECATED** - A dict specifying
14131413
additional MongoDB query modifiers. Use the keyword arguments listed
14141414
above instead.
1415+
- `allow_disk_use` (optional): if True, MongoDB may use temporary
1416+
disk files to store data exceeding the system memory limit while
1417+
processing a blocking sort operation. The option has no effect if
1418+
MongoDB can satisfy the specified sort using an index, or if the
1419+
blocking sort requires less memory than the 100 MiB limit.
14151420
14161421
.. note:: There are a number of caveats to using
14171422
:attr:`~pymongo.cursor.CursorType.EXHAUST` as cursor_type:
@@ -1429,48 +1434,55 @@ def find(self, *args, **kwargs):
14291434
connection will be closed and discarded without being returned to
14301435
the connection pool.
14311436
1437+
.. versionchanged:: 3.11
1438+
Added the ``allow_disk_use`` option.
1439+
14321440
.. versionchanged:: 3.7
1433-
Deprecated the `snapshot` option, which is deprecated in MongoDB
1441+
Deprecated the ``snapshot`` option, which is deprecated in MongoDB
14341442
3.6 and removed in MongoDB 4.0.
1435-
Deprecated the `max_scan` option. Support for this option is
1436-
deprecated in MongoDB 4.0. Use `max_time_ms` instead to limit server
1437-
side execution time.
1438-
1443+
Deprecated the ``max_scan`` option. Support for this option is
1444+
deprecated in MongoDB 4.0. Use ``max_time_ms`` instead to limit
1445+
server-side execution time.
14391446
14401447
.. versionchanged:: 3.6
14411448
Added ``session`` parameter.
14421449
14431450
.. versionchanged:: 3.5
1444-
Added the options `return_key`, `show_record_id`, `snapshot`,
1445-
`hint`, `max_time_ms`, `max_scan`, `min`, `max`, and `comment`.
1446-
Deprecated the option `modifiers`.
1451+
Added the options ``return_key``, ``show_record_id``, ``snapshot``,
1452+
``hint``, ``max_time_ms``, ``max_scan``, ``min``, ``max``, and
1453+
``comment``.
1454+
Deprecated the ``modifiers`` option.
14471455
14481456
.. versionchanged:: 3.4
1449-
Support the `collation` option.
1457+
Added support for the ``collation`` option.
14501458
14511459
.. versionchanged:: 3.0
1452-
Changed the parameter names `spec`, `fields`, `timeout`, and
1453-
`partial` to `filter`, `projection`, `no_cursor_timeout`, and
1454-
`allow_partial_results` respectively.
1455-
Added the `cursor_type`, `oplog_replay`, and `modifiers` options.
1456-
Removed the `network_timeout`, `read_preference`, `tag_sets`,
1457-
`secondary_acceptable_latency_ms`, `max_scan`, `snapshot`,
1458-
`tailable`, `await_data`, `exhaust`, `as_class`, and slave_okay
1459-
parameters. Removed `compile_re` option: PyMongo now always
1460+
Changed the parameter names ``spec``, ``fields``, ``timeout``, and
1461+
``partial`` to ``filter``, ``projection``, ``no_cursor_timeout``,
1462+
and ``allow_partial_results`` respectively.
1463+
Added the ``cursor_type``, ``oplog_replay``, and ``modifiers``
1464+
options.
1465+
Removed the ``network_timeout``, ``read_preference``, ``tag_sets``,
1466+
``secondary_acceptable_latency_ms``, ``max_scan``, ``snapshot``,
1467+
``tailable``, ``await_data``, ``exhaust``, ``as_class``, and
1468+
slave_okay parameters.
1469+
Removed ``compile_re`` option: PyMongo now always
14601470
represents BSON regular expressions as :class:`~bson.regex.Regex`
14611471
objects. Use :meth:`~bson.regex.Regex.try_compile` to attempt to
14621472
convert from a BSON regular expression to a Python regular
1463-
expression object. Soft deprecated the `manipulate` option.
1473+
expression object.
1474+
Soft deprecated the ``manipulate`` option.
14641475
14651476
.. versionchanged:: 2.7
1466-
Added `compile_re` option. If set to False, PyMongo represented BSON
1467-
regular expressions as :class:`~bson.regex.Regex` objects instead of
1468-
attempting to compile BSON regular expressions as Python native
1469-
regular expressions, thus preventing errors for some incompatible
1470-
patterns, see `PYTHON-500`_.
1471-
1472-
.. versionadded:: 2.3
1473-
The `tag_sets` and `secondary_acceptable_latency_ms` parameters.
1477+
Added ``compile_re`` option. If set to False, PyMongo represented
1478+
BSON regular expressions as :class:`~bson.regex.Regex` objects
1479+
instead of attempting to compile BSON regular expressions as Python
1480+
native regular expressions, thus preventing errors for some
1481+
incompatible patterns, see `PYTHON-500`_.
1482+
1483+
.. versionchanged:: 2.3
1484+
Added the ``tag_sets`` and ``secondary_acceptable_latency_ms``
1485+
parameters.
14741486
14751487
.. _PYTHON-500: https://jira.mongodb.org/browse/PYTHON-500
14761488

pymongo/cursor.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ def __init__(self, collection, filter=None, projection=None, skip=0,
114114
modifiers=None, batch_size=0, manipulate=True,
115115
collation=None, hint=None, max_scan=None, max_time_ms=None,
116116
max=None, min=None, return_key=False, show_record_id=False,
117-
snapshot=False, comment=None, session=None):
117+
snapshot=False, comment=None, session=None,
118+
allow_disk_use=None):
118119
"""Create a new cursor.
119120
120121
Should not be called directly by application developers - see
@@ -159,6 +160,9 @@ def __init__(self, collection, filter=None, projection=None, skip=0,
159160
raise TypeError("batch_size must be an integer")
160161
if batch_size < 0:
161162
raise ValueError("batch_size must be >= 0")
163+
# Only set if allow_disk_use is provided by the user, else None.
164+
if allow_disk_use is not None:
165+
allow_disk_use = validate_boolean("allow_disk_use", allow_disk_use)
162166

163167
if projection is not None:
164168
if not projection:
@@ -184,6 +188,7 @@ def __init__(self, collection, filter=None, projection=None, skip=0,
184188
self.__collation = validate_collation_or_none(collation)
185189
self.__return_key = return_key
186190
self.__show_record_id = show_record_id
191+
self.__allow_disk_use = allow_disk_use
187192
self.__snapshot = snapshot
188193
self.__set_hint(hint)
189194

@@ -426,6 +431,26 @@ def remove_option(self, mask):
426431
self.__query_flags &= ~mask
427432
return self
428433

434+
def allow_disk_use(self, allow_disk_use):
435+
"""Specifies whether MongoDB can use temporary disk files while
436+
processing a blocking sort operation.
437+
438+
Raises :exc:`TypeError` is `allow_disk_use` is not a boolean.
439+
440+
:Parameters:
441+
- `allow_disk_use`: if True, MongoDB may use temporary
442+
disk files to store data exceeding the system memory limit while
443+
processing a blocking sort operation.
444+
445+
.. versionadded:: 3.11
446+
"""
447+
if not isinstance(allow_disk_use, bool):
448+
raise TypeError('allow_disk_use must be a bool')
449+
self.__check_okay_to_chain()
450+
451+
self.__allow_disk_use = allow_disk_use
452+
return self
453+
429454
def limit(self, limit):
430455
"""Limits the number of results to be returned by this cursor.
431456
@@ -1069,7 +1094,8 @@ def _refresh(self):
10691094
self.__read_concern,
10701095
self.__collation,
10711096
self.__session,
1072-
self.__collection.database.client)
1097+
self.__collection.database.client,
1098+
self.__allow_disk_use)
10731099
self.__send_message(q)
10741100
elif self.__id: # Get More
10751101
if self.__limit:

pymongo/message.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ def _convert_write_result(operation, command, result):
182182

183183

184184
def _gen_find_command(coll, spec, projection, skip, limit, batch_size, options,
185-
read_concern, collation=None, session=None):
185+
read_concern, collation=None, session=None,
186+
allow_disk_use=None):
186187
"""Generate a find command document."""
187188
cmd = SON([('find', coll)])
188189
if '$query' in spec:
@@ -209,10 +210,13 @@ def _gen_find_command(coll, spec, projection, skip, limit, batch_size, options,
209210
cmd['readConcern'] = read_concern.document
210211
if collation:
211212
cmd['collation'] = collation
213+
if allow_disk_use is not None:
214+
cmd['allowDiskUse'] = allow_disk_use
212215
if options:
213216
cmd.update([(opt, True)
214217
for opt, val in _OPTIONS.items()
215218
if options & val])
219+
216220
return cmd
217221

218222

@@ -233,15 +237,16 @@ class _Query(object):
233237
__slots__ = ('flags', 'db', 'coll', 'ntoskip', 'spec',
234238
'fields', 'codec_options', 'read_preference', 'limit',
235239
'batch_size', 'name', 'read_concern', 'collation',
236-
'session', 'client', '_as_command')
240+
'session', 'client', 'allow_disk_use', '_as_command')
237241

238242
# For compatibility with the _GetMore class.
239243
exhaust_mgr = None
240244
cursor_id = None
241245

242246
def __init__(self, flags, db, coll, ntoskip, spec, fields,
243247
codec_options, read_preference, limit,
244-
batch_size, read_concern, collation, session, client):
248+
batch_size, read_concern, collation, session, client,
249+
allow_disk_use):
245250
self.flags = flags
246251
self.db = db
247252
self.coll = coll
@@ -256,6 +261,7 @@ def __init__(self, flags, db, coll, ntoskip, spec, fields,
256261
self.collation = collation
257262
self.session = session
258263
self.client = client
264+
self.allow_disk_use = allow_disk_use
259265
self.name = 'find'
260266
self._as_command = None
261267

@@ -279,6 +285,10 @@ def use_command(self, sock_info, exhaust):
279285
'Specifying a collation is unsupported with a max wire '
280286
'version of %d.' % (sock_info.max_wire_version,))
281287

288+
if sock_info.max_wire_version < 4 and self.allow_disk_use is not None:
289+
# Ignore allowDiskUse for MongoDB < 3.2.
290+
self.allow_disk_use = None
291+
282292
sock_info.validate_session(self.client, self.session)
283293

284294
return use_find_cmd
@@ -294,7 +304,7 @@ def as_command(self, sock_info):
294304
cmd = _gen_find_command(
295305
self.coll, self.spec, self.fields, self.ntoskip,
296306
self.limit, self.batch_size, self.flags, self.read_concern,
297-
self.collation, self.session)
307+
self.collation, self.session, self.allow_disk_use)
298308
if explain:
299309
self.name = 'explain'
300310
cmd = SON([('explain', cmd)])
@@ -1629,7 +1639,7 @@ def _first_batch(sock_info, db, coll, query, ntoreturn,
16291639
query = _Query(
16301640
0, db, coll, 0, query, None, codec_options,
16311641
read_preference, ntoreturn, 0, DEFAULT_READ_CONCERN, None, None,
1632-
None)
1642+
None, None)
16331643

16341644
name = next(iter(cmd))
16351645
publish = listeners.enabled_for_commands

test/crud/v2/find-allowdiskuse.json

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"runOn": [
3+
{
4+
"minServerVersion": "4.3.1"
5+
}
6+
],
7+
"collection_name": "test_find_allowdiskuse",
8+
"tests": [
9+
{
10+
"description": "Find does not send allowDiskuse when value is not specified",
11+
"operations": [
12+
{
13+
"object": "collection",
14+
"name": "find",
15+
"arguments": {
16+
"filter": {}
17+
}
18+
}
19+
],
20+
"expectations": [
21+
{
22+
"command_started_event": {
23+
"command": {
24+
"find": "test_find_allowdiskuse",
25+
"allowDiskUse": null
26+
}
27+
}
28+
}
29+
]
30+
},
31+
{
32+
"description": "Find sends allowDiskuse false when false is specified",
33+
"operations": [
34+
{
35+
"object": "collection",
36+
"name": "find",
37+
"arguments": {
38+
"filter": {},
39+
"allowDiskUse": false
40+
}
41+
}
42+
],
43+
"expectations": [
44+
{
45+
"command_started_event": {
46+
"command": {
47+
"find": "test_find_allowdiskuse",
48+
"allowDiskUse": false
49+
}
50+
}
51+
}
52+
]
53+
},
54+
{
55+
"description": "Find sends allowDiskUse true when true is specified",
56+
"operations": [
57+
{
58+
"object": "collection",
59+
"name": "find",
60+
"arguments": {
61+
"filter": {},
62+
"allowDiskUse": true
63+
}
64+
}
65+
],
66+
"expectations": [
67+
{
68+
"command_started_event": {
69+
"command": {
70+
"find": "test_find_allowdiskuse",
71+
"allowDiskUse": true
72+
}
73+
}
74+
}
75+
]
76+
}
77+
]
78+
}

test/test_cursor.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,18 @@ def test_add_remove_option_exhaust(self):
146146
self.assertEqual(0, cursor._Cursor__query_flags)
147147
self.assertFalse(cursor._Cursor__exhaust)
148148

149+
def test_allow_disk_use(self):
150+
db = self.db
151+
db.pymongo_test.drop()
152+
coll = db.pymongo_test
153+
154+
self.assertRaises(TypeError, coll.find().allow_disk_use, 'baz')
155+
156+
cursor = coll.find().allow_disk_use(True)
157+
self.assertEqual(True, cursor._Cursor__allow_disk_use)
158+
cursor = coll.find().allow_disk_use(False)
159+
self.assertEqual(False, cursor._Cursor__allow_disk_use)
160+
149161
def test_max_time_ms(self):
150162
db = self.db
151163
db.pymongo_test.drop()

0 commit comments

Comments
 (0)