Repository navigation
IsAdjacentTo at the date extremes: memory says adjacent, PostgreSQL does not (MinValue/MaxValue are ±infinity there) #110
Description
At the extremes of a date domain, IsAdjacentTo answers true in memory and false in PostgreSQL. Npgsql writes DateOnly.MinValue / MaxValue (and NodaTime's LocalDate.MinIsoValue / MaxIsoValue) as -infinity / infinity. On the server no finite date is next to -infinity or infinity, so a range bounded at an extreme is not adjacent to the range one day further in.
var first = DateRange.CreateFinite(DateOnly.MinValue, DateOnly.MinValue); // [0001-01-01,0001-01-01]
var rest = DateRange.CreateUnboundedEnd(new DateOnly(1, 1, 2)); // [0001-01-02,)
first.IsAdjacentTo(rest); // true
var last = DateRange.CreateUnboundedEnd(DateOnly.MaxValue); // [9999-12-31,)
DateRange.CreateFinite(new DateOnly(9999, 12, 30), new DateOnly(9999, 12, 30)).IsAdjacentTo(last); // true-- what EF Core writes for the two pairs above
SELECT daterange('-infinity','-infinity','[]') -|- '[0001-01-02,)'::daterange, -- f
'[9999-12-30,9999-12-31)'::daterange -|- '[infinity,)'::daterange; -- f
SELECT daterange('-infinity','-infinity','[]'); -- [-infinity,-infinity], not emptyConfirmed against PostgreSQL 17 on main (7f4dc47). Through EF Core, column, constant and parameter, the query answers false and memory answers true.
Scope. DateRange.IsAdjacentTo, LocalDateRange.IsAdjacentTo, and RangeSet<…>.IsAdjacentTo(range | set) over both. The continuous temporal types are unaffected: [x,infinity) -|- [infinity,) is adjacent on the server, as it is in memory. Union and Merge read back equal through EF Core, because the reader turns -infinity back into MinValue and canonicalizes again. On the server they still hold a different value ({(,-infinity],[0001-01-02,)} where memory has (,)), so a server-side comparison with that result also disagrees.
Not the documented boundary. docs/architecture.md#domain-width-and-the-boundary covers the integer sentinels. docs/efcore.md says a MaxValue bound becomes infinity and that "the shape checks behave consistently". Neither says adjacency diverges at the extremes.
Fix (10.0.0). Either document the row in the domain-boundary table and pin it in DomainBoundaryDivergenceTests (a documentation change, and the sweep then sets these rows aside as it does the integer boundary), or make the two sides agree, for example by not stepping across a date extreme in the discrete adjacency check. The second changes an in-memory answer, so it is breaking. Remove the DifferentialFuzzParityTests candidates for this family in the fix's PR.
Found by the differential fuzzing sweep (#104): 1 to 2 rows per run at the default 200 rows, about 30 at 2,000.
Activity
CaffeinatedCoder commentedon Oct 17, 2026
Oracle check (main at 3ecfa10): would catch.
DifferentialFuzzParityTests(Ranges_AgreeWithPostgres) fails on rowsDateRange IsAdjacentTo at the domain edgeandDateRange> IsAdjacentTo(set) at the domain edge, e.g.[0001-01-02,) ⋄ (,0001-01-01] → memory=true, EF=false, PostgreSQL=false.Verified by a run on 3ecfa10. At the default seed, 2 lines report
SKIPPED [known candidate, #110](rows 106 and 142). It was found by this sweep, which samples the extremes, so only 1–2 rows per run hit it.CaffeinatedCoder commentedon Oct 18, 2026
Resolved in #169 (
fix/date-extremes-adjacency-divergence, stacked on #168, 10.0.0) by the first of the two offered fixes: the row is documented as a domain boundary and pinned, and the in-memory answer stays. Reason: withNpgsql.DisableDateTimeInfinityConversionsset the extremes reach the server as dates and-|-istruethere, exactly as in memory (InfinityConversionsDisabledTests.AtTheDateExtremes_AdjacencyAgrees_BecauseTheExtremesStayDates), so a model that stopped stepping across an extreme would disagree with that configuration instead. The divergence is a property of Npgsql's infinity mapping, not of the algebra; nothing insrc/changes, and thebreakinglabel is removed.What is now true:
docs/architecture.mdcarries the date rows in the boundary table,docs/efcore.mdanddocs/ranges.mdsay it in the consumer's voice (claim E44),DomainBoundaryDivergenceTestspins it as a literal, a parameter and through EF Core forDateRange(both extremes) andLocalDateRange(MaxIsoValue), andDifferentialFuzzParityTestssets an adjacency row at a date extreme aside only when it disagrees in exactly the documented shape (memorytrue, EF and PostgreSQLfalse), counting it. The two candidates are removed; without the set-aside the sweep reports them again.