PYTHON-2875 Require hint with min/max queries (#712)

This commit is contained in:
Shane Harvey 2021-09-08 11:32:14 -07:00 committed by GitHub
parent 88e86f6f5a
commit d9e5666336
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 24 additions and 29 deletions

View File

@ -100,6 +100,8 @@ Breaking Changes in 4.0
- Removed :class:`bson.binary.UUIDLegacy`.
- The "tls" install extra is no longer necessary or supported and will be
ignored by pip.
- The ``hint`` option is now required when using ``min`` or ``max`` queries
with :meth:`~pymongo.collection.Collection.find`.
- ``name`` is now a required argument for the :class:`pymongo.driver_info.DriverInfo` class.
Notable improvements

View File

@ -591,6 +591,19 @@ can be changed to this::
show_record_id=False,
)
The hint parameter is required with min/max
...........................................
The ``hint`` option is now required when using ``min`` or ``max`` queries
with :meth:`~pymongo.collection.Collection.find` to ensure the query utilizes
the correct index. For example, code like this::
cursor = coll.find({}, min={'x', min_value})
can be changed to this::
cursor = coll.find({}, min={'x', min_value}, hint=[('x', ASCENDING)])
SONManipulator is removed
-------------------------

View File

@ -1060,11 +1060,9 @@ class Cursor(object):
if self.__id is None: # Query
if (self.__min or self.__max) and not self.__hint:
warnings.warn("using a min/max query operator without "
"specifying a Cursor.hint is deprecated. A "
"hint will be required when using min/max in "
"PyMongo 4.0",
DeprecationWarning, stacklevel=3)
raise InvalidOperation(
"Passing a 'hint' is required when using the min/max query"
" option to ensure the query utilizes the correct index")
q = self._query_class(self.__query_flags,
self.__collection.database.name,
self.__collection.name,

View File

@ -1932,9 +1932,8 @@ class TestCollection(IntegrationTest):
self.db.test.insert_many([{"x": 1}, {"x": 2}])
self.db.test.create_index("x")
cursor = self.db.test.find({"$min": {"x": 2}, "$query": {}})
if client_context.requires_hint_with_min_max_queries:
cursor = cursor.hint("x_1")
cursor = self.db.test.find({"$min": {"x": 2}, "$query": {}},
hint="x_1")
docs = list(cursor)
self.assertEqual(1, len(docs))

View File

@ -21,7 +21,6 @@ import re
import sys
import time
import threading
import warnings
sys.path[0:0] = [""]
@ -450,7 +449,6 @@ class TestCursor(IntegrationTest):
break
self.assertRaises(InvalidOperation, a.limit, 5)
@ignore_deprecations # Ignore max without hint.
def test_max(self):
db = self.db
db.test.drop()
@ -460,10 +458,7 @@ class TestCursor(IntegrationTest):
db.test.insert_many([{"j": j, "k": j} for j in range(10)])
def find(max_spec, expected_index):
cursor = db.test.find().max(max_spec)
if client_context.requires_hint_with_min_max_queries:
cursor = cursor.hint(expected_index)
return cursor
return db.test.find().max(max_spec).hint(expected_index)
cursor = find([("j", 3)], j_index)
self.assertEqual(len(list(cursor)), 3)
@ -489,7 +484,6 @@ class TestCursor(IntegrationTest):
self.assertRaises(TypeError, db.test.find().max, 10)
self.assertRaises(TypeError, db.test.find().max, {"j": 10})
@ignore_deprecations # Ignore min without hint.
def test_min(self):
db = self.db
db.test.drop()
@ -499,10 +493,7 @@ class TestCursor(IntegrationTest):
db.test.insert_many([{"j": j, "k": j} for j in range(10)])
def find(min_spec, expected_index):
cursor = db.test.find().min(min_spec)
if client_context.requires_hint_with_min_max_queries:
cursor = cursor.hint(expected_index)
return cursor
return db.test.find().min(min_spec).hint(expected_index)
cursor = find([("j", 3)], j_index)
self.assertEqual(len(list(cursor)), 7)
@ -528,23 +519,15 @@ class TestCursor(IntegrationTest):
self.assertRaises(TypeError, db.test.find().min, 10)
self.assertRaises(TypeError, db.test.find().min, {"j": 10})
@client_context.require_version_max(4, 1, -1)
def test_min_max_without_hint(self):
coll = self.db.test
j_index = [("j", ASCENDING)]
coll.create_index(j_index)
with warnings.catch_warnings(record=True) as warns:
warnings.simplefilter("default", DeprecationWarning)
with self.assertRaises(InvalidOperation):
list(coll.find().min([("j", 3)]))
self.assertIn('using a min/max query operator', str(warns[0]))
# Ensure the warning is raised with the proper stack level.
del warns[:]
list(coll.find().min([("j", 3)]))
self.assertIn('using a min/max query operator', str(warns[0]))
del warns[:]
with self.assertRaises(InvalidOperation):
list(coll.find().max([("j", 3)]))
self.assertIn('using a min/max query operator', str(warns[0]))
def test_batch_size(self):
db = self.db