﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
36116	prefetch_related() selects too many objects from a related table with a composite primary key and hidden related name	Jacob Walls	Simon Charette	"Noticed a ""fixme"" [https://github.com/django/django/blob/c28f821c9067050ba0d099349a4dfea2b29faf99/django/db/models/fields/related_descriptors.py#L170-L180 comment] in the code that pointed me here. With a hidden related name, e.g. `comments+`, we hit a branch that expects primary keys to consist of one column only. When a composite primary key violates that assumption, more objects than necessary will be fetched.

With this test:

{{{#!diff
diff --git a/tests/composite_pk/models/__init__.py b/tests/composite_pk/models/__init__.py
index 5996ae33b0..40750d91ae 100644
--- a/tests/composite_pk/models/__init__.py
+++ b/tests/composite_pk/models/__init__.py
@@ -1,7 +1,16 @@
-from .tenant import Comment, Post, Tenant, TimeStamped, Token, User
+from .tenant import (
+    Comment,
+    CommentWithHiddenUserField,
+    Post,
+    Tenant,
+    TimeStamped,
+    Token,
+    User,
+)
 
 __all__ = [
     ""Comment"",
+    ""CommentWithHiddenUserField"",
     ""Post"",
     ""Tenant"",
     ""TimeStamped"",
diff --git a/tests/composite_pk/models/tenant.py b/tests/composite_pk/models/tenant.py
index 6286ed2354..66fb371d3a 100644
--- a/tests/composite_pk/models/tenant.py
+++ b/tests/composite_pk/models/tenant.py
@@ -46,6 +46,25 @@ class Comment(models.Model):
     text = models.TextField(default="""", blank=True)
 
 
+class CommentWithHiddenUserField(models.Model):
+    pk = models.CompositePrimaryKey(""tenant"", ""id"")
+    tenant = models.ForeignKey(
+        Tenant,
+        on_delete=models.CASCADE,
+        related_name=""comments+"",
+    )
+    id = models.SmallIntegerField(unique=True, db_column=""comment_id"")
+    user_id = models.SmallIntegerField()
+    user = models.ForeignObject(
+        User,
+        on_delete=models.CASCADE,
+        from_fields=(""tenant_id"", ""user_id""),
+        to_fields=(""tenant_id"", ""id""),
+        related_name=""comments+"",
+    )
+    text = models.TextField(default="""", blank=True)
+
+
 class Post(models.Model):
     pk = models.CompositePrimaryKey(""tenant_id"", ""id"")
     tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, default=1)
diff --git a/tests/composite_pk/tests.py b/tests/composite_pk/tests.py
index 6b09480fb0..29da1be47b 100644
--- a/tests/composite_pk/tests.py
+++ b/tests/composite_pk/tests.py
@@ -16,8 +16,9 @@ from django.db import IntegrityError, connection
 from django.db.models import CompositePrimaryKey
 from django.forms import modelform_factory
 from django.test import TestCase
+from django.test.utils import CaptureQueriesContext
 
-from .models import Comment, Post, Tenant, TimeStamped, User
+from .models import Comment, CommentWithHiddenUserField, Post, Tenant, TimeStamped, User
 
 
 class CommentForm(forms.ModelForm):
@@ -38,6 +39,9 @@ class CompositePKTests(TestCase):
             email=""user0001@example.com"",
         )
         cls.comment = Comment.objects.create(tenant=cls.tenant, id=1, user=cls.user)
+        cls.comment_by_hidden_user = CommentWithHiddenUserField.objects.create(
+            tenant=cls.tenant, id=1, user=cls.user
+        )
 
     @staticmethod
     def get_primary_key_columns(table):
@@ -157,6 +161,13 @@ class CompositePKTests(TestCase):
         result = list(Comment.objects.iterator())
         self.assertEqual(result, [self.comment])
 
+    def test_prefetch_related_hidden_related_name(self):
+        with CaptureQueriesContext(connection) as queries:
+            CommentWithHiddenUserField.objects.prefetch_related(""user"").get(
+                pk=self.comment_by_hidden_user.pk
+            )
+        self.assertIn('""comment_id"" = 1', queries[1][""sql""])
+
     def test_query(self):
         users = User.objects.values_list(""pk"").order_by(""pk"")
         self.assertNotIn('AS ""pk""', str(users.query))
}}}

The `WHERE` is too permissive -- would expect both primary key columns to be checked, i.e. also ""comment_id"".

{{{#!py
======================================================================
FAIL: test_prefetch_related_hidden_related_name (composite_pk.tests.CompositePKTests.test_prefetch_related_hidden_related_name)
----------------------------------------------------------------------
Traceback (most recent call last):
  File ""/Users/source/django/tests/composite_pk/tests.py"", line 169, in test_prefetch_related_hidden_related_name
    self.assertIn('""comment_id"" = 1', queries[1][""sql""])
AssertionError: '""comment_id"" = 1' not found in 'SELECT ""composite_pk_user"".""tenant_id"", ""composite_pk_user"".""id"", ""composite_pk_user"".""email"" FROM ""composite_pk_user"" WHERE ""composite_pk_user"".""tenant_id"" IN (1)'

----------------------------------------------------------------------
Ran 1 test in 0.006s

FAILED (failures=1)
}}}"	Cleanup/optimization	closed	Database layer (models, ORM)	5.2	Release blocker	fixed		Simon Charette	Ready for checkin	1	0	0	0	0	0
