PetToys.BigDecimal.Npgsql
1.0.1
Prefix Reserved
dotnet add package PetToys.BigDecimal.Npgsql --version 1.0.1
NuGet\Install-Package PetToys.BigDecimal.Npgsql -Version 1.0.1
<PackageReference Include="PetToys.BigDecimal.Npgsql" Version="1.0.1" />
<PackageVersion Include="PetToys.BigDecimal.Npgsql" Version="1.0.1" />
<PackageReference Include="PetToys.BigDecimal.Npgsql" />
paket add PetToys.BigDecimal.Npgsql --version 1.0.1
#r "nuget: PetToys.BigDecimal.Npgsql, 1.0.1"
#:package PetToys.BigDecimal.Npgsql@1.0.1
#addin nuget:?package=PetToys.BigDecimal.Npgsql&version=1.0.1
#tool nuget:?package=PetToys.BigDecimal.Npgsql&version=1.0.1
PetToys.BigDecimal.Npgsql
PostgreSQL helpers for PetToys.BigDecimal.Core: they map the
arbitrary numeric values that PostgreSQL allows - and that decimal cannot
hold - onto BigDecimal when reading and writing through Npgsql.
Bring your own configured NpgsqlConnection or data source; this package only
deals with the value mapping.
Installation
dotnet add package PetToys.BigDecimal.Npgsql
The core type's own package, PetToys.BigDecimal.Core, comes along
as a dependency.
Releases go to nuget.org, so the command above is all that is needed. Prereleases are published to GitHub Packages instead: that feed has to be added to your
nuget.config, and it requires a personal access token withread:packageseven for a public package.
Usage
Register the mapping once, on the data source:
using Npgsql;
using PetToys.BigDecimal.Numerics;
await using var source = new NpgsqlDataSourceBuilder(connectionString)
.UseBigDecimal()
.Build();
Then ask for the type where you want it:
await using var command = source.CreateCommand(
"SELECT total FROM invoices WHERE id = $1");
command.Parameters.Add(new NpgsqlParameter { Value = invoiceId });
await using var reader = await command.ExecuteReaderAsync();
await reader.ReadAsync();
BigDecimal total = reader.GetBigDecimal("total");
Writing needs nothing but the value:
command.Parameters.Add(new NpgsqlParameter { Value = total });
numeric[] maps as well, to BigDecimal[], List<BigDecimal> and their
nullable forms; and a binary COPY import goes through the same mapping, which
is the path to use for more than a handful of rows.
UseBigDecimal is also an extension on INpgsqlTypeMapper, so it works from a
configuration callback and on NpgsqlSlimDataSourceBuilder.
Registering it does not change anything else
BigDecimal is for the columns that need it, not a replacement for
System.Decimal across an application, and the registration is built that way.
A numeric column read with GetValue is still a decimal, GetFieldType
still reports decimal, and a DataTable still fills with decimal. Code
written before this package was referenced reads exactly what it read before.
BigDecimal is reached by asking for it, per read.
What round-trips, and what does not
| Column | Coverage |
|---|---|
numeric(p, s), p up to 77 |
Lossless. This covers numeric(38, 18), the common money and blockchain precision, with room to spare. |
numeric unconstrained, integer part within the magnitude |
Accepted; fractional digits beyond what the magnitude leaves are rounded half to even. PostgreSQL allows 16383 of them, so a value read from such a column can lose digits silently. |
numeric unconstrained, integer part beyond the magnitude |
OverflowException. PostgreSQL allows 131072 integer digits. |
NaN, Infinity, -Infinity |
Lossless, as BigDecimal.NaN, BigDecimal.PositiveInfinity and BigDecimal.NegativeInfinity. PostgreSQL sorts NaN above every other numeric value where this type sorts it below every other value; both make NaN equal to itself. |
The type's own bounds are 77 significant digits, a largest magnitude of 2^256-1, and a range of 1e-255 to approximately 1.157e77.
Only one direction can fail. Every value of the type fits a numeric, so
writing never overflows and never rounds. Reading is the direction with a
boundary, and GetBigDecimal names the column when a value crosses it:
Column 'total' at ordinal 0 holds a numeric whose integer part is larger than
BigDecimal can represent: ...
That naming is what the accessors are for. Reading the same column through
GetFieldValue<BigDecimal> or through Dapper gets the same mapping and the same
OverflowException, without the column name.
Through Entity Framework Core, add
PetToys.BigDecimal.Npgsql.EntityFrameworkCore: it maps a
numeric column to a BigDecimal property over this package's registration,
and the value the caller can name there is the property, read off the
DbUpdateException Entity Framework raises.
Requirements
- Npgsql 10.0.3, up to but not including 11. The mapping registers through
Npgsql.Internal, which is the driver's only extension point for a new type and is published as experimental, so the floor is the version this package was built against and the range is closed at the major. A driver major that reshapes the extension point therefore fails at restore rather than at the first read; the ceiling moves once the new major has been tested against. - Trimmed and Native AOT publishing both work. This assembly is marked
IsAotCompatible, andNpgsql10.0.3 is itself marked trimmable, so the closure a publish trims carries no unmarked assembly. Both forms are verified by publishing a probe over this package and running it against a real server, not by a clean analyzer pass. Under Native AOT, build the data source withNpgsqlSlimDataSourceBuilder, which is Npgsql's own route for that case and whichUseBigDecimaloverloads; the slim builder starts with nothing enabled, so anumeric[]needsEnableArrays()on it. A trimmed build can use either builder. Serializing aBigDecimalto JSON in such an application needs a source-generated context, which is aSystem.Text.Jsonrule rather than one of this package's; the core package's README carries the detail. - PostgreSQL 14 or later for the infinities. That is where
numericgained the sign codes that carry them. Everything else works on any supported server; against an older one, writing an infinity is refused by the server itself and this package does not emulate or substitute anything.
Links
License
Provided under the Apache License, Version 2.0.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Npgsql (>= 10.0.3 && < 11.0.0)
- PetToys.BigDecimal.Core (>= 1.0.1)
-
net8.0
- Npgsql (>= 10.0.3 && < 11.0.0)
- PetToys.BigDecimal.Core (>= 1.0.1)
-
net9.0
- Npgsql (>= 10.0.3 && < 11.0.0)
- PetToys.BigDecimal.Core (>= 1.0.1)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on PetToys.BigDecimal.Npgsql:
| Package | Downloads |
|---|---|
|
PetToys.BigDecimal.Npgsql.EntityFrameworkCore
Entity Framework Core mapping that exposes PostgreSQL numeric columns as PetToys.BigDecimal.Core properties, over the handler PetToys.BigDecimal.Npgsql installs on the data source. |
|
|
PetToys.BigDecimal.Npgsql.Dapper
Dapper mapping that reads and writes PostgreSQL numeric columns as PetToys.BigDecimal.Core values, over the handler PetToys.BigDecimal.Npgsql installs on the data source. |
GitHub repositories
This package is not used by any popular GitHub repositories.
A patch release over 1.0.0. One tag versions every package in the repository, so
all six are published together; what each of them carries is below.
PetToys.BigDecimal.Core
Fixed:
- Parsing a literal whose exponent is far below the scale floor no longer costs
one division per digit it drops. A scale above 255 is reduced to it, so
"1e-99999" asked the pack to drop 99744 decimal positions and it divided for
every one of them, thousands of times over after the magnitude had already
become zero. The parser caps an exponent at 100000, which is what bounds the
worst case rather than what creates it. That input took 48 microseconds on
.NET 10 before the change; after it, the benchmark reads the same literal at
1.2x a parse that drops nothing, such as "1e-200". Every parsed value, scale
and sign is unchanged - those literals are zero at scale 255 before the
change and after it - and rounding, rescaling and the database wire paths
reach the same helper and stop the same way.
Notes:
- What GetHashCode costs is documented now, in the README and in the method's
own remarks. A hash has to agree with numeric equality, so it goes through
the value's shortest form: one carrying no trailing zeros costs 12.7x to 16.1x
decimal's hash, which strips them for the same reason, and one widened to a
database column's scale about 2x as much, because that one pays a division
pass over the magnitude. Nothing in the algorithm changed - the table of costs
covered arithmetic, parsing and formatting and simply did not reach hashing.
- What Pow guarantees for a power it cannot represent exactly is stated now, in
the method's own remarks and in the README, and the suite carries the deep
chains that back it. The power is raised in a working width of 154 digits
against the 77 a result keeps, so the rounding reads the digit the exact power
reads unless the exact power sits nearer the midpoint than 1e-66 of a unit in
the last place, which takes a 5 and then sixty-five zeros past the 77th
digit, or a 4 and then sixty-five nines. Nothing in the algorithm changed and
no value moved; what changed is that the documents no longer read as though
the exact power were carried.