Skip to content

Commit d9e5666

Browse files
authored
PYTHON-2875 Require hint with min/max queries (#712)
1 parent 88e86f6 commit d9e5666

File tree

5 files changed

+24
-29
lines changed

5 files changed

+24
-29
lines changed

doc/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ Breaking Changes in 4.0
100100
- Removed :class:`bson.binary.UUIDLegacy`.
101101
- The "tls" install extra is no longer necessary or supported and will be
102102
ignored by pip.
103+
- The ``hint`` option is now required when using ``min`` or ``max`` queries
104+
with :meth:`~pymongo.collection.Collection.find`.
103105
- ``name`` is now a required argument for the :class:`pymongo.driver_info.DriverInfo` class.
104106

105107
Notable improvements

doc/migrate-to-pymongo4.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,19 @@ can be changed to this::
591591
show_record_id=False,
592592
)
593593

594+
The hint parameter is required with min/max
595+
...........................................
596+
597+
The ``hint`` option is now required when using ``min`` or ``max`` queries
598+
with :meth:`~pymongo.collection.Collection.find` to ensure the query utilizes
599+
the correct index. For example, code like this::
600+
601+
cursor = coll.find({}, min={'x', min_value})
602+
603+
can be changed to this::
604+
605+
cursor = coll.find({}, min={'x', min_value}, hint=[('x', ASCENDING)])
606+
594607
SONManipulator is removed
595608
-------------------------
596609

pymongo/cursor.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,11 +1060,9 @@ def _refresh(self):
10601060

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

test/test_collection.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1932,9 +1932,8 @@ def test_min_query(self):
19321932
self.db.test.insert_many([{"x": 1}, {"x": 2}])
19331933
self.db.test.create_index("x")
19341934

1935-
cursor = self.db.test.find({"$min": {"x": 2}, "$query": {}})
1936-
if client_context.requires_hint_with_min_max_queries:
1937-
cursor = cursor.hint("x_1")
1935+
cursor = self.db.test.find({"$min": {"x": 2}, "$query": {}},
1936+
hint="x_1")
19381937

19391938
docs = list(cursor)
19401939
self.assertEqual(1, len(docs))

test/test_cursor.py

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import sys
2222
import time
2323
import threading
24-
import warnings
2524

2625
sys.path[0:0] = [""]
2726

@@ -450,7 +449,6 @@ def test_limit(self):
450449
break
451450
self.assertRaises(InvalidOperation, a.limit, 5)
452451

453-
@ignore_deprecations # Ignore max without hint.
454452
def test_max(self):
455453
db = self.db
456454
db.test.drop()
@@ -460,10 +458,7 @@ def test_max(self):
460458
db.test.insert_many([{"j": j, "k": j} for j in range(10)])
461459

462460
def find(max_spec, expected_index):
463-
cursor = db.test.find().max(max_spec)
464-
if client_context.requires_hint_with_min_max_queries:
465-
cursor = cursor.hint(expected_index)
466-
return cursor
461+
return db.test.find().max(max_spec).hint(expected_index)
467462

468463
cursor = find([("j", 3)], j_index)
469464
self.assertEqual(len(list(cursor)), 3)
@@ -489,7 +484,6 @@ def find(max_spec, expected_index):
489484
self.assertRaises(TypeError, db.test.find().max, 10)
490485
self.assertRaises(TypeError, db.test.find().max, {"j": 10})
491486

492-
@ignore_deprecations # Ignore min without hint.
493487
def test_min(self):
494488
db = self.db
495489
db.test.drop()
@@ -499,10 +493,7 @@ def test_min(self):
499493
db.test.insert_many([{"j": j, "k": j} for j in range(10)])
500494

501495
def find(min_spec, expected_index):
502-
cursor = db.test.find().min(min_spec)
503-
if client_context.requires_hint_with_min_max_queries:
504-
cursor = cursor.hint(expected_index)
505-
return cursor
496+
return db.test.find().min(min_spec).hint(expected_index)
506497

507498
cursor = find([("j", 3)], j_index)
508499
self.assertEqual(len(list(cursor)), 7)
@@ -528,23 +519,15 @@ def find(min_spec, expected_index):
528519
self.assertRaises(TypeError, db.test.find().min, 10)
529520
self.assertRaises(TypeError, db.test.find().min, {"j": 10})
530521

531-
@client_context.require_version_max(4, 1, -1)
532522
def test_min_max_without_hint(self):
533523
coll = self.db.test
534524
j_index = [("j", ASCENDING)]
535525
coll.create_index(j_index)
536526

537-
with warnings.catch_warnings(record=True) as warns:
538-
warnings.simplefilter("default", DeprecationWarning)
539-
list(coll.find().min([("j", 3)]))
540-
self.assertIn('using a min/max query operator', str(warns[0]))
541-
# Ensure the warning is raised with the proper stack level.
542-
del warns[:]
527+
with self.assertRaises(InvalidOperation):
543528
list(coll.find().min([("j", 3)]))
544-
self.assertIn('using a min/max query operator', str(warns[0]))
545-
del warns[:]
529+
with self.assertRaises(InvalidOperation):
546530
list(coll.find().max([("j", 3)]))
547-
self.assertIn('using a min/max query operator', str(warns[0]))
548531

549532
def test_batch_size(self):
550533
db = self.db

0 commit comments

Comments
 (0)