#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.)

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top