#37291 new Bug

Passing F("pk") to a tuple lookup can hang

Reported by: Jacob Walls Owned by:
Component: Database layer (models, ORM) Version: 5.2
Severity: Release blocker Keywords: compositeprimarykey
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

This test provides F("pk") directly to the TupleIn lookup. It hangs due to a sanity check in the composite PK logic:

diff --git a/tests/foreign_object/test_tuple_lookups.py b/tests/foreign_object/test_tuple_lookups.py
index 008f118994..fb1965fa6a 100644
--- a/tests/foreign_object/test_tuple_lookups.py
+++ b/tests/foreign_object/test_tuple_lookups.py
@@ -168,6 +168,12 @@ class TupleLookupsTests(TestCase):
             with self.subTest(customer=customer.id, query=str(qs.query)):
                 self.assertSequenceEqual(qs, contacts)
 
+    def test_tuple_in_subquery_f(self):
+        self.assertCountEqual(
+            Contact.objects.filter(TupleIn(F("pk"), Contact.objects.values("pk"))),
+            Contact.objects.all(),
+        )
+
     def test_tuple_in_rhs_must_be_collection_of_tuples_or_lists(self):
         test_cases = (
             (1, 2, 3),

That sanity check iterates over the left-hand side expression, which is apparently not safe if the left-hand side is an F object, as when __iter__() missing, Python falls back to __getitem__(), and F.__getitem__() can perpetually iterate an index:

  /Users/jwalls/django/tests/foreign_object/test_tuple_lookups.py(173)test_tuple_in_subquery_f()
-> Contact.objects.filter(TupleIn(F("pk"), Contact.objects.values("pk"))),
  /Users/jwalls/django/django/db/models/lookups.py(35)__init__()
-> self.rhs = self.get_prep_lookup()
  /Users/jwalls/django/django/db/models/fields/tuple_lookups.py(302)get_prep_lookup()
-> self.check_rhs_is_tuple_or_list()
  /Users/jwalls/django/django/db/models/fields/tuple_lookups.py(63)check_rhs_is_tuple_or_list()
-> lhs_str = self.get_lhs_str()
  /Users/jwalls/django/django/db/models/fields/tuple_lookups.py(89)get_lhs_str()
-> names = ", ".join(repr(f.name) for f in self.lhs)
  /Users/jwalls/django/django/db/models/fields/tuple_lookups.py(89)<genexpr>()->"'pk'"
-> names = ", ".join(repr(f.name) for f in self.lhs)
> /Users/jwalls/django/django/db/models/expressions.py(898)__getitem__()

F.__getitem__():

    def __getitem__(self, subscript):
        return Sliced(self, subscript)

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top