Skip to content

docs: the in-memory reasons overstate what PostgreSQL lacks, and the Shift/Expand one is stale since #224 #402

Closed

Description

@CaffeinatedCoder

Problem

docs/efcore.md#what-runs-where and the pages and XML comments that repeat it explain each in-memory operation by what PostgreSQL lacks. Several of those reasons are wrong or stale, and the opening sentence implies that no dedicated operator means no translation, which Gap and Gaps (composed SQL) already contradict. The decisions stay; their explanations are what's wrong. Measured 2026-10-25 on PostgreSQL 16 (feasibility only; none of these is translated):

Operation Stated reason What holds
Shift, Expand "an infinity date absorbs the step where DateOnly.MaxValue overflows" Stale since #224: a bound standing for infinity stays there, in memory as in PostgreSQL (InfinityShiftRoundTripTests). Still true: empty becomes (,) when rebuilt, time wraps (23:00 + 2 h = 01:00) where TimeRange throws, and a bound moved past .NET's last date throws in memory where PostgreSQL returns 10000-01-04. The daylight-saving example holds only for interval '1 day'; an exact TimeSpan would be interval '24 hours'.
Length "int4range overflows int4 before a cast can widen it" Casting first works: upper(r)::int8 - lower(r)::int8 gives 4,294,967,295 for [-2147483648,2147483647), and a CASE gives the empty range 0. The real obstacle is temporal: a range open to infinity fails with cannot subtract infinite dates where memory returns a finite length.
Values(), Split "generate_series returns rows, not a value" ARRAY(SELECT generate_series(…)) is one value ({1,2,3}). What's left is size and EF Core's support for a collection result.
Bridge conversions "only through unnest and a custom aggregate" Built-in range_agg over one-element ranges gives {[1,4),[7,8)} for {1,2,3,7}; an element type's extreme needs care ([2147483647,2147483647] can't be built as int4range). The reverse direction is enumeration.
Clamp "would need a CASE per shape" LEAST(GREATEST(v, lower(r)), upper(r)) leaves an unbounded side alone because both skip NULL. It needs one branch for empty, and a step for a discrete type's exclusive upper bound.
Value set Intersect, Except "the array type has no intersection, difference" Filtering the left array with unnest … WITH ORDINALITY keeps its order ({a,e}). It's canonical only when the stored arrays are, like Union.
Value set Add "no sorted insert" True, and the real reason is stronger: inserting in order means sorting in SQL, where text follows the database collation, not ordinal.
Value set indexer "canonical order is the CLR comparer's, not the server's" Subscripting reads the stored order and sorts nothing. What differs: a row another tool wrote may not be canonical, arrays start at 1, and an index past the end is NULL where memory throws.
RangeSet Count and indexer (added in #399) "need unnest, which returns a set of rows" A scalar subquery over unnest(x) works (2, [5,7)).

Two promises are broader than EF Core's behaviour:

  • "Inside a Select it is safe": EF Core evaluates on the client only in the final projection; a Select that a later Where or OrderBy reads from must translate.
  • "You can use these in Where, OrderBy and Select": a value set Union composes only under some operators (ValueSetSqlGuard).

Change

Every place that gives one of these reasons says what holds:

  • docs/efcore.md#what-runs-where, with an opening that separates "not translated, composed SQL would do" from "SQL would answer differently";
  • the EF Core and EF Core NodaTime READMEs, the core README, getting-started.md, value-sets.md, ranges.md and testing.md;
  • AGENTS.md;
  • the XML comments on Split, Values, Shift and Expand of every range type, SetRangeBridge and ValueSetRangeExtensions.

Claim E31's quote follows the narrowed sentence. main first, stacked on PR #400; then the 10.x cherry-pick, stacked on PR #401.

Release

Docs and comments only. A Docs: row under ### Fixed in the release it ships in.

Activity

  1. added this to the 10.2.0 milestone on Oct 25, 2026
  2. added
    documentationImprovements or additions to documentation
    area/rangesRange unions, variants and the shape engines
    area/range-setRangeSet and multirange algebra
    area/value-setsValue set families and wrapper arities
    package/nodatimeAffects CodoMetis.ValueRanges.NodaTime
    package/efcoreAffects CodoMetis.ValueRanges.EFCore.PostgreSQL
    package/coreAffects CodoMetis.ValueRanges
    package/efcore-nodatimeAffects CodoMetis.ValueRanges.EFCore.PostgreSQL.NodaTime
    on Oct 25, 2026
  3. added 2 commits that reference this issue on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/range-setRangeSet and multirange algebraarea/rangesRange unions, variants and the shape enginesarea/value-setsValue set families and wrapper aritiesdocumentationImprovements or additions to documentationpackage/coreAffects CodoMetis.ValueRangespackage/efcoreAffects CodoMetis.ValueRanges.EFCore.PostgreSQLpackage/efcore-nodatimeAffects CodoMetis.ValueRanges.EFCore.PostgreSQL.NodaTimepackage/nodatimeAffects CodoMetis.ValueRanges.NodaTime

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions