Preserve input iterable type in resolve_lookup_value

Fix regression where list inputs were coerced to tuples in Query.resolve_lookup_value, breaking exact value queries for fields sensitive to input type (e.g., PickledField). Adds tests to ensure input type is preserved. Refs #30687.
This commit is contained in:
utkarsh.arya@zomato.com 2025-11-15 22:58:23 +00:00
parent b93a0e34d9
commit e10a37076b
2 changed files with 43 additions and 2 deletions

View file

@ -1073,7 +1073,8 @@ class Query(BaseExpression):
))
else:
resolved_values.append(sub_value)
value = tuple(resolved_values)
# Preserve the type of the input iterable (list or tuple).
value = type(value)(resolved_values)
return value
def solve_lookup_type(self, lookup):

View file

@ -2,7 +2,7 @@ from datetime import datetime
from django.core.exceptions import FieldError
from django.db.models import CharField, F, Q
from django.db.models.expressions import SimpleCol
from django.db.models.expressions import Col, SimpleCol
from django.db.models.fields.related_lookups import RelatedIsNull
from django.db.models.functions import Lower
from django.db.models.lookups import Exact, GreaterThan, IsNull, LessThan
@ -113,3 +113,43 @@ class TestQuery(SimpleTestCase):
clone = query.clone()
clone.add_select_related(['note', 'creator__extra'])
self.assertEqual(query.select_related, {'creator': {}})
def test_resolve_lookup_value_preserves_list_type(self):
"""
resolve_lookup_value() should preserve the type of list/tuple inputs.
This is important for field lookups that depend on exact type matching
(e.g., PickledField).
Regression test for issue where resolve_lookup_value() was converting
all iterables to tuples, breaking exact value queries for fields that
depend on matching input types.
"""
query = Query(Author)
# Test that list input returns list output
list_value = [1, 2, 3]
resolved = query.resolve_lookup_value(list_value, can_reuse=None, allow_joins=True, simple_col=False)
self.assertIsInstance(resolved, list)
self.assertEqual(resolved, [1, 2, 3])
# Test that tuple input returns tuple output
tuple_value = (1, 2, 3)
resolved = query.resolve_lookup_value(tuple_value, can_reuse=None, allow_joins=True, simple_col=False)
self.assertIsInstance(resolved, tuple)
self.assertEqual(resolved, (1, 2, 3))
# Test with expressions in list
list_with_f = [F('num'), 2, 3]
resolved = query.resolve_lookup_value(list_with_f, can_reuse=None, allow_joins=True, simple_col=False)
self.assertIsInstance(resolved, list)
self.assertEqual(len(resolved), 3)
self.assertIsInstance(resolved[0], Col)
self.assertEqual(resolved[1], 2)
self.assertEqual(resolved[2], 3)
# Test with expressions in tuple
tuple_with_f = (F('num'), 2, 3)
resolved = query.resolve_lookup_value(tuple_with_f, can_reuse=None, allow_joins=True, simple_col=False)
self.assertIsInstance(resolved, tuple)
self.assertEqual(len(resolved), 3)
self.assertIsInstance(resolved[0], Col)
self.assertEqual(resolved[1], 2)
self.assertEqual(resolved[2], 3)