#37353 new Bug

QuerySet.values("pk").distinct().order_by() on a composite primary key silently truncates the primary key

Reported by: Dave Gaeddert Owned by:
Component: Database layer (models, ORM) Version: 5.2
Severity: Normal Keywords: composite primary key
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Selecting a composite primary key with values() / values_list(), combined with distinct() and order_by() on a field that isn't selected, returns the primary key with a component missing:

class Tenant(models.Model):
    pass

class User(models.Model):
    pk = models.CompositePrimaryKey("tenant_id", "id")
    tenant = models.ForeignKey(Tenant, models.CASCADE)
    id = models.SmallIntegerField(unique=True)
    email = models.EmailField(unique=True)

User.objects.values("pk").distinct().order_by("email")
# [{'pk': (1,)}, {'pk': (1,)}, {'pk': (2,)}]
# expected [{'pk': (1, 1)}, {'pk': (1, 2)}, {'pk': (2, 3)}]

User.objects.values("pk", "id").distinct().order_by("email")
# IndexError: list index out of range

No exception in the first case — silent data loss. Reproduces on 5.2, 6.0, and main, on both PostgreSQL 16 and SQLite.

Cause: ordering by an unselected field with distinct() appends that field to the select clause via get_extra_select(), and the extra column is then stripped from each row by slicing with SQLCompiler.col_count. col_count is the number of selections, but a CompositePrimaryKey is one selection compiled to N columns, so the slice cuts into the primary key itself.

Found while verifying the fix for #37222. Jacob Walls agreed this is a bug on the PR thread: https://github.com/django/django/pull/21659#discussion_r3899304732

Fix with a regression test on my fork: https://github.com/davegaeddert/django/pull/6 — happy to open it against main if accepted.

(AI assistance: Claude Code was used to find and reproduce this and draft the fix; I verified the reproducer and test results.)

Change History (0)

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