Fixed #31060 -- Reallowed window expressions to be used in conditions outside of queryset filters.
Regression in 4edad1ddf6
.
Thanks utapyngo for the report.
This commit is contained in:
parent
5708327c37
commit
bf12273db4
|
@ -100,7 +100,10 @@ class Q(tree.Node):
|
||||||
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
|
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
|
||||||
# We must promote any new joins to left outer joins so that when Q is
|
# We must promote any new joins to left outer joins so that when Q is
|
||||||
# used as an expression, rows aren't filtered due to joins.
|
# used as an expression, rows aren't filtered due to joins.
|
||||||
clause, joins = query._add_q(self, reuse, allow_joins=allow_joins, split_subq=False)
|
clause, joins = query._add_q(
|
||||||
|
self, reuse, allow_joins=allow_joins, split_subq=False,
|
||||||
|
check_filterable=False,
|
||||||
|
)
|
||||||
query.promote_joins(joins)
|
query.promote_joins(joins)
|
||||||
return clause
|
return clause
|
||||||
|
|
||||||
|
|
|
@ -1190,7 +1190,7 @@ class Query(BaseExpression):
|
||||||
|
|
||||||
def build_filter(self, filter_expr, branch_negated=False, current_negated=False,
|
def build_filter(self, filter_expr, branch_negated=False, current_negated=False,
|
||||||
can_reuse=None, allow_joins=True, split_subq=True,
|
can_reuse=None, allow_joins=True, split_subq=True,
|
||||||
reuse_with_filtered_relation=False):
|
reuse_with_filtered_relation=False, check_filterable=True):
|
||||||
"""
|
"""
|
||||||
Build a WhereNode for a single filter clause but don't add it
|
Build a WhereNode for a single filter clause but don't add it
|
||||||
to this Query. Query.add_q() will then add this filter to the where
|
to this Query. Query.add_q() will then add this filter to the where
|
||||||
|
@ -1229,6 +1229,7 @@ class Query(BaseExpression):
|
||||||
used_aliases=can_reuse,
|
used_aliases=can_reuse,
|
||||||
allow_joins=allow_joins,
|
allow_joins=allow_joins,
|
||||||
split_subq=split_subq,
|
split_subq=split_subq,
|
||||||
|
check_filterable=check_filterable,
|
||||||
)
|
)
|
||||||
if hasattr(filter_expr, 'resolve_expression'):
|
if hasattr(filter_expr, 'resolve_expression'):
|
||||||
if not getattr(filter_expr, 'conditional', False):
|
if not getattr(filter_expr, 'conditional', False):
|
||||||
|
@ -1244,7 +1245,8 @@ class Query(BaseExpression):
|
||||||
raise FieldError("Cannot parse keyword query %r" % arg)
|
raise FieldError("Cannot parse keyword query %r" % arg)
|
||||||
lookups, parts, reffed_expression = self.solve_lookup_type(arg)
|
lookups, parts, reffed_expression = self.solve_lookup_type(arg)
|
||||||
|
|
||||||
self.check_filterable(reffed_expression)
|
if check_filterable:
|
||||||
|
self.check_filterable(reffed_expression)
|
||||||
|
|
||||||
if not allow_joins and len(parts) > 1:
|
if not allow_joins and len(parts) > 1:
|
||||||
raise FieldError("Joined field references are not permitted in this query")
|
raise FieldError("Joined field references are not permitted in this query")
|
||||||
|
@ -1253,7 +1255,8 @@ class Query(BaseExpression):
|
||||||
value = self.resolve_lookup_value(value, can_reuse, allow_joins)
|
value = self.resolve_lookup_value(value, can_reuse, allow_joins)
|
||||||
used_joins = {k for k, v in self.alias_refcount.items() if v > pre_joins.get(k, 0)}
|
used_joins = {k for k, v in self.alias_refcount.items() if v > pre_joins.get(k, 0)}
|
||||||
|
|
||||||
self.check_filterable(value)
|
if check_filterable:
|
||||||
|
self.check_filterable(value)
|
||||||
|
|
||||||
clause = self.where_class()
|
clause = self.where_class()
|
||||||
if reffed_expression:
|
if reffed_expression:
|
||||||
|
@ -1349,7 +1352,8 @@ class Query(BaseExpression):
|
||||||
return self.build_filter(filter_expr, allow_joins=False)[0]
|
return self.build_filter(filter_expr, allow_joins=False)[0]
|
||||||
|
|
||||||
def _add_q(self, q_object, used_aliases, branch_negated=False,
|
def _add_q(self, q_object, used_aliases, branch_negated=False,
|
||||||
current_negated=False, allow_joins=True, split_subq=True):
|
current_negated=False, allow_joins=True, split_subq=True,
|
||||||
|
check_filterable=True):
|
||||||
"""Add a Q-object to the current filter."""
|
"""Add a Q-object to the current filter."""
|
||||||
connector = q_object.connector
|
connector = q_object.connector
|
||||||
current_negated = current_negated ^ q_object.negated
|
current_negated = current_negated ^ q_object.negated
|
||||||
|
@ -1361,7 +1365,7 @@ class Query(BaseExpression):
|
||||||
child_clause, needed_inner = self.build_filter(
|
child_clause, needed_inner = self.build_filter(
|
||||||
child, can_reuse=used_aliases, branch_negated=branch_negated,
|
child, can_reuse=used_aliases, branch_negated=branch_negated,
|
||||||
current_negated=current_negated, allow_joins=allow_joins,
|
current_negated=current_negated, allow_joins=allow_joins,
|
||||||
split_subq=split_subq,
|
split_subq=split_subq, check_filterable=check_filterable,
|
||||||
)
|
)
|
||||||
joinpromoter.add_votes(needed_inner)
|
joinpromoter.add_votes(needed_inner)
|
||||||
if child_clause:
|
if child_clause:
|
||||||
|
|
|
@ -17,3 +17,8 @@ Bugfixes
|
||||||
* Fixed a regression in Django 3.0 where ``RegexPattern``, used by
|
* Fixed a regression in Django 3.0 where ``RegexPattern``, used by
|
||||||
:func:`~django.urls.re_path`, returned positional arguments to be passed to
|
:func:`~django.urls.re_path`, returned positional arguments to be passed to
|
||||||
the view when all optional named groups were missing (:ticket:`31061`).
|
the view when all optional named groups were missing (:ticket:`31061`).
|
||||||
|
|
||||||
|
* Reallowed, following a regression in Django 3.0,
|
||||||
|
:class:`~django.db.models.expressions.Window` expressions to be used in
|
||||||
|
conditions outside of queryset filters, e.g. in
|
||||||
|
:class:`~django.db.models.expressions.When` conditions (:ticket:`31060`).
|
||||||
|
|
|
@ -4,8 +4,8 @@ from unittest import mock, skipIf, skipUnless
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.db import NotSupportedError, connection
|
from django.db import NotSupportedError, connection
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
F, Func, OuterRef, Q, RowRange, Subquery, Value, ValueRange, Window,
|
BooleanField, Case, F, Func, OuterRef, Q, RowRange, Subquery, Value,
|
||||||
WindowFrame,
|
ValueRange, When, Window, WindowFrame,
|
||||||
)
|
)
|
||||||
from django.db.models.aggregates import Avg, Max, Min, Sum
|
from django.db.models.aggregates import Avg, Max, Min, Sum
|
||||||
from django.db.models.functions import (
|
from django.db.models.functions import (
|
||||||
|
@ -846,6 +846,22 @@ class NonQueryWindowTests(SimpleTestCase):
|
||||||
with self.assertRaisesMessage(NotSupportedError, msg):
|
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||||
qs.annotate(total=Sum('dense_rank', filter=Q(name='Jones'))).filter(total=1)
|
qs.annotate(total=Sum('dense_rank', filter=Q(name='Jones'))).filter(total=1)
|
||||||
|
|
||||||
|
def test_conditional_annotation(self):
|
||||||
|
qs = Employee.objects.annotate(
|
||||||
|
dense_rank=Window(expression=DenseRank()),
|
||||||
|
).annotate(
|
||||||
|
equal=Case(
|
||||||
|
When(id=F('dense_rank'), then=Value(True)),
|
||||||
|
default=Value(False),
|
||||||
|
output_field=BooleanField(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
# The SQL standard disallows referencing window functions in the WHERE
|
||||||
|
# clause.
|
||||||
|
msg = 'Window is disallowed in the filter clause'
|
||||||
|
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||||
|
qs.filter(equal=True)
|
||||||
|
|
||||||
def test_invalid_order_by(self):
|
def test_invalid_order_by(self):
|
||||||
msg = 'order_by must be either an Expression or a sequence of expressions'
|
msg = 'order_by must be either an Expression or a sequence of expressions'
|
||||||
with self.assertRaisesMessage(ValueError, msg):
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
|
Loading…
Reference in New Issue