Skip to content

fix(pg-core): align .desc() index NULLS ordering with desc() order-by (#5978) - #6142

Open
aliabbas-muhammadi wants to merge 1 commit into
drizzle-team:mainfrom
aliabbas-muhammadi:fix/pg-desc-index-nulls-ordering
Open

fix(pg-core): align .desc() index NULLS ordering with desc() order-by (#5978)#6142
aliabbas-muhammadi wants to merge 1 commit into
drizzle-team:mainfrom
aliabbas-muhammadi:fix/pg-desc-index-nulls-ordering

Conversation

@aliabbas-muhammadi

Copy link
Copy Markdown

Fixes #5978.

What / Why

A .desc() index and the desc() order-by helper disagree on NULLS ordering, so Postgres silently refuses to use the index for the ORDER BY ... DESC LIMIT it was created for.

  • The index builder pins nulls: 'last' for every column, so a .desc() index emits ... DESC NULLS LAST.
  • The desc() order-by helper emits a bare desc, which Postgres reads with its DESC default of NULLS FIRST.

Postgres matches an index to an ORDER BY on pathkeys that include the nulls flag (and does not consult NOT NULL), so a DESC NULLS LAST index can never serve ORDER BY col DESC — the planner falls back to a full scan + sort. That is the exact "index ignored for ORDER BY ... DESC LIMIT" symptom in #5978.

Change

drizzle-orm/src/pg-core/columns/common.ts: stop pinning nulls: 'last' in the index column config. Leaving it undefined lets the drizzle-kit serializer apply Postgres's own direction-aware default (desc => first, asc => last), so a bare .desc() index now emits DESC NULLS FIRST — matching what desc() order-by produces.

Only bare .desc() changes:

  • .asc() / default columns still resolve to NULLS LAST (unchanged).
  • Explicit .desc().nullsLast() / .nullsFirst() are honored as before.

This is the reporter's preferred alignment direction (align the index to the query default), and it leaves the desc() order-by output — and therefore every existing query's NULL placement — untouched.

Tests

drizzle-kit/tests/indexes/pg.test.ts: added a regression test asserting the .desc() index DDL is ... DESC NULLS FIRST and that desc() order-by emits a bare desc (the same NULLS FIRST default), so the two agree. Updated the existing index tests (and the DB-gated push test) that encoded the old DESC NULLS LAST output for implicit .desc() columns to the corrected NULLS FIRST.

Note on generated migrations

This changes generated DDL for existing implicit .desc() indexes: the next drizzle-kit generate drops + recreates them as DESC NULLS FIRST. That is the intended correction (the old index was unusable for the query it was built for), but flagging it so the snapshot change is expected.

gel-core has the identical latent line (gel-core/columns/common.ts); left untouched to keep this fix scoped to the Postgres issue, but happy to mirror it.

I based this on main; glad to retarget to rc5 if you prefer — the affected line is identical there (common.ts:583).

A .desc() index pinned nulls:'last' and emitted DESC NULLS LAST, but the
desc() order-by helper emits a bare DESC (Postgres default NULLS FIRST).
Postgres matches indexes to ORDER BY on pathkeys including the nulls flag,
so the index could never serve the ORDER BY ... DESC LIMIT it was built for.

Stop pinning nulls:'last' so drizzle-kit applies Postgres's direction
default (desc => first), making a bare .desc() index emit DESC NULLS FIRST
to match the query. asc columns and explicit nullsLast/nullsFirst unchanged.

Fixes drizzle-team#5978
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: .desc() index (DESC NULLS LAST) contradicts desc() orderBy (bare desc = NULLS FIRST), so Postgres silently never uses the index

1 participant