#36388 closed Bug (fixed)
QuerySet.union no longer supports empty args
Reported by: | Antoine Humeau | Owned by: | Colleen Dunlap |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 5.2 |
Severity: | Release blocker | Keywords: | |
Cc: | Simon Charette | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Hello there,
Up until Django 5.1 (included), it was possible to use the union queryset method with an empty arg list, example:
User.objects.union()
This example is obviously useless but the following pattern would be equivalent and would be more reallistic:
def unite_querysets(querysets): queryset, *other_querysets = querysets queryset = queryset.union(*other_querysets)
Since Django 5.2, that triggers the following exception:
AttributeError: 'NoneType' object has no attribute 'elide_empty'
The line that triggers the exception is the following: https://github.com/django/django/blob/5.2.1/django/db/models/sql/compiler.py#L616
empty_compiler is never initialized.
I am not sure this really qualifies as a bug, it might be a unsupported usage, still it is a regression.
Change History (17)
comment:1 by , 2 months ago
Cc: | added |
---|
comment:2 by , 2 months ago
Component: | Uncategorized → Database layer (models, ORM) |
---|---|
Severity: | Normal → Release blocker |
Triage Stage: | Unreviewed → Accepted |
Marking as a release blocker but willing to downgrade
comment:4 by , 2 months ago
Thanks for the report and triage folks. I think it's worth fixing by having QuerySet.union
return self
if not other_qs
given it's a non-invasive patch and dealing with empty sequence in a different manner would be annoyance.
comment:5 by , 2 months ago
Owner: | set to |
---|---|
Status: | new → assigned |
comment:6 by , 2 months ago
Has patch: | set |
---|
comment:7 by , 2 months ago
Patch needs improvement: | set |
---|
Left some comments on the PR, the proposed approach generates an unnecessary UNION
against itself that causes the improper results to be returned when all
is used as they are not deduped.
comment:8 by , 2 months ago
Patch needs improvement: | unset |
---|
Hi Simon! I responded. It's already returning self and it's still breaking. Also do I make my PR pointing to the 5.2 branch or main?
comment:9 by , 2 months ago
Patch needs improvement: | set |
---|
comment:10 by , 2 months ago
It should point at main
which your new PR does, thank you for that.
Please have a look at this comment. The fact the tests are passing is not a good indicative that the right thing is happening here as the adjusted test I provided demonstrates (it does a UNION
against itself which is not safe when all
is used).
I still believe that the right way of fixing this is to adjust QuerySet.union
to return self
when not other_qs
as the compilation layer is not equipped to deal with an empty combination of queries and it's just faster to avoid the query cloning if we can.
comment:11 by , 2 months ago
is that not what this is doing already? https://github.com/django/django/blob/0b2ed4f7c8396c8d9aa8428a40e6b25c31312889/django/db/models/query.py#L1548
comment:12 by , 2 months ago
ok nevermind I'm rereading this and it looks like I may have misinterpreted...
comment:13 by , 2 months ago
No, the branch you linked is gated by a if isinstance(self, EmptyQuerySet)
and qs
is a list of all non-empty querysets. Django is able to detect when a queryset cannot match any rows (the most common example is filter(pk=[])
) and the logic you linked deals with this case.
What I mean is that if the *other_qs
args capture is empty then you can immediately return as some_set OR *other_sets
means some_set
.
comment:15 by , 2 months ago
Patch needs improvement: | unset |
---|---|
Triage Stage: | Accepted → Ready for checkin |
git bisect to 9cb8baa0c4fa2c10789c5c8b65f4465932d4d172
Possible regression test
tests/queries/test_qs_combinators.py
However, I agree that this usage isn't supported. Perhaps a breaking change note is enough here? We might want a nicer error also