fix(sql): render NULLS FIRST/LAST for the orderBy nulls option - #30101
fix(sql): render NULLS FIRST/LAST for the orderBy nulls option#30101wasaybuilds wants to merge 2 commits into
Conversation
`OrderByOptions.nulls` typechecked at the call site and was dropped at build time: `OrderByItem` had nowhere to carry a NULL placement, so the builder had nothing to write to and no target ever emitted a NULLS clause. A sort that reads as explicitly NULL-ordered in the source compiled clean and returned rows in a different order, silently. Give `OrderByItem` a `nulls` slot, pass the option through `resolveOrderBy`, and render it in the Postgres and SQLite adapters (select lists, window ORDER BY, and aggregate ORDER BY alike). Omitting the option still emits no clause, so existing plans are byte-identical. `reverse()` flips the placement along with the direction, since inverting a total order has to move NULLs to the opposite end for a backward page to mirror the forward one. Fixes prisma#29932 Signed-off-by: Abdul Wasay <wasaya670@gmail.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
🚧 Files skipped from review as they are similar to previous changes (10)
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughThe SQL builder now preserves optional NULL placement in ChangesNULL ordering
Estimated code review effort: 3 (Moderate) | ~25 minutes Merge Risk: ⚪ Minimal · up to The PR preserves existing ordering when NULL placement is omitted and adds explicit NULLS FIRST/LAST behavior for supported ordering paths. No actionable merge-blocking risk remains beyond normal checks and review. Sequence Diagram(s)sequenceDiagram
participant SQLBuilder
participant OrderByItem
participant SQLRenderer
participant Database
SQLBuilder->>OrderByItem: pass direction and nulls placement
OrderByItem->>SQLRenderer: provide order-by AST
SQLRenderer->>Database: execute ORDER BY with NULLS FIRST or NULLS LAST
Database-->>SQLBuilder: return ordered results
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
Linked issue
Fixes #29932
Summary
OrderByOptions.nullstypechecked at the call site and was discarded at build time, so a sort that reads as explicitly NULL-ordered in the source compiled clean and returned rows in a different order, with nothing raising. The root cause is the one the reporter guessed:OrderByItemhad no slot for a NULL placement, so the builder option had nothing to write to and no target ever emitted aNULLSclause.Of the two closes the issue offers, this takes the "wire it up" one:
OrderByItemgains anullsslot,resolveOrderBypasses the option through on both overloads, and the Postgres and SQLite adapters render it — select lists, windowORDER BY, and aggregateORDER BYalike.Omitting the option emits no clause, so existing plans stay byte-identical (asserted per adapter, not just claimed).
reverse()flips the placement along with the direction, since inverting a total order has to move NULLs to the opposite end for a backward cursor page to mirror the forward one.One detail worth flagging for reviewers: the two targets' defaults disagree. PostgreSQL ranks NULLs highest (last under
asc); SQLite ranks them lowest (first underasc). That divergence is the argument for the option, so it is documented on theNullsPlacementtype and in the skill reference rather than left implicit.Testing performed
pnpm --filter @internal/sql-relational-core test— 499 pass (6 new: placement carried, preserved acrossrewrite, flipped byreverse, double-reverseround trip)pnpm --filter @internal/sql-builder test— 174 pass (3 new:nullsreaches the AST on bothorderByoverloads, absent when unset)pnpm --filter @internal/adapter-postgres test/@internal/adapter-sqlite— exact-SQL assertions forNULLS FIRST/NULLS LAST, the no-clause case, and a window-functionORDER BYpnpm --filter @internal/sql-orm-client test— 771 pass, unchanged (largest downstream consumer of order items)order-by-nulls.integration.test.tsagainst PGlite: seeds the issue's own repro rows ('b', NULL, 'a', NULL, 'c') and asserts real returned row order for overridden and default placement in both directionspnpm lint:skills, andbiome format+biome checkvia the pre-commit hook on every touched fileTwo things I could not get a clean local signal on, both pre-existing on an unmodified
mainin my environment (verified by stashing):@prisma/orm-*shell packages fail to build withdropped "…" (ambiguous between …)barrel-aggregation errors, which cascades intoe2e-tests/integration-teststypecheck. Unrelated to this change, but it means I could not runpnpm typecheckend to end.render-typescript.roundtripanddb-init-update.clisuites fail in both adapters. Same failures before and after this change; they look like Windows path/line-ending artifacts on my machine.Repo-wide
pnpm lintis also unusable for me becausecore.autocrlf=trueyields a CRLF working tree in an LF repo, which flags files I never touched. The per-filebiome checkin the pre-commit hook did pass on this diff.Skill update
Added the
nullsoption toskills/prisma-8/references/queries-postgres.mdnext to the existingdb.sql.orderBy(..., { direction })example, including the differing per-target defaults. No CLI, config, or error-code surface changed.Checklist
git commit -s) per the DCO.TML-NNNN: <sentence-case title>form. I have no Linear ticket as an external contributor, so I used the conventional-commit title CONTRIBUTING.md asks external contributors for. Happy to retitle to whatever a maintainer prefers.Notes for the reviewer
Deliberately deferred, since CONTRIBUTING.md asks for one logical change per PR: the ORM lane still has no
nullssurface (p.nullable.desc({ nulls: 'last' })). The AST slot this PR adds is what such a surface would write to, so it can land as a follow-up — happy to open one if you want it.The issue's alternative resolution was to delete
nullsfromOrderByOptionsso the gap fails at compile time instead. I went the other way because the type is already published and the capability is a plainORDER BYfeature both targets support (SQLite since 3.30). Say the word if you would rather have the removal.Also worth a reviewer's eye:
reverse()now flips the NULL placement. Nothing in-tree setsnullson a reversed item today, so it is not observable yet, but it is the semantics cursor pagination will need once the ORM lane exposes the option.Summary by CodeRabbit
New Features
NULLS FIRSTandNULLS LASTordering for SQL queries.ORDER BYclauses.Documentation