Repository navigation
fix: declare lower() and upper() NULL for a non-null range - #353
Merged
Merged
Conversation
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>
This was referenced Oct 24, 2026
Open
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 #349
Stacked on #352, which registers the disagreement as candidates. This PR removes the nine #349 entries, so merge #352 first; GitHub then retargets this PR to
main.Problem
BoundFunctiondeclaredlower()andupper()withargumentsPropagateNullability: [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, as the in-memoryLowerBound()andUpperBound()returnnull. So over a required column EF Core foldedLowerBound() == nulltoWHERE FALSE, turned!= nullandHasValueinto no filter, dropped theCOALESCEof?? fallback(open-ended rows fell out of a filter, and a projection threwNullable object must have a value), and on a nullable column tested the bound with the column's ownIS NULL. Every range type andRangeSetwent through the same helper.Change
ValueRangesMethodCallTranslator.BoundFunction:argumentsPropagateNullability: [false], keepingnullable: true. That coverslowerandupperon ranges and onRangeSet, and the discreteupper(x) - 1, which EF Core still reduces toupper(x) IS NULLwhen it is compared with null.ExecutedQueryTests.BoundsComparedWithNull_SelectTheRowsMemorySelects: each family now seeds every shape into its range column and, as a one-element multirange, into itsRangeSetcolumn (Seats/SeatBlocks, …,BillingPeriod/BillingPeriods), and runs all nine operations on both. The operations are written once over the bound and spliced ontox.LowerBound()andx.UpperBound()of each column.QueryTranslationTests: two tests pin the SQL without a database:lower(b."Period") IS NULLandupper(b."BlockedDays") IS NULLwith noFALSE, andCOALESCE(upper(b."Period") - 1, …).test/oracles/candidates.json: 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 entries removed, 16 left. 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 stay.test/oracles/claims.json: E70 now namesRangeSet.LowerBound/UpperBound, says "range or multirange column", and quotes the newdocs/efcore.mdsentence below.docs/efcore.md: "So== null,HasValueand??on a bound select the same rows in a query as in memory, on a range or aRangeSetcolumn."docs/migration.md: a new## Migration from v10.xsection with### Check bound comparisons with null in queries(what changed per query form, and how to find the calls).### Breakingline under[11.0.0-preview.1]. A silent fix into a major's preview goes under Breaking (ChangedAnswers_AppearOnlyInAMinor); the 10.x port gets the### Changed answersrow of its minor.Tests
All local, SDK
11.0.100-rc.2.26504.105, Docker via OrbStack.candidate fixed: remove the entry …lines and no other row, the newRangeSetrows included.PredicatesOverANullableColumnstill skips 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).[true]back): the bound sweep fails on 234 rows, 117 on the range columns and 117 on theRangeSetcolumns, none of them a known candidate; for exampleLowerBound() == null on SeatBlocks (RangeSet<Int32Range>): server rows {}, memory rows {(,a] (,b] (,) empty}. Both new translation tests fail too. Restored afterwards.postgres:16-alpine: all eight test projects pass (core 1397, NodaTime 238, EF 293, EF NodaTime 83, ASP.NET Core 92, conventions 407, integration 286, infinity-disabled 4).ExecutedQueryTestsonpostgres:18-alpine: 15/15.Not covered here: the
release/10.xport, which needs a 10.x minor with a### Changed answersrow; its own issue is filed separately.🤖 Generated with Claude Code