0
0
mirror of https://github.com/django/django.git synced 2024-11-24 20:07:01 +01:00

Demonstrate how implicit ordering of extra(select) can be preserved.

This commit is contained in:
Simon Charette 2024-09-02 23:51:49 -04:00
parent 9a83c66561
commit ed6788bbd7
No known key found for this signature in database
4 changed files with 53 additions and 9 deletions

View File

@ -210,9 +210,19 @@ class ValuesIterable(BaseIterable):
if query.selected: if query.selected:
names = list(query.selected) names = list(query.selected)
else: else:
# RemovedInDjango60Warning: place extra(select) entries at the
# beginning of the SELECT clause until it's completely removed.
extra_names = []
annotation_select = []
for alias, expression in query.annotation_select.items():
if isinstance(expression, _ExtraSelectFirstRawSQL):
extra_names.append(alias)
else:
annotation_select.append(alias)
names = [ names = [
*extra_names,
*query.values_select, *query.values_select,
*query.annotation_select, *annotation_select,
] ]
indexes = range(len(names)) indexes = range(len(names))
for row in compiler.results_iter( for row in compiler.results_iter(
@ -250,9 +260,19 @@ class NamedValuesListIterable(ValuesListIterable):
names = queryset._fields names = queryset._fields
else: else:
query = queryset.query query = queryset.query
# RemovedInDjango60Warning: place extra(select) entries at the
# beginning of the SELECT clause until it's completely removed.
extra_names = []
annotation_select = []
for alias, expression in query.annotation_select.items():
if isinstance(expression, _ExtraSelectFirstRawSQL):
extra_names.append(alias)
else:
annotation_select.append(alias)
names = [ names = [
*extra_names,
*query.values_select, *query.values_select,
*query.annotation_select, *annotation_select,
] ]
tuple_class = create_namedtuple_class(*names) tuple_class = create_namedtuple_class(*names)
new = tuple.__new__ new = tuple.__new__
@ -275,6 +295,17 @@ class FlatValuesListIterable(BaseIterable):
yield row[0] yield row[0]
# RemovedInDjango60Warning
class _ExtraSelectFirstRawSQL(RawSQL):
"""
Internal RawSQL subclass used during the deprecation of extra(select) to
preserve the undocumented implicit ordering of members at the begining of
the SELECT clause.
"""
implicitly_select_first = True
class QuerySet(AltersData): class QuerySet(AltersData):
"""Represent a lazy database lookup for a set of objects.""" """Represent a lazy database lookup for a set of objects."""
@ -1724,8 +1755,6 @@ class QuerySet(AltersData):
category=RemovedInDjango60Warning, category=RemovedInDjango60Warning,
stacklevel=2, stacklevel=2,
) )
# XXX: This is mising the logic to always place extras at the begining
# of the select clause.
if select_params: if select_params:
param_iter = iter(select_params) param_iter = iter(select_params)
else: else:
@ -1739,7 +1768,9 @@ class QuerySet(AltersData):
if pos == 0 or entry[pos - 1] != "%": if pos == 0 or entry[pos - 1] != "%":
entry_params.append(next(param_iter)) entry_params.append(next(param_iter))
pos = entry.find("%s", pos + 2) pos = entry.find("%s", pos + 2)
clone.query.add_annotation(RawSQL(entry, entry_params), name) clone.query.add_annotation(
_ExtraSelectFirstRawSQL(entry, entry_params), name
)
if where: if where:
warnings.warn( warnings.warn(
"extra(where) usage is deprecated, use filter() with RawSQL instead.", "extra(where) usage is deprecated, use filter() with RawSQL instead.",

View File

@ -262,9 +262,22 @@ class SQLCompiler:
} }
selected = [] selected = []
if self.query.selected is None: if self.query.selected is None:
# RemovedInDjango60Warning: place extra(select) entries at the
# beginning of the SELECT clause until it's completely removed.
leading_annotation_select = []
annotation_select = []
if self.query.values_select_all:
for alias, expression in self.query.annotation_select.items():
if getattr(expression, "implicitly_select_first", False):
leading_annotation_select.append((alias, expression))
else:
annotation_select.append((alias, expression))
else:
annotation_select = self.query.annotation_select.items()
selected = [ selected = [
*leading_annotation_select,
*((None, col) for col in cols), *((None, col) for col in cols),
*self.query.annotation_select.items(), *annotation_select,
] ]
else: else:
for alias, expression in self.query.selected.items(): for alias, expression in self.query.selected.items():

View File

@ -286,7 +286,7 @@ class ExtraRegressTests(TestCase):
select={"foo": "first", "bar": "second", "whiz": "third"} select={"foo": "first", "bar": "second", "whiz": "third"}
).values_list() ).values_list()
), ),
[(obj.pk, "first", "second", "third", "first", "second", "third")], [("first", "second", "third", obj.pk, "first", "second", "third")],
) )
# Extra columns after an empty values_list() are still included # Extra columns after an empty values_list() are still included
@ -296,7 +296,7 @@ class ExtraRegressTests(TestCase):
select={"foo": "first", "bar": "second", "whiz": "third"} select={"foo": "first", "bar": "second", "whiz": "third"}
) )
), ),
[(obj.pk, "first", "second", "third", "first", "second", "third")], [("first", "second", "third", obj.pk, "first", "second", "third")],
) )
# Extra columns ignored completely if not mentioned in values_list() # Extra columns ignored completely if not mentioned in values_list()

View File

@ -2841,7 +2841,7 @@ class ValuesQuerysetTests(TestCase):
self.assertEqual(type(values).__name__, "Row") self.assertEqual(type(values).__name__, "Row")
self.assertEqual( self.assertEqual(
values._fields, values._fields,
("id", "num", "other_num", "another_num", "num2", "id__count"), ("num2", "id", "num", "other_num", "another_num", "id__count"),
) )
self.assertEqual(values.num, 72) self.assertEqual(values.num, 72)
self.assertEqual(values.num2, 73) self.assertEqual(values.num2, 73)