[Django] #28731: Passing an empty Q() to a When inside a Case causes an OperationError

33 views
Skip to first unread message

Django

unread,
Oct 21, 2017, 1:59:29 PM10/21/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van | Owner: nobody
Bussel |
Type: Bug | Status: new
Component: Database | Version: 1.11
layer (models, ORM) |
Severity: Normal | Keywords:
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
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.

--
Ticket URL: <https://code.djangoproject.com/ticket/28731>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Oct 21, 2017, 2:00:09 PM10/21/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: nobody
Type: Bug | Status: new
Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Description changed by Tom van Bussel:

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>

Django

unread,
Oct 21, 2017, 5:38:14 PM10/21/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: nobody
Type: Bug | Status: new

Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

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>

Django

unread,
Oct 22, 2017, 9:49:11 AM10/22/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: nobody
Type: Bug | Status: new

Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

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>

Django

unread,
Oct 23, 2017, 2:25:47 PM10/23/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: nobody
Type: Bug | Status: new

Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Tim Graham):

* stage: Unreviewed => Accepted


--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:4>

Django

unread,
Oct 29, 2017, 5:11:33 AM10/29/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: nobody
Type: Bug | Status: new

Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

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>

Django

unread,
Oct 29, 2017, 5:11:40 AM10/29/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: nobody
Type: Bug | Status: new

Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Adam (Chainz) Johnson):

* cc: Adam (Chainz) Johnson (added)


--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:6>

Django

unread,
Nov 5, 2017, 4:20:25 PM11/5/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: assigned

Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Tim Martin):

* owner: nobody => Tim Martin
* status: new => assigned


--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:7>

Django

unread,
Nov 6, 2017, 6:11:57 AM11/6/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: assigned
Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

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>

Django

unread,
Nov 6, 2017, 3:49:38 PM11/6/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: assigned
Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

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>

Django

unread,
Nov 6, 2017, 4:09:22 PM11/6/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: assigned
Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

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>

Django

unread,
Nov 7, 2017, 4:54:46 AM11/7/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: assigned
Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

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>

Django

unread,
Nov 7, 2017, 3:25:41 PM11/7/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: assigned
Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

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>

Django

unread,
Nov 18, 2017, 4:19:59 PM11/18/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: assigned
Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

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>

Django

unread,
Nov 21, 2017, 4:38:33 PM11/21/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: assigned
Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Tim Martin):

* 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>

Django

unread,
Dec 16, 2017, 5:49:49 PM12/16/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: assigned
Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Tim Martin):

FWIW, this should be ready to re-review.

--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:15>

Django

unread,
Dec 16, 2017, 8:26:37 PM12/16/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: assigned
Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Simon Charette):

* stage: Accepted => Ready for checkin


--
Ticket URL: <https://code.djangoproject.com/ticket/28731#comment:16>

Django

unread,
Dec 26, 2017, 11:09:26 AM12/26/17
to django-...@googlegroups.com
#28731: Passing an empty Q() to a When inside a Case causes an OperationError
-------------------------------------+-------------------------------------
Reporter: Tom van Bussel | Owner: Tim
| Martin
Type: Bug | Status: closed

Component: Database layer | Version: 1.11
(models, ORM) |
Severity: Normal | Resolution: fixed

Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Tim Graham <timograham@…>):

* 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>

Reply all
Reply to author
Forward
0 new messages