Repository navigation
fix: declare lower() and upper() NULL for a non-null range (10.x, port of #349) - #367
Merged
CaffeinatedCoder merged 2 commits intoOct 24, 2026
Merged
Conversation
Two sweeps in ExecutedQueryTests now end in Candidates.Verdict, compared with LINQ over the rows as loaded. BoundsComparedWithNull runs LowerBound() and UpperBound() against null, HasValue, ?? in a filter and a projection, and a filter on a projected bound over nine shapes in thirteen range columns; all 117 (operation, column) pairs disagree today, one candidate per operation under nullable DateRange column holding NULL and every shape, one line per row, so only the NULL row is quarantined; one candidate per operation under #350. E70 quotes the bound-null sentence of docs/efcore.md, held by ExecutedQueryTests. With argumentsPropagateNullability: [false] in BoundFunction applied locally, the nine #349 candidates report candidate fixed and nothing else fails; the fix is a separate PR. Closes #351 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 2c44472)
BoundFunction declared lower() and upper() with argumentsPropagateNullability [true], which tells EF Core the bound is NULL only when the column is. PostgreSQL also returns NULL for the empty range and for an unbounded side, so EF Core folded LowerBound() == null on a required column to WHERE FALSE, dropped the COALESCE of a ??, and tested a nullable column's bound with the column's own IS NULL. The flag is now [false] for both, on ranges and on RangeSet. The bound sweep in ExecutedQueryTests now runs every operation on the RangeSet column of each family too (13 + 13 columns), and its nine #349 candidates are removed. With the old flag it fails on 234 rows, 117 per kind; with the new one every row agrees. Two translation tests pin the SQL without a database. Claim E70 covers RangeSet's bounds and quotes the efcore.md sentence that now promises the query answer. Found-By: US Defect-Class: SQL Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 89a7a38)
CaffeinatedCoder
enabled auto-merge
October 24, 2026 11:46
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 #354
The 10.x port of #349 for 10.2.0 (#358):
git cherry-pick -xof #352's commit (2c44472) and #353's fix commit (89a7a38), both merged intomain. Againstrelease/10.xthe closing keyword closes nothing, so #354 is closed by hand after the merge.Problem
10.1.0 declares
lower()andupper()withargumentsPropagateNullability: [true], so EF Core takes a bound as NULL only when its column is. PostgreSQL also returns NULL for the empty range and for an unbounded side, as the in-memoryLowerBound()andUpperBound()returnnull. Over a required column:LowerBound() == nullbecameWHERE FALSE;!= nullandHasValuefiltered nothing;?? fallbacklost itsCOALESCE, so open-ended rows fell out of a filter and a projection threw.On a nullable column, the bound was tested with the column's own
IS NULL. This affects every range type andRangeSet.Change
main: the flag becomes[false]inBoundFunction.ExecutedQueryTestsgets the bound sweep, over the range andRangeSetcolumns of all 13 families, and the nullable-column sweep for A null range answers some extensions in memory and throws on others, and the answers it gives disagree with PostgreSQL on a NULL column #350.QueryTranslationTestsgets the two SQL pins. Claim E70 is added, and the nine LowerBound() and UpperBound() in a query: == null becomes WHERE FALSE and ?? max loses its COALESCE, because lower() and upper() are declared NULL only for a NULL argument #349 candidates are added and then removed, leaving A null range answers some extensions in memory and throws on others, and the answers it gives disagree with PostgreSQL on a NULL column #350's twelve.claims.json: only E70 is taken.main's E64–E69 are 11-line claims (Npgsql 11's temporal keys) and stay out.main's### Breakingline under[11.0.0-preview.1]becomes a### Changed answersrow under[10.2.0], in the root, EF Core and EF Core NodaTime changelogs. It links 10.x: LowerBound() and UpperBound() in a query: == null becomes WHERE FALSE and ?? loses its COALESCE (port of #349) #354 anddocs/migration.md#changed-answers-in-1020. The root section opens with a line pointing there.docs/migration.md:main's## Migration from v10.xbecomes## Changed answers in 10.2.0, with the same subsection,### Check bound comparisons with null in queries, where "Before 11.0.0" reads "Before 10.2.0". When 10.2.0 is carried tomain(Release 10.2.0: date the changelogs, tag v10.2.0, close the 10.x-only issues, carry the sections to main #365), that subsection meets its twin; Release 10.2.0: date the changelogs, tag v10.2.0, close the 10.x-only issues, carry the sections to main #365 says how to reconcile them.docs/efcore.md: the sentence that claim E70 quotes.Tests
Local, SDK 10.0.402, Docker:
postgres:16-alpine: all eight projects pass (core 1397, NodaTime 238, EF 305, EF NodaTime 91, ASP.NET Core 92, conventions 406, integration 282, infinity-disabled 4).ExecutedQueryTestsonpostgres:18-alpine: 15/15.-p:EfFloat=10.0.13 -p:NpgsqlEfFloat=10.0.3, versions confirmed indeps.json): translation tests 305/305,ExecutedQueryTests15/15.[true]): the bound sweep fails on 234 rows (117 range, 117RangeSet), none a known candidate, and both translation pins fail. Restored afterwards.pr-hygiene.shon a simulated event withsilent: "Labels and issue link are in order", so the[10.2.0]### Changed answersrow links 10.x: LowerBound() and UpperBound() in a query: == null becomes WHERE FALSE and ?? loses its COALESCE (port of #349) #354.🤖 Generated with Claude Code