mirror of
https://github.com/django/django.git
synced 2025-07-03 17:29:12 +00:00
[5.2.x] Fixed #36464 -- Fixed "__in" tuple lookup on backends lacking native support.
When native support for tuple lookups is missing in a DB backend, it can be emulated with an EXISTS clause. This is controlled by the backend feature flag "supports_tuple_lookups". The mishandling of subquery right-hand side in `TupleIn` (added to support `CompositePrimaryKey` in Refs #373) was likely missed because the only core backend we test with the feature flag disabled (Oracle < 23.4) supports it natively. Thanks to Nandana Raol for the report, and to Sarah Boyce, Jacob Walls, and Natalia Bidart for reviews. Backport of 192bc7a7be92e20cc250907fb4083df689715679 from main.
This commit is contained in:
parent
db5da3c91c
commit
a150160c9f
@ -1,9 +1,10 @@
|
||||
import itertools
|
||||
|
||||
from django.core.exceptions import EmptyResultSet
|
||||
from django.db.models import Field
|
||||
from django.db import models
|
||||
from django.db.models.expressions import (
|
||||
ColPairs,
|
||||
Exists,
|
||||
Func,
|
||||
ResolvedOuterRef,
|
||||
Subquery,
|
||||
@ -25,7 +26,7 @@ from django.db.models.sql.where import AND, OR, WhereNode
|
||||
class Tuple(Func):
|
||||
allows_composite_expressions = True
|
||||
function = ""
|
||||
output_field = Field()
|
||||
output_field = models.Field()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.source_expressions)
|
||||
@ -340,7 +341,21 @@ class TupleIn(TupleLookupMixin, In):
|
||||
rhs = self.rhs
|
||||
if not rhs:
|
||||
raise EmptyResultSet
|
||||
if not self.rhs_is_direct_value():
|
||||
if isinstance(rhs, Query):
|
||||
rhs_exprs = itertools.chain.from_iterable(
|
||||
(
|
||||
select_expr
|
||||
if isinstance((select_expr := select[0]), ColPairs)
|
||||
else [select_expr]
|
||||
)
|
||||
for select in rhs.get_compiler(connection=connection).get_select()[0]
|
||||
)
|
||||
rhs = rhs.clone()
|
||||
rhs.add_q(
|
||||
models.Q(*[Exact(col, val) for col, val in zip(self.lhs, rhs_exprs)])
|
||||
)
|
||||
return compiler.compile(Exists(rhs))
|
||||
elif not self.rhs_is_direct_value():
|
||||
return super(TupleLookupMixin, self).as_sql(compiler, connection)
|
||||
|
||||
# e.g.: (a, b, c) in [(x1, y1, z1), (x2, y2, z2)] as SQL:
|
||||
|
@ -16,3 +16,7 @@ Bugfixes
|
||||
* Fixed a regression in Django 5.2.3 where ``Value(None, JSONField())`` used in
|
||||
a :class:`~django.db.models.expressions.When` condition was incorrectly
|
||||
serialized as SQL ``NULL`` instead of JSON ``null`` (:ticket:`36453`).
|
||||
|
||||
* Fixed a crash in Django 5.2 when performing an ``__in`` lookup involving a
|
||||
composite primary key and a subquery on backends that lack native support for
|
||||
tuple lookups (:ticket:`36464`).
|
||||
|
@ -1,3 +1,6 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.db import connection
|
||||
from django.db.models import (
|
||||
Case,
|
||||
F,
|
||||
@ -246,6 +249,10 @@ class CompositePKFilterTests(TestCase):
|
||||
Comment.objects.filter(user=self.user_1).contains(self.comment_1), True
|
||||
)
|
||||
|
||||
def test_filter_query_does_not_mutate(self):
|
||||
queryset = User.objects.filter(comments__in=Comment.objects.all())
|
||||
self.assertEqual(str(queryset.query), str(queryset.query))
|
||||
|
||||
def test_filter_users_by_comments_in(self):
|
||||
c1, c2, c3, c4, c5 = (
|
||||
self.comment_1,
|
||||
@ -541,3 +548,12 @@ class CompositePKFilterTests(TestCase):
|
||||
).filter(filtered_tokens=(1, 1)),
|
||||
[self.tenant_1],
|
||||
)
|
||||
|
||||
|
||||
@skipUnlessDBFeature("supports_tuple_lookups")
|
||||
class CompositePKFilterTupleLookupFallbackTests(CompositePKFilterTests):
|
||||
def setUp(self):
|
||||
feature_patch = patch.object(
|
||||
connection.features, "supports_tuple_lookups", False
|
||||
)
|
||||
self.enterContext(feature_patch)
|
||||
|
Loading…
x
Reference in New Issue
Block a user