Repository navigation
fix: read a YearMonthRange upper bound in a query as the first day of its month (#506) - #509
Merged
Merged
Conversation
… 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>
… 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>
…ry, with the migration and changelog (#506) 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). The v10.x migration list, a section with the before and after table, the Breaking lines of 11.0.0-preview.1 in the root and EF Core NodaTime changelogs, and claim E88. 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>
This was referenced Oct 26, 2026
10.x: read a YearMonthRange upper bound in a query as the first day of its month (port of #506)
#511
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #506
Problem
A
YearMonthRangeis stored as a month-aligneddaterange, and aYearMonthyou pass in is sent as the first day of its month. In a query,UpperBound()readupper(x) - 1, the last day of the last month. That loads as the right month, so projections agreed with memory and the tests that project the bound passed. In a filter it compared unequal to the month, and greater:[2026-01,2026-01]UpperBound() == 2026-01UpperBound() > 2026-01Clamp(2026-02) == 2026-01RangeSet<YearMonthRange, YearMonth>.UpperBound()andClampread the same helper.ClamponYearMonthRangeruns as SQL since #486 (10.4.0, 11.0.0-preview.1), andUpperBound()since the type's EF mapping.Change
CoarseUpperBound(new, internal) andIRangeTypeDefinition.CoarseUpperBound. It holds the span of one element as an interval, and the element an inclusiveinfinitystands for.YearMonthRangeTypeDefinitionsets("1 month", 9999-12).ValueRangesMethodCallTranslator.UpperBoundFunction: on such a type,CoarseUpperwritesCASE WHEN NOT isfinite(upper(x)) THEN DATE '9999-12-01' ELSE CAST(upper(x) - INTERVAL '1 month' AS date) END. 9999-12 ends onLocalDate.MaxIsoValue, which Npgsql writes asinfinity, and no interval steps back from that. WithNpgsql.DisableDateTimeInfinityConversionsthe bound is a finite 10000-01-01, and the interval reaches 9999-12-01 on its own. Other types produce the same SQL as before.YearMonthRangeupper bounds compared in queries. A### Breakingline in 11.0.0-preview.1 of the root and EF Core NodaTime changelogs.YearMonthBoundParityTestsis registered as a differential oracle, with two seeds.It changes answers, so it's labelled
silent. On 10.x it ships in 10.4.0 under### Changed answers(port issue to follow).Tests
ClampDomainParityTestsnow comparesClamp(@p)with every point by==,<and>in a filter, on all 13 range types. Before, it compared with the same point only, which never reaches the clamped bound. With the fix reverted,YearMonthRangehas 420 disagreements, and the other twelve types agree.YearMonthBoundParityTests(new, integration):LowerBound()andUpperBound()of the range and itsRangeSet, over every shape between five months (9999-12 among them), compared with each month by==,<and>in a filter against memory over the loaded rows.YearMonthUpperBoundTests(infinity-disabled project):UpperBound() == monthat 2026-01, 9999-11 and 9999-12, with the conversions off.NodaQueryTranslationTests: the two asserts on the oldupper(x) - 1now assert the new SQL.oracles.cs seeds yearmonth-uppercatches both seeds: the day step in both oracles, and the droppedinfinityarm inYearMonthBoundParityTests.Clampcan't reach that arm, because no month lies above 9999-12.🤖 Generated with Claude Code