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
A server-side predicate over a stored NaN or infinite float answers in PostgreSQL's order; only loading throws #232
docs/why.md says loading a row with a NaN or infinite float "throws instead of returning a value that would sort differently in memory", and docs/efcore.md says "Only finite values load". Both suggest the model is protected from PostgreSQL's NaN order, but a server-side predicate over such a row answers silently in PostgreSQL's order. Found by the pre-tag analysis (#179), part B, with rows written directly in SQL:
Stored
Query
Answer
Score = '[0,NaN)'
Where(r => r.Score.Contains(1e308))
the row is returned
Ratio = 'NaN'
r.Score.Contains(r.Ratio) on [0,)
1 row
Weights = '{1,NaN}'
Select(r => r.Weights.Count)
2
Only materializing the entity throws. The operand guards are complete: a NaN constant, a NaN parameter, and server-side constructors all throw before the query runs.
Change
Document it where the finite-only rule is stated, in docs/efcore.md (custom types / "stricter than the database") and docs/why.md. A query over rows the application did not write can answer in PostgreSQL's NaN order. To keep NaN and infinity out of a column, add a CHECK constraint (for example lower(x) <> 'NaN' AND upper(x) <> 'NaN', NOT ('NaN' = ANY(x)) for arrays, and the infinity equivalents), measured on a live server. Add a claim for the sentence.
Done when
The docs state the boundary. A live test shows the recommended constraints refuse NaN and ±Infinity in each column kind (range, multirange, array).
Measured on PostgreSQL 16.15 and 18.1 while fixing this (#241): the bounds check suggested above (lower(x) <> 'NaN' AND upper(x) <> 'NaN' and the infinity equivalents) is complete for a range but not for a multirange. A multirange's lower/upper are its outer bounds and are NULL beside an unbounded side, which a CHECK lets pass, so {(,-Infinity],[1,2)}, {(,-Infinity),[1,2)}, {[1,2),[Infinity,)}, {[1,2),(Infinity,)} and {(,0),[1,Infinity),[NaN,)} are all stored. The documented multirange constraint checks the text instead (m::text !~* '(nan|inf)'); arrays use NOT (a && '{NaN,Infinity,-Infinity}').
Activity
CaffeinatedCoder commentedon Oct 18, 2026
Measured on PostgreSQL 16.15 and 18.1 while fixing this (#241): the bounds check suggested above (
lower(x) <> 'NaN' AND upper(x) <> 'NaN'and the infinity equivalents) is complete for a range but not for a multirange. A multirange'slower/upperare its outer bounds and are NULL beside an unbounded side, which a CHECK lets pass, so{(,-Infinity],[1,2)},{(,-Infinity),[1,2)},{[1,2),[Infinity,)},{[1,2),(Infinity,)}and{(,0),[1,Infinity),[NaN,)}are all stored. The documented multirange constraint checks the text instead (m::text !~* '(nan|inf)'); arrays useNOT (a && '{NaN,Infinity,-Infinity}').