Skip to content

CodoMetis.ValueRanges.Npgsql: a trimmable, Native-AOT-safe bridge for Npgsql without EF Core #47

Open

Description

@CaffeinatedCoder

Decision (maintainer, 2026-10-18)

The bridge to Npgsql gets a package of its own, CodoMetis.ValueRanges.Npgsql. It targets code that uses Npgsql without EF Core, such as hand-written ADO.NET, Dapper or Dapper.AOT, and its purpose is a fully trimmable, Native-AOT-compatible way to store and read every range, multirange and value set. Today the only way to get these types into PostgreSQL is EF Core, whose Native AOT support is experimental.

Why a new package: the issue was deferred from 9.0.0 because choosing the wrong home (core, the EF package, or a new package) costs a major to undo. A new package is the one home that can't be wrong. Core stays free of Npgsql. The EF package already depends on Npgsql, so it can depend on this package. Adding a package is additive, so it doesn't need a major.

Target: a 10.x minor (10.1.0) inside the additions window (#178), merged forward to the 11 line. Additions are frozen for 10.0.0 (#58).

Tier 1: a bridge on public, stable Npgsql API only

Conversions between this library's types and what Npgsql reads and writes natively:

  • every range union ↔ NpgsqlRange<T>
  • every RangeSet ↔ NpgsqlRange<T>[] (Npgsql's multirange representation)
  • every value set ↔ T[], with a wrapper arity converting element-wise through its primitive
  • the custom store types (timerange, float8range, float4range) by data type name on the parameter

It is AOT-safe with NpgsqlSlimDataSourceBuilder plus EnableRanges(), EnableMultiranges() and EnableArrays(). Measure the NodaTime satellite separately (Npgsql.NodaTime on the slim builder) before promising it.

The hard part is the values, not the conversion. A value arriving from PostgreSQL must enter through the library's factories, and a value written must be the one Npgsql would bind: canonical discrete bounds, the ±infinity ↔ MinValue/MaxValue mapping for dates and timestamps, microsecond truncation toward 2000-01-01, NormalizeElement and wrapper validation, and the finite-only guard for the float family (#153). The EF package already implements all of this, internally, in RangeProviderConversion, StoreTemporal/NodaStoreTemporal, StoreText and the type definitions. Those rules move into this package, and the EF package consumes them. Two copies would drift, which is exactly what the clone sweep exists to catch (#200). Before moving anything, check that the code depends only on Npgsql, not on EF.

What a consumer loses compared with EF is LINQ translation. SQL is written with PostgreSQL's operators. The docs give the operator for each member; test/oracles/postgres-surface.json already holds that mapping.

Tier 2: direct reads and writes, later

reader.GetFieldValue<DateRange>() and new NpgsqlParameter<DateRange>(…) need a type info resolver: IPgTypeInfoResolver / PgConverter<T> in Npgsql.Internal. That API is experimental (DynamicTypeInfoResolver is [Experimental("NPG9001")] and [RequiresDynamicCode]). A resolver over the closed, concrete types avoids the dynamic-code requirement, so it can stay AOT-safe. It still ties the package to one Npgsql major, so the 10.x and 11.x lines each need their own build. Decide after tier 1 has shipped and been used.

Not this issue

#209 is the EF Core side of AOT: compiled models generated with dotnet ef dbcontext optimize --nativeaot need a public static Default on each plugin type mapping. It is independent of this package. This package doesn't need #209, and #209 doesn't give a non-EF path.

Done when (tier 1)

  • the package ships beside the other four, discovered by the conventions and packaging tests without edits
  • the store rules live in it once, and the EF package uses them (TemporalStoreParityTests still passes)
  • an integration test round-trips every discovered range, multirange and value set type through raw Npgsql on PostgreSQL 16 and 18, with parity against the EF path
  • a Native AOT consumer in test/consumer-smoke-test.sh publishes and runs a real query through the slim data source builder. That consumer is the only proof of AOT safety: seed a defect there, not in a unit test
  • docs/ has the page (plain-docs), with claims in claims.json

Labels: there is no package/* label for the new package yet. Add one when the package is created, rather than inventing it now.

Activity

  1. added this to the 9.1.0 milestone on Oct 17, 2026
  2. removed this from the 9.1.0 milestone on Oct 17, 2026
  3. CaffeinatedCoder commented on Oct 17, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Moved off 9.1.0 into the backlog (#83), with no milestone.

    release/9.x has been cut, and 9.1.0 ships with the work that's already finished. Nothing has started on this issue, and it shouldn't hold up the tag. This is an addition, so the 10.0.0 epic (#58) doesn't take it either: that epic is kept to the silent and breaking fixes so they aren't held up behind new features.

    When someone picks it up, it goes under the epic of the release it will ship in and takes that milestone. A 9.x minor would need the change cherry-picked onto release/9.x.

  4. changed the title [-]Decide on a public NpgsqlRange<T> bridge[/-] [+]CodoMetis.ValueRanges.Npgsql: a trimmable, Native-AOT-safe bridge for Npgsql without EF Core[/+] on Oct 18, 2026
  5. CaffeinatedCoder commented on Oct 18, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Rewritten 2026-10-18 from 'decide on a public NpgsqlRange bridge' into the package design, after the maintainer chose a separate package. It stays in the backlog (#83) because no 10.1.0 epic exists yet; it moves under that epic, with its milestone, when 10.1.0 is opened. The EF-side AOT path is #209, which is separate.

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/aotNative AOT and trimmingarea/range-setRangeSet and multirange algebraarea/rangesRange unions, variants and the shape enginesarea/value-setsValue set families and wrapper aritiesenhancementNew feature or requestpackage/coreAffects CodoMetis.ValueRangespackage/efcoreAffects CodoMetis.ValueRanges.EFCore.PostgreSQL

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions