Opened 55 minutes ago
Last modified 19 minutes ago
#37474 new Bug
Combined queries with values() ordered by a non-selected field crash after a clone is evaluated
| Reported by: | cheek-walnut | Owned by: | |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 5.2 |
| Severity: | Low | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
On a combined query (e.g. union()) using values() and ordered by a field that isn't in values(), evaluating one queryset breaks other querysets cloned from the same query.
class Author(models.Model): name = models.CharField(max_length=20) age = models.IntegerField() class Book(models.Model): title = models.CharField(max_length=20) pages = models.IntegerField() author = models.ForeignKey(Author, models.CASCADE, related_name="books")
With two books (pages 10 and 20):
qs = Book.objects.filter(pages__gt=0).union(Book.objects.filter(pages__gt=0)) qs = qs.values("title").order_by("pages") qs2 = qs.all() list(qs) # [{'title': 'b1'}, {'title': 'b2'}] list(qs2) # IndexError
File "django/db/models/sql/compiler.py", line 77, in setup_query
self.select, self.klass_info, self.annotation_col_map = self.get_select(
File "django/db/models/sql/compiler.py", line 295, in get_select
expression = cols[expression]
IndexError: tuple index out of range
Evaluating qs2 first breaks qs instead. list(qs[:1]) followed by list(qs), or evaluating qs.all() twice, crash the same way. Ordering by a selected field (order_by("title")) works.
Expected: both evaluations return [{'title': 'b1'}, {'title': 'b2'}], as on Django 5.1.
Regression in 65ad4ade74dc9208b9d686a451cd6045df0c9c3a (Refs #28900): the queries work on its parent and fail on it. Reproduced on 5.2.19, 6.0.10, 6.1.3 and main with SQLite, and on main with PostgreSQL 16; 5.1.15 is not affected.
The cause appears to be that Query.clone() (django/db/models/sql/query.py:387-395 at 5bd95c57a2) doesn't copy the selected dict, so clones share it, while Query.add_select_col() (:2265, called from SQLCompiler.get_order_by() at compiler.py:544 for combined queries) mutates it in place and each clone keeps its own select tuple. This is different from #37466, which is about composite primary keys.
AI disclosure: found during a code audit with Claude Code 2.1.317 (Claude Opus 5.5), which also reduced the test case and ran it on the versions above and on 65ad4ade74 and its parent. I reviewed this report and re-ran the reproduction on main and stable/5.1.x.
Change History (4)
comment:1 by , 52 minutes ago
| Summary: | Combined queries with values() ordered by a non-selected field crash with IndexError after a clone is → Combined queries with values() ordered by a non-selected field crash after a clone is evaluated |
|---|---|
| Version: | 6.1 → 5.2 |
comment:3 by , 35 minutes ago
| Description: | modified (diff) |
|---|
comment:4 by , 19 minutes ago
| Description: | modified (diff) |
|---|
This is easier to hit than the description suggests, because common operations evaluate a clone of the queryset with its ordering, e.g.
Paginator,first()or slicing. With five books:qs.first()followed bylist(qs), andvalues_list("pk", flat=True)evaluated in slices (ids[0:2], thenids[2:4]), crash the same way.All of these work on 5.1.15 and on the parent of 65ad4ade74, and fail on 65ad4ade74, 5.2.19, 6.0.10, 6.1.3 and main.
AI disclosure: these variants were also found and run with Claude Code 2.1.317 (Claude Opus 5.5); I re-ran them on main and stable/5.1.x.