Opened 57 minutes ago
#37356 new Bug
Filtering on a window expression crashes or truncates rows when a composite primary key is selected
| Reported by: | Dave Gaeddert | Owned by: | |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 5.2 |
| Severity: | Normal | Keywords: | composite primary key, window expressions |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
class Tenant(models.Model):
pass
class Token(models.Model):
pk = models.CompositePrimaryKey("tenant_id", "id")
tenant = models.ForeignKey(Tenant, models.CASCADE)
id = models.SmallIntegerField()
secret = models.CharField(max_length=10)
qs = Token.objects.annotate(
rn=Window(RowNumber(), partition_by=F("tenant_id"), order_by="id")
).filter(rn=1)
qs.values("pk", "secret")
# IndexError: list index out of range (PostgreSQL)
qs.values_list("pk", "secret")
# [((1,), ''), ((3,), '')] (SQLite: pk silently missing a component)
# expected [((1, 1), ''), ((2, 3), '')]
Reproduces on 5.2, 6.0, and main.
Cause: get_select(with_col_aliases=True) assigns one colN alias per selection, but a CompositePrimaryKey compiles to N columns, so the alias lands on only the last one. get_qualify_sql() then masks the outer query to the aliases, which is one column short of the physical row:
SELECT "col1", "secret" FROM ( SELECT "token"."tenant_id", "token"."id" AS "col1", "token"."secret" AS "secret", ...
Unlike #37353, #37354, and #37355 there's no minimal fix for this one: a selection would need to carry N aliases, which changes the (expr, (sql, params), alias) contract shared by get_select(), as_sql(), and get_qualify_sql(). Per Jacob Walls' suggestion on the #37222 PR thread, the practical option is probably to raise NotSupportedError for a composite primary key selected under a window filter, consistent with how other single-expression contexts (Max("pk")) reject composite keys.
Found while verifying the fix for #37222: https://github.com/django/django/pull/21659#discussion_r3899304732
Details: https://github.com/davegaeddert/django/issues/7
(AI assistance: Claude Code was used to find and reproduce this; I verified the reproducer.)