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.
This commit is contained in:
utkarsh.arya@zomato.com 2025-11-15 22:58:53 +00:00
parent a7038adbd0
commit feaa06d96d
2 changed files with 70 additions and 2 deletions

View file

@ -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,

View file

@ -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()),