Fixed #31496 -- Fixed QuerySet.values()/values_list() crash on combined querysets ordered by annotations.
This commit is contained in:
parent
300def5c34
commit
464a4c0c59
|
@ -322,6 +322,11 @@ class SQLCompiler:
|
||||||
if col in self.query.annotations:
|
if col in self.query.annotations:
|
||||||
# References to an expression which is masked out of the SELECT
|
# References to an expression which is masked out of the SELECT
|
||||||
# clause.
|
# clause.
|
||||||
|
if self.query.combinator and self.select:
|
||||||
|
# Don't use the resolved annotation because other
|
||||||
|
# combinated queries might define it differently.
|
||||||
|
expr = F(col)
|
||||||
|
else:
|
||||||
expr = self.query.annotations[col]
|
expr = self.query.annotations[col]
|
||||||
if isinstance(expr, Value):
|
if isinstance(expr, Value):
|
||||||
# output_field must be resolved for constants.
|
# output_field must be resolved for constants.
|
||||||
|
@ -378,10 +383,14 @@ class SQLCompiler:
|
||||||
else:
|
else:
|
||||||
if col_alias:
|
if col_alias:
|
||||||
raise DatabaseError('ORDER BY term does not match any column in the result set.')
|
raise DatabaseError('ORDER BY term does not match any column in the result set.')
|
||||||
# Add column used in ORDER BY clause without an alias to
|
# Add column used in ORDER BY clause to the selected
|
||||||
# the selected columns.
|
# columns and to each combined query.
|
||||||
self.query.add_select_col(src)
|
order_by_idx = len(self.query.select) + 1
|
||||||
resolved.set_source_expressions([RawSQL('%d' % len(self.query.select), ())])
|
col_name = f'__orderbycol{order_by_idx}'
|
||||||
|
for q in self.query.combined_queries:
|
||||||
|
q.add_annotation(expr_src, col_name)
|
||||||
|
self.query.add_select_col(resolved, col_name)
|
||||||
|
resolved.set_source_expressions([RawSQL(f'{order_by_idx}', ())])
|
||||||
sql, params = self.compile(resolved)
|
sql, params = self.compile(resolved)
|
||||||
# Don't add the same column twice, but the order direction is
|
# Don't add the same column twice, but the order direction is
|
||||||
# not taken into account so we strip it. When this entire method
|
# not taken into account so we strip it. When this entire method
|
||||||
|
|
|
@ -1881,9 +1881,9 @@ class Query(BaseExpression):
|
||||||
self.select = ()
|
self.select = ()
|
||||||
self.values_select = ()
|
self.values_select = ()
|
||||||
|
|
||||||
def add_select_col(self, col):
|
def add_select_col(self, col, name):
|
||||||
self.select += col,
|
self.select += col,
|
||||||
self.values_select += col.output_field.name,
|
self.values_select += name,
|
||||||
|
|
||||||
def set_select(self, cols):
|
def set_select(self, cols):
|
||||||
self.default_cols = False
|
self.default_cols = False
|
||||||
|
|
|
@ -211,6 +211,28 @@ class QuerySetSetOperationTests(TestCase):
|
||||||
with self.subTest(qs=qs):
|
with self.subTest(qs=qs):
|
||||||
self.assertEqual(list(qs), expected_result)
|
self.assertEqual(list(qs), expected_result)
|
||||||
|
|
||||||
|
def test_union_with_values_list_and_order_on_annotation(self):
|
||||||
|
qs1 = Number.objects.annotate(
|
||||||
|
annotation=Value(-1),
|
||||||
|
multiplier=F('annotation'),
|
||||||
|
).filter(num__gte=6)
|
||||||
|
qs2 = Number.objects.annotate(
|
||||||
|
annotation=Value(2),
|
||||||
|
multiplier=F('annotation'),
|
||||||
|
).filter(num__lte=5)
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
qs1.union(qs2).order_by('annotation', 'num').values_list('num', flat=True),
|
||||||
|
[6, 7, 8, 9, 0, 1, 2, 3, 4, 5],
|
||||||
|
)
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
qs1.union(qs2).order_by(
|
||||||
|
F('annotation') * F('multiplier'),
|
||||||
|
'num',
|
||||||
|
).values('num'),
|
||||||
|
[6, 7, 8, 9, 0, 1, 2, 3, 4, 5],
|
||||||
|
operator.itemgetter('num'),
|
||||||
|
)
|
||||||
|
|
||||||
def test_count_union(self):
|
def test_count_union(self):
|
||||||
qs1 = Number.objects.filter(num__lte=1).values('num')
|
qs1 = Number.objects.filter(num__lte=1).values('num')
|
||||||
qs2 = Number.objects.filter(num__gte=2, num__lte=3).values('num')
|
qs2 = Number.objects.filter(num__gte=2, num__lte=3).values('num')
|
||||||
|
|
Loading…
Reference in New Issue