Repository navigation
Add explicit NULL ordering and NULL-aware cursor pagination - #24
Open
kac-axelor wants to merge 1 commit into
Open
kac-axelor wants to merge 1 commit into
kac-axelor wants to merge 1 commit into
Conversation
Extend OrderBy with ASC_NULLS_FIRST/LAST and DESC_NULLS_FIRST/LAST, exposed through the GraphQL OrderBy enum and translated to SQL NULLS FIRST/LAST. Fix cursor pagination over nullable columns: - Tie-break uses IS NOT DISTINCT FROM so multi-key cursors do not drop rows when a key is NULL. - Keyset predicate includes trailing NULLs when they sit after the cursor, and handles a NULL cursor value explicitly. - Plain ASC/DESC fall back to the Postgres default (ASC -> NULLS LAST, DESC -> NULLS FIRST); backward pagination flips null position with direction.
kac-axelor
force-pushed
the
orderby-null-sorting
branch
from
June 10, 2026 22:39
69b78b5 to
e036bd3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SQL has two orthogonal concepts for ordering nullable columns: the direction (ASC/DESC) and the NULL placement (NULLS FIRST/LAST). Postgres applies a default —
ASC → NULLS LAST,DESC → NULLS FIRST— but callers often need explicit control, e.g. "most recent first, items without a date at the bottom" (DESC NULLS LAST).The
OrderBytype previously exposed only"ASC"and"DESC", so callers could not pin NULL placement independently of direction. This PR adds four explicit variants and plumbs them through the parser, the GraphQL schema, and TypeORM'sOrderByCondition.While extending the cursor-pagination code for the new variants, several pre-existing bugs surfaced in the plain
ASC/DESCpath. Cursor pagination over nullable columns was silently dropping or misordering rows; this PR fixes those too.Changes
1. Extend the
OrderByenumOrderBygainsASC_NULLS_FIRST,ASC_NULLS_LAST,DESC_NULLS_FIRST,DESC_NULLS_LAST. The companionORDER_OPS/ORDER_INVERTEDtables inparser/types.tsare extended so existing comparison and inversion logic works for the new values — including symmetric flipping of both direction and null position for backward pagination.2. Translate to TypeORM's
OrderByConditionA new
toSqlOrderhelper inrepository/query/utils.tsmaps the enum string to TypeORM's{ order, nulls }shape.createSelectQueryand the backward-pagination subquery route through it so the generated SQL contains realNULLS FIRST/LASTclauses.3. Surface the values in GraphQL
The
OrderByenum ingraphql-schema.tsgains the four new values. Because every per-entityOrderByinput andJsonOrderByValue.orderreuse this single enum, the new values become available on every orderable field automatically. The schema change is purely additive — existingASC/DESCclients keep working.4. NULL-aware cursor predicate (bug fix)
parser/cursor.tsis rewritten to be NULL-correct. Three pre-existing bugs are fixed:=, which is unknown for NULL = NULL → rows were silently dropped. Now usesIS NOT DISTINCT FROM.key > :cursoris unknown when the row's key is NULL. When NULLs sit on the "after" side of the current direction, the predicate now addsOR key IS NULL.IS NOT NULLwhen NULLs are at the start of our direction,FALSEwhen at the end.NULL placement is derived from the enum (explicit
NULLS_FIRST/LASTwins; otherwise the Postgres default), and flipped for backward pagination so previous-page boundaries are symmetric with forward.5. Tests
"ASC"/"DESC"— these fail against the old code.6. README
"Sorting and Ordering" section gains an example, the list of six supported values, and a one-liner about the default fallback.
Notes
The new SQL syntax (
NULLS FIRST/LAST,IS NOT DISTINCT FROM) is Postgres-specific. Goovee ORM targets Postgres, so this is fine.The cursor bug was scoped to nullable columns. Cursors keyed on
id(the most common case, as shown in the README) were unaffected, which is likely why the bug survived this long.