From 53ea4cce2fd08e84b9cdb6363267ccb9525f7060 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Mon, 14 Oct 2024 19:21:48 -0400 Subject: [PATCH] Fixed #35744 -- Relabelled external aliases of combined queries. Just like normal queries, combined queries' outer references might fully resolve before their reference is assigned its final alias. Refs #29338. Thanks Antony_K for the report and example, and thanks Mariusz Felisiak for the review. --- django/db/models/sql/query.py | 10 +++++++++ tests/queries/test_qs_combinators.py | 32 +++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index aef3f48f10..b7b93c235a 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1021,11 +1021,21 @@ class Query(BaseExpression): if alias == old_alias: table_aliases[pos] = new_alias break + + # 3. Rename the direct external aliases and the ones of combined + # queries (union, intersection, difference). self.external_aliases = { # Table is aliased or it's being changed and thus is aliased. change_map.get(alias, alias): (aliased or alias in change_map) for alias, aliased in self.external_aliases.items() } + for combined_query in self.combined_queries: + external_change_map = { + alias: aliased + for alias, aliased in change_map.items() + if alias in combined_query.external_aliases + } + combined_query.change_aliases(external_change_map) def bump_prefix(self, other_query, exclude=None): """ diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index ad1017c8af..2f6e93cde8 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -14,7 +14,16 @@ from django.db.models.functions import Mod from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import CaptureQueriesContext -from .models import Author, Celebrity, ExtraInfo, Number, ReservedName +from .models import ( + Annotation, + Author, + Celebrity, + ExtraInfo, + Note, + Number, + ReservedName, + Tag, +) @skipUnlessDBFeature("supports_select_union") @@ -450,6 +459,27 @@ class QuerySetSetOperationTests(TestCase): [8, 1], ) + @skipUnlessDBFeature("supports_select_intersection") + def test_intersection_in_nested_subquery(self): + tag = Tag.objects.create(name="tag") + note = Note.objects.create(tag=tag) + annotation = Annotation.objects.create(tag=tag) + tags = Tag.objects.order_by() + tags = tags.filter(id=OuterRef("tag_id")).intersection( + tags.filter(id=OuterRef(OuterRef("tag_id"))) + ) + qs = Note.objects.filter( + Exists( + Annotation.objects.filter( + Exists(tags), + notes__in=OuterRef("pk"), + ) + ) + ) + self.assertIsNone(qs.first()) + annotation.notes.add(note) + self.assertEqual(qs.first(), note) + def test_union_in_subquery_related_outerref(self): e1 = ExtraInfo.objects.create(value=7, info="e3") e2 = ExtraInfo.objects.create(value=5, info="e2")