diff --git a/doc/changelog.rst b/doc/changelog.rst index 096f2f267..f6e01f3ef 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -28,6 +28,8 @@ PyMongo 4.6 brings a number of improvements including: - :meth:`~pymongo.uri_parser.parse_uri` now considers the delimiting slash (``/``) between hosts and connection options optional. For example, "mongodb://example.com?tls=true" is now a valid URI. +- Fixed a bug where PyMongo would incorrectly promote all cursors to exhaust cursors + when connected to load balanced MongoDB clusters or Serverless clusters. Changes in Version 4.5 ---------------------- diff --git a/pymongo/message.py b/pymongo/message.py index 468574550..c04f4a887 100644 --- a/pymongo/message.py +++ b/pymongo/message.py @@ -565,7 +565,7 @@ class _GetMore: if use_cmd: spec = self.as_command(conn, apply_timeout=True)[0] - if self.conn_mgr: + if self.conn_mgr and self.exhaust: flags = _OpMsg.EXHAUST_ALLOWED else: flags = 0 diff --git a/test/mockupdb/test_cursor.py b/test/mockupdb/test_cursor.py new file mode 100644 index 000000000..1cf3a05ed --- /dev/null +++ b/test/mockupdb/test_cursor.py @@ -0,0 +1,61 @@ +# Copyright 2023-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test PyMongo cursor does not set exhaustAllowed automatically (PYTHON-4007).""" +from __future__ import annotations + +import unittest + +from mockupdb import MockupDB, OpMsg, going + +from bson.objectid import ObjectId +from pymongo import MongoClient + + +class TestCursor(unittest.TestCase): + def test_getmore_load_balanced(self): + server = MockupDB() + server.autoresponds( + "hello", + isWritablePrimary=True, + msg="isdbgrid", + minWireVersion=0, + maxWireVersion=20, + helloOk=True, + serviceId=ObjectId(), + ) + server.run() + self.addCleanup(server.stop) + + client = MongoClient(server.uri, loadBalanced=True) + self.addCleanup(client.close) + collection = client.db.coll + cursor = collection.find() + with going(next, cursor): + request = server.receives(OpMsg({"find": "coll"})) + self.assertEqual(request.flags, 0, "exhaustAllowed should not be set") + # Respond with a different namespace. + request.reply({"cursor": {"id": 123, "firstBatch": [{}]}}) + + # 3 batches, check exhaustAllowed on all getMores. + for i in range(1, 3): + with going(next, cursor): + request = server.receives(OpMsg({"getMore": 123})) + self.assertEqual(request.flags, 0, "exhaustAllowed should not be set") + cursor_id = 123 if i < 2 else 0 + request.replies({"cursor": {"id": cursor_id, "nextBatch": [{}]}}) + + +if __name__ == "__main__": + unittest.main()