Skip to content

fix: read a YearMonthRange upper bound in a query as the first day of its month (#511, port of #506) - #515

Merged
CaffeinatedCoder merged 5 commits into
release/10.xfrom
fix/511-yearmonth-upper-bound-10.x
Oct 26, 2026
Merged

CaffeinatedCoder merged 5 commits into
release/10.xfrom
fix/511-yearmonth-upper-bound-10.x

Conversation

@CaffeinatedCoder

Copy link
Copy Markdown
Owner

Closes #511

Port of #509 (#506) to release/10.x. The fix, test and seed commits are cherry-picked with -x and applied without conflicts. The docs commit is re-made for 10.x's layout.

Problem

In a query, a YearMonthRange upper bound read as the last day of its month. It loaded as the right month, but in a filter UpperBound() == month was false and > month was true for a range ending in that month. Clamp (which runs as SQL from 10.4.0) and RangeSet.UpperBound() read the same helper.

Change

As #509: CoarseUpperBound, YearMonth's ("1 month", 9999-12), and CoarseUpper writing CASE WHEN NOT isfinite(upper(x)) THEN DATE '9999-12-01' ELSE CAST(upper(x) - INTERVAL '1 month' AS date) END.

Tests

On release/10.x, EF Core 10.0.0 floor:

  • Full suite green on PostgreSQL 16: unit, conventions, ASP.NET Core, both EF translation projects and both integration projects. That includes the new YearMonthBoundParityTests, the ClampDomainParityTests comparisons and YearMonthUpperBoundTests with the infinity conversions off.
  • oracles.cs seeds yearmonth-upper: both seeds caught, the day step by both oracles and the dropped infinity arm by YearMonthBoundParityTests.
  • Newest-patch leg (-p:EfFloat=10.0.13 -p:NpgsqlEfFloat=10.0.3): the three YearMonth and Clamp classes, 30/30.
  • PostgreSQL 18: ClampDomainParityTests and YearMonthBoundParityTests, 14/14.

🤖 Generated with Claude Code

CaffeinatedCoder and others added 4 commits October 26, 2026 18:15
… its month (#506)

A YearMonthRange is a month-aligned daterange, and a YearMonth operand
binds as the first day of its month. UpperBound() read upper(x) - 1, the
last day of the last month: it loaded as that month, but in a filter it
compared unequal to it, and greater. UpperBound() == january and
Clamp(february) == january were false for [2026-01,2026-01], where memory
gives true; RangeSet.UpperBound() and Clamp read the same helper.

A definition can name a CoarseUpperBound: the span of one element as an
interval, and the element an inclusive infinity stands for. YearMonthRange
steps back a month, CAST(upper(x) - INTERVAL '1 month' AS date), and
answers 9999-12-01 when the bound is infinity, which no interval steps
back from (9999-12 ends on the date Npgsql writes as infinity). With the
infinity conversions disabled the bound is a finite 10000-01-01 and the
interval reaches 9999-12-01 itself.

ClampDomainParityTests compares Clamp(@p) with every point by ==, < and >
in a filter on all thirteen range types; YearMonthBoundParityTests does
the same for LowerBound() and UpperBound() of the range and its set. With
the dispatch removed both fail on YearMonthRange (420 Clamp
disagreements), and the other twelve types agree.

Found-By: RV
Defect-Class: SQL

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 2ca9c86)
… bound (#506)

Two seeds: the bound stepping back a day (both oracles catch it), and the
infinity arm dropped, which only the bound readers reach, since no month
lies above 9999-12 for Clamp to clamp to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 615639a)
…ry, with the migration and changelog (#511)

Store a YearMonthRange says the query reads the first day of the end
month and compares with a month in a filter as memory does, 9999-12
included; the discrete-bounds note points there; architecture.md says how
(CoarseUpperBound). A section under Changed answers in 10.4.0, the
Changed answers rows of 10.4.0 in the root and EF Core NodaTime
changelogs linking #511, and claim E88.

(cherry picked from commit cd76720, with the 10.x changelog and migration layout)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nfinity conversions off (#506)

9999-12 ends on 9999-12-31, which Npgsql then sends as a date, so the
server keeps [...,10000-01-01): a month back reaches 9999-12-01 without
the infinity arm. Fails with the step reverted to a day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 989d54d)
(cherry picked from commit 03bdb40f155b61bdf7f339f05702da35ccf2d661)
@CaffeinatedCoder CaffeinatedCoder added this to the 10.4.0 milestone Oct 26, 2026
@CaffeinatedCoder CaffeinatedCoder added bug Something isn't working silent Answers wrongly without throwing — fixes go in the next major, never deferred area/ranges Range unions, variants and the shape engines area/range-set RangeSet and multirange algebra package/efcore Affects CodoMetis.ValueRanges.EFCore.PostgreSQL package/efcore-nodatime Affects CodoMetis.ValueRanges.EFCore.PostgreSQL.NodaTime severity/medium Inconsistency or failure in a narrow scenario labels Oct 26, 2026
@CaffeinatedCoder
CaffeinatedCoder merged commit 43809c2 into release/10.x Oct 26, 2026
10 checks passed
@CaffeinatedCoder
CaffeinatedCoder deleted the fix/511-yearmonth-upper-bound-10.x branch October 26, 2026 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/range-set RangeSet and multirange algebra area/ranges Range unions, variants and the shape engines bug Something isn't working package/efcore Affects CodoMetis.ValueRanges.EFCore.PostgreSQL package/efcore-nodatime Affects CodoMetis.ValueRanges.EFCore.PostgreSQL.NodaTime severity/medium Inconsistency or failure in a narrow scenario silent Answers wrongly without throwing — fixes go in the next major, never deferred

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant