Skip to content

Translate ToRangeSet() from Int32Set, Int64Set, DateSet, LocalDateSet and YearMonthSet (split from #407) #487

Open

Description

@CaffeinatedCoder

Problem

ToRangeSet() on Int32Set, Int64Set, DateSet, LocalDateSet and YearMonthSet runs in memory only. #407 holds both bridge directions together with enumeration, but the two directions aren't alike:

  • Multirange to set has to enumerate. Its input may be unbounded, and it yields one element per step.
  • Set to multirange doesn't. Its input is finite, and its output is never larger.

Translating this direction also makes cross-family questions translate through operators that already run as SQL: b.Dates.ToRangeSet().IsContainedBy(b.Stay) ("are all the booked days inside the stay?") or b.Dates.ToRangeSet().Overlaps(closedPeriods).

Measured (PostgreSQL 16.15 and 18.1)

  • (SELECT range_agg(int4range(x, x, '[]')) FROM unnest('{1,2,3,5}'::int[]) x) is {[1,4),[5,6)}.
  • int.MaxValue fails: integer out of range, because the canonical form needs x + 1. long.MaxValue is the same case.
  • The empty set gives NULL (range_agg over no rows), where memory gives RangeSet.Empty. It needs COALESCE(…, '{}').
  • A NULL element in a row another tool wrote gives int4range(NULL, NULL, '[]'), which is (,). So {1,NULL} bridges to {(,)}, the whole domain. It must be filtered with WHERE x IS NOT NULL, as Translate value set Intersect, Except, Add and a canonical Union for the non-text families with composed SQL #478's canonical form does.
  • DateOnly.MaxValue, which Npgsql writes as infinity, aggregates to [infinity,infinity]; 9999-12-31 itself gives [9999-12-31,10000-01-01). Both modes of Npgsql's infinity conversions need a test.
  • YearMonthSet is stored as month-aligned dates, so its one-element range spans a month, not a day. Not measured yet.

Change

Translate as COALESCE((SELECT range_agg(<range>(vrN.x, vrN.x, '[]')) FROM unnest(set) AS vrN(x) WHERE vrN.x IS NOT NULL), '{}'), inside #478's NULL-over-NULL wrapper. Build it after EF Core's alias pass, as #478 builds its expressions, behind a placeholder that fails closed. At int.MaxValue and long.MaxValue, compare with what memory returns and what storing that result does (the domain-boundary table in docs/architecture.md). Then decide whether the server error is the documented answer.

Does it change an answer?

No. A predicate that throws today gets the in-memory answer.

Done when

  • ToRangeSet() translates for all five set types, core and NodaTime
  • a live test on PostgreSQL 16 and 18 agrees with memory, including the empty set, a NULL column, a NULL element written by another tool, and the extremes in both infinity modes
  • docs/efcore.md#what-runs-where moves this direction out of the bridge row, and Translate enumeration and the bridge conversions — a batch after 10.3.0 #407 narrows to enumeration and the reverse bridge

Split out of #407.

Activity

  1. added
    enhancementNew feature or request
    area/range-setRangeSet and multirange algebra
    area/value-setsValue set families and wrapper arities
    package/efcoreAffects CodoMetis.ValueRanges.EFCore.PostgreSQL
    package/efcore-nodatimeAffects CodoMetis.ValueRanges.EFCore.PostgreSQL.NodaTime
    on Oct 26, 2026
  2. CaffeinatedCoder commented on Oct 26, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Deferred to 10.5.0 (decided 2026-10-26). It only adds a translation and changes no answer, so it doesn't hold up the 10.4.0 tag. It stays under the backlog epic #83 until the 10.5.0 epic is opened after v10.4.0.

  3. added this to the 10.5.0 milestone on Oct 26, 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/value-setsValue set families and wrapper aritiesenhancementNew feature or requestpackage/efcoreAffects CodoMetis.ValueRanges.EFCore.PostgreSQLpackage/efcore-nodatimeAffects CodoMetis.ValueRanges.EFCore.PostgreSQL.NodaTime

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions