Skip to content

Pin NULL-is-smallest ORDER BY on PostgreSQL - #5

Merged
dsblank merged 1 commit into
mainfrom
fix-postgresql-null-ordering
Oct 10, 2026
Merged

dsblank merged 1 commit into
mainfrom
fix-postgresql-null-ordering

Conversation

@dsblank

@dsblank dsblank commented Oct 9, 2026

Copy link
Copy Markdown
Owner

Fixes #4.

Problem

The keyset seek (_keyset_leg_sql) and the evaluator's _null_safe_cmp both assume NULL sorts as the smallest value in both directions, which is SQLite's default. compile_query emitted a bare ASC/DESC. PostgreSQL's default is the opposite: NULL sorts last in ASC and first in DESC. As a result, PostgreSQL ordered results differently from SQLite, and keyset paging dropped rows.

Fix

  • New _order_direction_sql: the PostgreSQL dialect emits ASC NULLS FIRST / DESC NULLS LAST. SQLite output is unchanged.
  • The handle tiebreaker stays bare. It's the NOT NULL primary key, so the clause would change nothing there.
  • Docstrings in query.py and proxied_query.py now say the order is set explicitly on PostgreSQL.

Testing

  • Updated 2 SQL-string assertions and added 2 tests: PostgreSQL gets the NULLS clauses in both directions, and SQLite gets none. All 741 tests pass.

  • gramps-web-api's tests/test_object_query_parsing.py passes against this branch (120 tests).

  • On a real PostgreSQL server (SharedPostgreSQL dev tree, 7,262 people, 5,359 with no birth place), sorted by birth.place.title and paged 500 at a time with keyset cursors:

    asc paged desc paged
    before 1,903 of 7,262 5,359 of 7,262
    after 7,262, same order as unpaged 7,262, same order as unpaged

🤖 Generated with Claude Code

The keyset seek (_keyset_leg_sql) and the evaluator's _null_safe_cmp
both assume NULL sorts as the smallest value in both directions --
SQLite's default. compile_query emitted a bare ASC/DESC, and
PostgreSQL's default is the opposite (NULL last in ASC, first in DESC),
so results reordered relative to SQLite and keyset paging dropped rows.

The PostgreSQL dialect now emits ASC NULLS FIRST / DESC NULLS LAST.
SQLite output is unchanged. The handle tiebreaker stays bare: it's the
NOT NULL primary key, so the clause would change nothing there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dsblank
dsblank merged commit ba278f1 into main Oct 10, 2026
6 checks passed
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.

NULL ordering differs on PostgreSQL: ORDER BY and keyset paging disagree, paging drops rows

1 participant