Skip to content

Translate Clamp on Int32Range, Int64Range and DecimalRange #406

Closed

Description

@CaffeinatedCoder

Problem

Clamp(value) on a range runs in memory only (docs/efcore.md#what-runs-where). The obstacle the docs gave until #402 ("a CASE per shape") is overstated: GREATEST and LEAST skip NULL, so an unbounded side needs no branch.

Change

Translate Clamp on Int32Range, Int64Range and DecimalRange, matching RangeExtensions.Clamp:

  • Memory's rule: null for the empty range; the value itself inside; otherwise the finite bound it falls outside of, whatever that bound's inclusiveness (on a continuous type an exclusive bound is returned although the range doesn't contain it). An unbounded side never constrains. The value is first passed through NormalizeElement.
  • SQL: CASE WHEN isempty(x) THEN NULL ELSE LEAST(GREATEST(v, lower(x)), <upper>) END. <upper> is upper(x) for numrange. For int4range/int8range it is upper(x) - 1, because PostgreSQL canonicalizes to an exclusive upper bound where memory's End is inclusive. upper(x) - 1 can't overflow, since the canonical exclusive bound is at least one above a real value.
  • Measured on PostgreSQL 16: LEAST(GREATEST(15, lower('(,10]')), upper('(,10]') - 1) is 10.
  • To check first: DecimalRange's NormalizeElement (scale) against what numeric returns, since Same compares the loaded decimal.
  • Declared nullability: NULL for a NULL range or a NULL value (rule (c)).

Done when

Every condition on the 10.3.0 epic's parity list holds for the three types, against memory over the loaded rows on a live server. translation-support.json classifies them server (the other range types stay client with their reason), and the docs list the three under "Runs as SQL". main first, then the cherry-pick.

Activity

  1. added this to the 10.3.0 milestone on Oct 25, 2026
  2. added
    enhancementNew feature or request
    area/rangesRange unions, variants and the shape engines
    package/efcoreAffects CodoMetis.ValueRanges.EFCore.PostgreSQL
    on Oct 25, 2026
  3. CaffeinatedCoder commented on Oct 25, 2026

    @CaffeinatedCoder
    OwnerAuthor

    PR #440 on main. Measured on PostgreSQL 16 before writing it, and the issue's "to check first" item is resolved:

    • DecimalRange keeps the default NormalizeElement (the identity), so memory sees the value as given.
    • numeric returns the stored scale (2.500), which decimal equality ignores.

    A NULL range or value needs its own test, because GREATEST/LEAST skip NULL and would otherwise answer with the other operand. ClampParityTests covers #408's parity list for these three types. The composition sweeps run over DateRange, whose Clamp stays in memory. The 10.x cherry-pick follows the merge.

  4. added a commit that references this issue on Oct 25, 2026
  5. CaffeinatedCoder commented on Oct 25, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Runs as SQL on main since PR #440 (84dc121). The release/10.x cherry-pick is tracked separately (port issue under #408).

  6. 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/rangesRange unions, variants and the shape enginesenhancementNew feature or requestpackage/efcoreAffects CodoMetis.ValueRanges.EFCore.PostgreSQL

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions