You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Docs: ordering by LowerBound() or UpperBound() puts open-ended and empty rows where PostgreSQL puts NULLs, also on a required column #359
docs/ranges.md#sort-ranges documents a deliberate difference: "PostgreSQL puts NULL last in an ascending ORDER BY. In memory, null sorts first. On a nullable column, the two orders differ only in where the nulls go." The page doesn't say that the same holds for an ordering by a bound, where the NULLs come from non-null ranges and so appear on a required column too. The README and docs/efcore.md show exactly that query as their first ordering example: bookings.OrderBy(b => b.Period.LowerBound()); // ORDER BY lower(b."Period").
Measured 2026-10-24 on PostgreSQL 16, over a required Int32Range column. The rows by id are 1 [1,20], 2 [1,1], 3 [20,20], 4 (,1], 5 (,20], 6 [1,), 7 [20,), 8 (,) and 9 empty:
So a grid sorted by "from" shows the open-start and empty rows at the bottom when it sorts in the database, and at the top in memory. Ordering by the range itself (OrderBy(r => r.Seats)) uses PostgreSQL's range order, where a missing lower bound sorts first, and agrees (RangeOrderParityTests).
Change
Follow the documented decision and say where it applies:
Extend the "Good to know" bullet in docs/ranges.md#sort-ranges, or add one under docs/efcore.md#read-bounds-merge-ranges-and-aggregate. An ordering by LowerBound() or UpperBound() puts the empty rows and the rows without that bound where PostgreSQL puts NULLs, also on a required column. To sort them as memory does, order by IsUnboundedStart() / IsEmpty() first, or by the range.
Say so beside the README and docs/efcore.md examples that order by LowerBound().
Rewriting the ordering in the query interceptor, so a bound's NULL sorts as in memory, would depart from that decision for bounds only. That is the maintainer's call, not this issue's.
Done when
The sentence is in the docs and quoted by a claim. The claim is held by a test that orders a column of every shape by each bound in both directions and asserts the documented placement.
changed the title [-]OrderBy(LowerBound()) and OrderByDescending(UpperBound()) put open-ended and empty rows at the other end than memory[/-][+]Docs: ordering by LowerBound() or UpperBound() puts open-ended and empty rows where PostgreSQL puts NULLs, also on a required column[/+]on Oct 24, 2026
Activity
[-]OrderBy(LowerBound()) and OrderByDescending(UpperBound()) put open-ended and empty rows at the other end than memory[/-][+]Docs: ordering by LowerBound() or UpperBound() puts open-ended and empty rows where PostgreSQL puts NULLs, also on a required column[/+]docs: ordering by a bound puts the rows without it where PostgreSQL p…
Merge pull request #374 from CaffeinatedCoder/docs/364-bound-ordering…