decimal

package module
v0.0.0-...-98e21a4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 23, 2026 License: MIT Imports: 7 Imported by: 0

README

pgx-quagmt-udecimal

Test Coverage

PostgreSQL numeric, float8, and int8 type support for jackc/pgx/v5 using quagmt/udecimal.

This is a port of jackc/pgx-shopspring-decimal, rewritten to use quagmt/udecimal — a high-performance 128-bit fixed-point decimal library — instead of shopspring/decimal.

Installation

go get github.com/ppreeper/pgx-quagmt-udecimal

Quick Start

Register the type mappings after establishing a connection:

import (
    "github.com/jackc/pgx/v5"
    "github.com/ppreeper/pgx-quagmt-udecimal"
)

conn, err := pgx.Connect(ctx, connString)
if err != nil {
    // handle error
}

decimal.Register(conn.TypeMap())

Or with a connection pool:

pool, err := pgxpool.New(ctx, connString)
if err != nil {
    // handle error
}

decimal.Register(pool.Config().ConnConfig.TypeMap)

Or via AfterConnect in the test runner or custom config:

config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
    decimal.Register(conn.TypeMap())
    return nil
}

Supported Types

Go Type PostgreSQL Types Nullable
udecimal.Decimal numeric, float8, int8 No
udecimal.NullDecimal numeric, float8, int8 Yes
decimal.Decimal numeric, float8, int8 No
decimal.NullDecimal numeric, float8, int8 Yes

Slice variants are also supported: []T, *[]T, []*T, *[]*T.

Usage Examples

Query with numeric
var amount udecimal.Decimal
err := conn.QueryRow(ctx, `SELECT price FROM products WHERE id = $1`, id).Scan(&amount)
Insert with numeric
amount := udecimal.MustParse("19.99")
_, err := conn.Exec(ctx, `INSERT INTO orders (amount) VALUES ($1)`, amount)
Nullable decimals
var discount udecimal.NullDecimal
err := conn.QueryRow(ctx, `SELECT discount FROM orders WHERE id = $1`, id).Scan(&discount)

if discount.Valid {
    fmt.Println("Discount:", discount.Decimal)
} else {
    fmt.Println("No discount")
}
Arrays
var amounts []udecimal.Decimal
err := conn.QueryRow(ctx, `SELECT prices FROM product WHERE id = $1`, id).Scan(&amounts)
Using the wrapper types

The decimal.Decimal and decimal.NullDecimal wrapper types are type aliases for udecimal.Decimal and udecimal.NullDecimal. They exist to implement pgx's scanning/encoding interfaces. You can use either the udecimal types directly or the decimal wrappers — both work after registration:

// These are equivalent after decimal.Register():
var a udecimal.Decimal
var b decimal.Decimal

// Both can be scanned into:
conn.QueryRow(ctx, `SELECT price FROM products LIMIT 1`).Scan(&a)
conn.QueryRow(ctx, `SELECT price FROM products LIMIT 1`).Scan(&b)

How It Works

This library hooks into pgx's type system at three levels:

  1. Custom codec — NumericCodec overrides DecodeValue to decode PostgreSQL numeric binary format directly into udecimal.Decimal.

  2. Encode/Scan plan wrappers — TryWrapNumericEncodePlan and TryWrapNumericScanPlan intercept pgx's encode/scan pipeline, wrapping udecimal.Decimal and udecimal.NullDecimal with local types that implement ScanNumeric, NumericValue, ScanFloat64, Float64Value, ScanInt64, and Int64Value.

  3. Default type registration — Registers all type variants (T, *T, []T, *[]T, []*T, *[]*T) for both udecimal and wrapper types under the numeric PostgreSQL type name.

Conversion details
  • pgtype.Numeric ↔ udecimal.Decimal: The Numeric type stores values as Int * 10^Exp. Conversion uses string building for scanning and a fast-path via ToHiLo() (128-bit coefficient) with string fallback for encoding.
  • pgtype.Float8 ↔ udecimal.Decimal: Uses NewFromFloat64 / InexactFloat64. NaN and Infinity are rejected on scan.
  • pgtype.Int8 ↔ udecimal.Decimal: Uses NewFromInt64 / Int64. Includes a workaround for math.MinInt64 (udecimal's Int64() rejects it due to an upstream edge case).

Limitations

  • Max precision: udecimal supports up to 19 decimal places. PostgreSQL numeric supports up to 16383. Values exceeding 19 decimal places will fail to parse.
  • NaN / Infinity: PostgreSQL numeric supports NaN and Infinity. This library rejects them on scan (returns an error), as udecimal has no representation for these values.
  • Float64 conversion: InexactFloat64() may lose precision for very large numbers. This is inherent to float64 representation.

Benchmarks

Run benchmarks with:

go test -bench=. -run=XXX ./...

Key results (Ryzen 7 7730U):

Benchmark Time
Int64 round-trip ~21 ns/op
Numeric round-trip ~224 ns/op
Float64 round-trip ~203 ns/op
Null scan (null value) ~2-3 ns/op
Encode/Scan plan wrapper (hit) ~0.25 ns/op

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Register

func Register(m *pgtype.Map)

Register registers the quagmt/udecimal integration with a pgtype.Map.

func TryWrapNumericEncodePlan

func TryWrapNumericEncodePlan(value interface{}) (plan pgtype.WrappedEncodePlanNextSetter, nextValue interface{}, ok bool)

func TryWrapNumericScanPlan

func TryWrapNumericScanPlan(target interface{}) (plan pgtype.WrappedScanPlanNextSetter, nextDst interface{}, ok bool)

Types

type Decimal

type Decimal udecimal.Decimal

func (Decimal) Float64Value

func (d Decimal) Float64Value() (pgtype.Float8, error)

func (Decimal) Int64Value

func (d Decimal) Int64Value() (pgtype.Int8, error)

func (Decimal) NumericValue

func (d Decimal) NumericValue() (pgtype.Numeric, error)

func (*Decimal) ScanFloat64

func (d *Decimal) ScanFloat64(v pgtype.Float8) error

func (*Decimal) ScanInt64

func (d *Decimal) ScanInt64(v pgtype.Int8) error

func (*Decimal) ScanNumeric

func (d *Decimal) ScanNumeric(v pgtype.Numeric) error

type NullDecimal

type NullDecimal udecimal.NullDecimal

func (NullDecimal) Float64Value

func (d NullDecimal) Float64Value() (pgtype.Float8, error)

func (NullDecimal) Int64Value

func (d NullDecimal) Int64Value() (pgtype.Int8, error)

func (NullDecimal) NumericValue

func (d NullDecimal) NumericValue() (pgtype.Numeric, error)

func (*NullDecimal) ScanFloat64

func (d *NullDecimal) ScanFloat64(v pgtype.Float8) error

func (*NullDecimal) ScanInt64

func (d *NullDecimal) ScanInt64(v pgtype.Int8) error

func (*NullDecimal) ScanNumeric

func (d *NullDecimal) ScanNumeric(v pgtype.Numeric) error

type NumericCodec

type NumericCodec struct {
	pgtype.NumericCodec
}

func (NumericCodec) DecodeValue

func (NumericCodec) DecodeValue(tm *pgtype.Map, oid uint32, format int16, src []byte) (interface{}, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL