summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2025-04-02 13:32:38 -0400
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2025-04-03 22:20:50 +0200
commit8ebdd37a0b1755842baae3bd34d388156ad4bf53 (patch)
tree3433e0ea9965d65905b7353b6783657add9e9185
parent317690403a40fbaf52c6abcbc8d39f199c9b5102 (diff)
[5.2.x] Fixed #36290 -- Made TupleIn() lookup discard tuples containing None.
Just like the In() lookup discards of None members TupleIn() should discard tuples containing any None as NULL != NULL in SQL and the framework expects such queries to be elided under some circumstances. Refs #31667, #36116. Thanks Basptise Mispelon for bisecting the regression to 626d77e. Backport of f7f38f3a0b44d8c6d14344dae66b6ce52cd77b55 from main
-rw-r--r--django/db/models/fields/tuple_lookups.py13
-rw-r--r--docs/releases/5.2.1.txt3
-rw-r--r--tests/composite_pk/test_filter.py7
-rw-r--r--tests/foreign_object/models/person.py2
-rw-r--r--tests/foreign_object/tests.py13
5 files changed, 37 insertions, 1 deletions
diff --git a/django/db/models/fields/tuple_lookups.py b/django/db/models/fields/tuple_lookups.py
index 6c21263b6a..4523a19a98 100644
--- a/django/db/models/fields/tuple_lookups.py
+++ b/django/db/models/fields/tuple_lookups.py
@@ -318,6 +318,10 @@ class TupleIn(TupleLookupMixin, In):
lhs = self.lhs
for vals in rhs:
+ # Remove any tuple containing None from the list as NULL is never
+ # equal to anything.
+ if any(val is None for val in vals):
+ continue
result.append(
Tuple(
*[
@@ -327,6 +331,9 @@ class TupleIn(TupleLookupMixin, In):
)
)
+ if not result:
+ raise EmptyResultSet
+
return compiler.compile(Tuple(*result))
def get_fallback_sql(self, compiler, connection):
@@ -342,9 +349,15 @@ class TupleIn(TupleLookupMixin, In):
lhs = self.lhs
for vals in rhs:
+ # Remove any tuple containing None from the list as NULL is never
+ # equal to anything.
+ if any(val is None for val in vals):
+ continue
lookups = [Exact(col, val) for col, val in zip(lhs, vals)]
root.children.append(WhereNode(lookups, connector=AND))
+ if not root.children:
+ raise EmptyResultSet
return root.as_sql(compiler, connection)
diff --git a/docs/releases/5.2.1.txt b/docs/releases/5.2.1.txt
index a4e73841bd..b17e63ebd1 100644
--- a/docs/releases/5.2.1.txt
+++ b/docs/releases/5.2.1.txt
@@ -12,3 +12,6 @@ Bugfixes
* Fixed a regression in Django 5.2 that caused a crash when annotating
aggregate expressions over query that uses explicit grouping by transforms
followed by field references (:ticket:`36292`).
+
+* Fixed a regression in Django 5.2 that caused unnecessary queries when
+ prefetching nullable foreign key relationships (:ticket:`36290`).
diff --git a/tests/composite_pk/test_filter.py b/tests/composite_pk/test_filter.py
index c633348cc1..39f19daecc 100644
--- a/tests/composite_pk/test_filter.py
+++ b/tests/composite_pk/test_filter.py
@@ -206,6 +206,13 @@ class CompositePKFilterTests(TestCase):
[self.comment_1],
)
+ def test_filter_by_pk_in_none(self):
+ with self.assertNumQueries(0):
+ self.assertSequenceEqual(
+ Comment.objects.filter(pk__in=[(None, 1), (1, None)]),
+ [],
+ )
+
def test_filter_comments_by_user_and_order_by_pk_asc(self):
self.assertSequenceEqual(
Comment.objects.filter(user=self.user_1).order_by("pk"),
diff --git a/tests/foreign_object/models/person.py b/tests/foreign_object/models/person.py
index d536ab63d7..4290ad6d3b 100644
--- a/tests/foreign_object/models/person.py
+++ b/tests/foreign_object/models/person.py
@@ -84,7 +84,7 @@ class Friendship(models.Model):
)
from_friend_id = models.IntegerField()
to_friend_country_id = models.IntegerField()
- to_friend_id = models.IntegerField()
+ to_friend_id = models.IntegerField(null=True)
# Relation Fields
from_friend = models.ForeignObject(
diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py
index 8b36df29d7..0bba0eaa98 100644
--- a/tests/foreign_object/tests.py
+++ b/tests/foreign_object/tests.py
@@ -297,6 +297,19 @@ class MultiColumnFKTests(TestCase):
self.assertEqual(friendships[0].to_friend, self.george)
self.assertEqual(friendships[1].to_friend, self.sam)
+ def test_prefetch_foreignobject_null_hidden_forward_skipped(self):
+ fiendship = Friendship.objects.create(
+ from_friend_country=self.usa,
+ from_friend_id=self.bob.id,
+ to_friend_country_id=self.usa.id,
+ to_friend_id=None,
+ )
+ with self.assertNumQueries(1):
+ self.assertEqual(
+ Friendship.objects.prefetch_related("to_friend").get(),
+ fiendship,
+ )
+
def test_prefetch_foreignobject_reverse(self):
Membership.objects.create(
membership_country=self.usa, person=self.bob, group=self.cia