--
Ticket URL: <https://code.djangoproject.com/ticket/28731>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
Old description:
> The following code causes an OperationError:
> {{{
> q = Q() # Dynamically constructed, possibly empty
> x = TestModel.objects.annotate(test_name=When(q, then=Value(True)),
> default=Value(False), output_field=BooleanField())).all()
> print(x)
> }}}
> The code above results in a SQL query which contains {{{CASE WHEN
> THEN}}}, with nothing between the WHEN and THEN.
New description:
The following code causes an OperationError:
{{{
q = Q() # Dynamically constructed, possibly empty
x = TestModel.objects.annotate(test_name=Case(When(q, then=Value(True)),
default=Value(False), output_field=BooleanField())).all()
print(x)
}}}
The code above results in a SQL query which contains {{{CASE WHEN
THEN}}}, with nothing between the WHEN and THEN.
--
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:1>
Comment (by Tomer Chachamu):
Thanks for the bug report.
What did you expect to be the value of `x[0].test_name`?
We don't have any documentation on how a `Q(**kwargs)` object behaves when
`len(kwargs) == 0`, and we have no tests for it. Perhaps it should be
raising an exception (or initially a deprecation warning).
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:2>
Comment (by Tom van Bussel):
Replying to [comment:2 Tomer Chachamu]:
> What did you expect to be the value of `x[0].test_name`?
I expected `x[0].test_name == True` to hold.
> We don't have any documentation on how a `Q(**kwargs)` object behaves
when `len(kwargs) == 0`, and we have no tests for it. Perhaps it should be
raising an exception (or initially a deprecation warning).
Currently `TestModel.objects.filter(Q()).all() ==
TestModel.objects.all()`, so it would be nice if that behavior is matched
everywhere.
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:3>
* stage: Unreviewed => Accepted
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:4>
Comment (by Adam (Chainz) Johnson):
> it would be nice if that behavior is matched everywhere.
I agree, probably `Q()` should result in SQL something like `1=1`.
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:5>
* cc: Adam (Chainz) Johnson (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:6>
* owner: nobody => Tim Martin
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:7>
Comment (by Tomer Chachamu):
Currently `Q() | Q(value=1)` outputs the same SQL as `Q(value=1)` which
will not be the case if `Q()` is effectively always `true`.
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:8>
Comment (by Tim Martin):
Worse, you get the same result for `Q() & Q(value=1)` as you do for `Q() |
Q(value=1)`, because empty `Q()` expressions are ignored when combining
(`query_utils.py:68`). I don't think there's an easy way to make this
consistent.
I'm guessing it should be possible to make the originally reported case
not trigger invalid SQL, which is a step forward. Maybe it should report
this as an invalid expression?
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:9>
Comment (by Adam (Chainz) Johnson):
Those are some good points Tim/Tomer. I agree that making When() raise an
error when passed an empty Q would make sense.
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:10>
Comment (by Tomer Chachamu):
If the usage of `Q()` is deprecated and will be removed then there's no
need for it to create valid SQL (in more situations than it does
currently). We could add tests for the existing behaviour of `Q() &
Q(value=1)` and of `Q() | Q(value=1)`.
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:11>
Comment (by Tim Martin):
I don't think it's explicitly deprecated, we'd need to have a discussion
on the mailing list to get consensus on that.
However, I think reporting an explicit "empty `Q()` object not supported"
error is much better than sending invalid SQL to the database and getting
it rejected. I don't think the DB layer should ever send invalid SQL.
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:12>
Comment (by Tim Martin):
After a bit of digging, I found the rationale for this behaviour in
#28211. The argument seems to be that it's common to do something like:
{{{
filters = Q()
if condition:
filters |= Q(x=1)
if other_condition:
filters |= Q(y=2)
}}}
I'm not sure I agree that this is the right design but there is at least a
rationale here, and it's been like this for many years now so I don't
think changing it is viable. This rationale supports the idea that empty
Q() objects are legitimate steps in building up a query, but shouldn't be
used directly. I'll make this report an explicit error rather than invalid
SQL.
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:13>
* has_patch: 0 => 1
Comment:
I've added a [https://github.com/django/django/pull/9373 pull request]
that makes this raise an exception.
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:14>
Comment (by Tim Martin):
FWIW, this should be ready to re-review.
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:15>
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:16>
* status: assigned => closed
* resolution: => fixed
Comment:
In [changeset:"5778b5701d6a0feb3053b70891cd8ce80b6e8601" 5778b57]:
{{{
#!CommitTicketReference repository=""
revision="5778b5701d6a0feb3053b70891cd8ce80b6e8601"
Fixed #28731 -- Added an error message when using an empty Q() in a When
expression.
Otherwise it generates invalid SQL.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:17>