From feaa06d96de54e15b41fb50221a222c63961aa0f Mon Sep 17 00:00:00 2001 From: "utkarsh.arya@zomato.com" Date: Sat, 15 Nov 2025 22:58:53 +0000 Subject: [PATCH] Fix composed queries keeping columns after multiple values()/values_list() calls Allow changing the list of columns in composed queries (union, intersection, difference) when values() or values_list() is called multiple times on the same queryset, ensuring correct results in repeated evaluations. Closes issue in compiler.py#L428-L433. Adds regression tests to cover this behavior. --- django/db/models/sql/compiler.py | 14 ++++++- tests/queries/test_qs_combinators.py | 58 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index a44adfc760..9163b9eaa4 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -424,8 +424,18 @@ class SQLCompiler: try: # If the columns list is limited, then all combined queries # must have the same columns list. Set the selects defined on - # the query on all combined queries, if not already set. - if not compiler.query.values_select and self.query.values_select: + # the query on all combined queries. Update combined queries + # if the values_select differs to allow values()/values_list() + # to be called multiple times on the combined query, but only + # if both queries use the same model. + if ( + self.query.values_select and + compiler.query.model == self.query.model and + ( + not compiler.query.values_select or + compiler.query.values_select != self.query.values_select + ) + ): compiler.query.set_values(( *self.query.extra_select, *self.query.values_select, diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 3902db59e2..7da4be2e72 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -124,6 +124,64 @@ class QuerySetSetOperationTests(TestCase): reserved_name = qs1.union(qs1).values_list('name', 'order', 'id').get() self.assertEqual(reserved_name[:2], ('a', 2)) + def test_union_multiple_values_list(self): + # Test that calling values_list() multiple times on the same + # composed query properly changes the columns. + ReservedName.objects.create(name='a', order=2) + qs1 = ReservedName.objects.all() + union = qs1.union(qs1) + # First call with two fields + result1 = union.values_list('name', 'order').get() + self.assertEqual(result1, ('a', 2)) + # Second call with different fields - should return only one field + result2 = union.values_list('order').get() + self.assertEqual(result2, (2,)) + # Third call with different fields + result3 = union.values_list('name').get() + self.assertEqual(result3, ('a',)) + + @skipUnlessDBFeature('supports_select_intersection') + def test_intersection_multiple_values_list(self): + # Test that calling values_list() multiple times on the same + # composed query properly changes the columns. + ReservedName.objects.create(name='b', order=3) + qs1 = ReservedName.objects.all() + intersection = qs1.intersection(qs1) + # First call with two fields + result1 = intersection.values_list('name', 'order').get() + self.assertEqual(result1, ('b', 3)) + # Second call with different fields - should return only one field + result2 = intersection.values_list('order').get() + self.assertEqual(result2, (3,)) + + @skipUnlessDBFeature('supports_select_difference') + def test_difference_multiple_values_list(self): + # Test that calling values_list() multiple times on the same + # composed query properly changes the columns. + ReservedName.objects.create(name='c', order=4) + qs1 = ReservedName.objects.all() + qs2 = ReservedName.objects.none() + difference = qs1.difference(qs2) + # First call with two fields + result1 = difference.values_list('name', 'order').get() + self.assertEqual(result1, ('c', 4)) + # Second call with different fields - should return only one field + result2 = difference.values_list('order').get() + self.assertEqual(result2, (4,)) + + def test_union_multiple_values(self): + # Test that calling values() multiple times on the same + # composed query properly changes the columns. + ReservedName.objects.create(name='d', order=5) + qs1 = ReservedName.objects.all() + union = qs1.union(qs1) + # First call with two fields + result1 = union.values('name', 'order').get() + self.assertEqual(result1, {'name': 'd', 'order': 5}) + # Second call with different fields - should return only one field + result2 = union.values('order').get() + self.assertEqual(result2, {'order': 5}) + def test_union_with_two_annotated_values_list(self): qs1 = Number.objects.filter(num=1).annotate( count=Value(0, IntegerField()),