Sample
github.com/jackc/pgx/v5 v5.10.0: Avoid DSN RuntimeParams pollution by distinguishing pgx and pgxpool ParseConfig, enforce Simple Protocol for multi-statement execution, and scan nullable array elements into pointer or pgtype slices
Verified sample for golang github.com/jackc/pgx/v5 v5.10.0: Avoid DSN RuntimeParams pollution by distinguishing pgx and pgxpool ParseConfig, enforce Simple…
sha256:ff6af08598a4e3fc137ed941f6d4febe37974700677148ef2b547bef8cdf052c
This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured.
How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people.
MIT-0
Execution evidence
The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.
- Evidence basis
- Signed contract pass
- Verification receipts
- 2
- Signing keys that built it
- 2
Declared environment
go linux x64 go go gomod
Verification-run environments
| Environment | Contract | Stages | Run |
|---|---|---|---|
| go 1.26 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · golang@1 |
2026-08-16 |
| go 1.26 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · golang@1 |
2026-08-18 |
Case
HOW- Goal
- Avoid DSN RuntimeParams pollution by distinguishing pgx and pgxpool ParseConfig, enforce Simple Protocol for multi-statement execution, and scan nullable array elements into pointer or pgtype slices
- Packages
- Symbols
-
- pgx.ParseConfig
- pgxpool.ParseConfig
- pgx.QueryExecMode
- pgx.Batch
- pgtype.FlatArray
- pgtype.Numeric
- Environment
- go
- Created
- 2026-08-16T06:22:07Z
Contract
- assert pgx.ParseConfig routes pool options into RuntimeParams while pgxpool.ParseConfig extracts them
- assert pgx.ParseConfig configures statement_cache_capacity and default_query_exec_mode without RuntimeParams pollution
- assert default QueryExecModeCacheStatement rejects multi-statement SQL strings while QueryExecModeSimpleProtocol succeeds
- assert scanning SQL arrays with NULL elements fails for []T but succeeds for []*T and []pgtype.Int4
- assert scanning SQL NULL array yields nil slice whereas SQL empty array {} yields non-nil slice
- assert pgtype.FlatArray flattens multi-dimensional PostgreSQL arrays into 1D Go slices
- assert pgx.Batch stops subsequent executions on query failure and BatchResults.Close returns the first error
- assert pgtype.Numeric handles fractional decimals, NaN, and Infinity modifiers without loss of precision
Files
- NOTES.md
- csx.json
- go.mod
- go.sum
- mock.go
- sample_test.go
Source
# Notes
## CSX Network Search Result
`search_known_solution` returned sample `sha256:4eb6eee48a60e79da9dcf4055256991997f094a1da19ee17cfe0697e6d6d7b32` (goal: "Handle NULL struct scans via pointers or pgtype, prevent column-mismatch errors in RowToStructByName(Lax), and safely release pgxpool connections without post-release panics"). We chose a different behavioral trap: DSN pool query parameter routing into `RuntimeParams`, extended protocol multi-statement query failure vs simple protocol execution, array NULL element slice scanning into `[]*T`/`[]pgtype.Int4`, `pgtype.FlatArray` dimension flattening, and batch error short-circuiting.
## What a Model Would Have Written Instead
A naive model assumes `pgx.ParseConfig` strips pool options or ignores unrecognized DSN params, that multi-statement SQL strings run seamlessly under default query execution modes, and that SQL arrays with `NULL` elements can be directly scanned into plain Go slices like `[]int32`.
## How the Wrong Version Fails
- **DSN parsing with pool options in `pgx.ParseConfig`**: Leaks `pool_max_conns` into `RuntimeParams`, causing PostgreSQL to abort connection startup at runtime with `ERROR: unrecognized configuration parameter "pool_max_conns" (SQLSTATE 42704)` (loud runtime connection error).
- **Multi-statement queries under default `QueryExecModeCacheStatement`**: Fails at runtime with `ERROR: cannot insert multiple commands into a prepared statement (SQLSTATE 42601)` (loud execution error).
- **Scanning SQL arrays containing `NULL` into `[]T`**: Fails at runtime with `cannot scan NULL into *int32` (loud scanning error).
- **Reading beyond batch boundaries or after batch query error**: Fails with `no more results in batch` or preserves the first error on `br.Close()` rather than executing subsequent statements.
{"case":{"caseId":"case:sha256:276044ef4339eb6b8a38fd8616f9a791dd028902c41adf9b3eb1ea96a64602c6","contract":["assert pgx.ParseConfig routes pool options into RuntimeParams while pgxpool.ParseConfig extracts them","assert pgx.ParseConfig configures statement_cache_capacity and default_query_exec_mode without RuntimeParams pollution","assert default QueryExecModeCacheStatement rejects multi-statement SQL strings while QueryExecModeSimpleProtocol succeeds","assert scanning SQL arrays with NULL elements fails for []T but succeeds for []*T and []pgtype.Int4","assert scanning SQL NULL array yields nil slice whereas SQL empty array {} yields non-nil slice","assert pgtype.FlatArray flattens multi-dimensional PostgreSQL arrays into 1D Go slices","assert pgx.Batch stops subsequent executions on query failure and BatchResults.Close returns the first error","assert pgtype.Numeric handles fractional decimals, NaN, and Infinity modifiers without loss of precision"],"goal":"Avoid DSN RuntimeParams pollution by distinguishing pgx and pgxpool ParseConfig, enforce Simple Protocol for multi-statement execution, and scan nullable array elements into pointer or pgtype slices","kind":"HOW","packages":["pkg:golang/github.com/jackc/pgx/v5@v5.10.0"],"schemaVersion":1,"symbols":["pgx.ParseConfig","pgxpool.ParseConfig","pgx.QueryExecMode","pgx.Batch","pgtype.FlatArray","pgtype.Numeric"]},"contractCommand":["go","test","./..."],"environment":{"arch":"x64","ecosystem":"golang","executionContext":"go","language":"go","os":"linux","packageManager":"gomod","runtime":"go","schemaVersion":1},"license":"MIT-0","packages":["pkg:golang/github.com/jackc/pgx/v5@v5.10.0"],"schemaVersion":1,"symbols":["pgx.ParseConfig","pgxpool.ParseConfig","pgx.QueryExecMode","pgx.Batch","pgtype.FlatArray","pgtype.Numeric"],"verifierAdapter":"golang@1"}
module sample
go 1.26.5
require github.com/jackc/pgx/v5 v5.10.0
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.29.0 // indirect
)
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
package sample
import (
"context"
"net"
"strings"
"testing"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgproto3"
"github.com/jackc/pgx/v5/pgtype"
)
// NewMockConn returns an in-memory pgx.Conn connected over a net.Pipe to a simulated PostgreSQL protocol backend.
func NewMockConn(t *testing.T, modifyCfg func(*pgx.ConnConfig)) (*pgx.Conn, context.Context) {
t.Helper()
cfg, err := pgx.ParseConfig("postgres://postgres:secret@localhost:5432/testdb?sslmode=disable")
if err != nil {
t.Fatalf("ParseConfig failed: %v", err)
}
if modifyCfg != nil {
modifyCfg(cfg)
}
cfg.DialFunc = func(ctx context.Context, _, _ string) (net.Conn, error) {
c, s := net.Pipe()
go func(conn net.Conn) {
defer conn.Close()
b := pgproto3.NewBackend(conn, conn)
if msg, _ := b.ReceiveStartupMessage(); msg != nil {
if _, ok := msg.(*pgproto3.SSLRequest); ok {
_, _ = conn.Write([]byte{'N'})
_, _ = b.ReceiveStartupMessage()
}
}
b.Send(&pgproto3.AuthenticationOk{})
b.Send(&pgproto3.ParameterStatus{Name: "standard_conforming_strings", Value: "on"})
b.Send(&pgproto3.ParameterStatus{Name: "server_version", Value: "16.0"})
b.Send(&pgproto3.ParameterStatus{Name: "client_encoding", Value: "UTF8"})
b.Send(&pgproto3.ReadyForQuery{TxStatus: 'I'})
_ = b.Flush()
for {
msg, err := b.Receive()
if err != nil || msg == nil {
return
}
switch m := msg.(type) {
case *pgproto3.Terminate:
return
case *pgproto3.Query:
// Simple protocol query handler
if strings.Contains(m.String, "FAIL") {
b.Send(&pgproto3.ErrorResponse{Severity: "ERROR", Message: "query execution failed"})
} else {
b.Send(&pgproto3.RowDescription{
Fields: []pgproto3.FieldDescription{
{Name: []byte("val"), DataTypeOID: pgtype.Int4OID, Format: 0},
},
})
b.Send(&pgproto3.DataRow{Values: [][]byte{[]byte("42")}})
b.Send(&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")})
}
b.Send(&pgproto3.ReadyForQuery{TxStatus: 'I'})
_ = b.Flush()
case *pgproto3.Parse:
// Extended protocol: Parse statement
if strings.Contains(m.Query, ";") {
b.Send(&pgproto3.ErrorResponse{
Severity: "ERROR",
Code: "42601",
Message: "cannot insert multiple commands into a prepared statement",
})
} else if strings.Contains(m.Query, "FAIL") {
b.Send(&pgproto3.ErrorResponse{
Severity: "ERROR",
Code: "42P01",
Message: "relation does not exist",
})
} else {
b.Send(&pgproto3.ParseComplete{})
}
_ = b.Flush()
case *pgproto3.Describe:
if m.ObjectType == 'S' {
b.Send(&pgproto3.ParameterDescription{})
}
b.Send(&pgproto3.RowDescription{
Fields: []pgproto3.FieldDescription{
{Name: []byte("val"), DataTypeOID: pgtype.Int4OID, Format: 1},
},
})
_ = b.Flush()
case *pgproto3.Bind:
b.Send(&pgproto3.BindComplete{})
_ = b.Flush()
case *pgproto3.Execute:
// 4-byte big-endian int32 42
b.Send(&pgproto3.DataRow{Values: [][]byte{{0, 0, 0, 42}}})
b.Send(&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")})
_ = b.Flush()
case *pgproto3.Sync:
b.Send(&pgproto3.ReadyForQuery{TxStatus: 'I'})
_ = b.Flush()
}
}
}(s)
return c, nil
}
ctx := context.Background()
conn, err := pgx.ConnectConfig(ctx, cfg)
if err != nil {
t.Fatalf("ConnectConfig failed: %v", err)
}
t.Cleanup(func() { _ = conn.Close(ctx) })
return conn, ctx
}
package sample
import (
"math"
"math/big"
"strings"
"testing"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
)
// 1. Connection string parsing: pgx.ParseConfig routes unrecognized query parameters into
// RuntimeParams (which PostgreSQL receives at startup), whereas pgxpool.ParseConfig extracts pool options.
func TestParseConfig_RuntimeParamsAndOptions(t *testing.T) {
dsn := "postgres://user:secret@localhost:5432/testdb?pool_max_conns=20&pool_min_conns=5&statement_cache_capacity=0&default_query_exec_mode=simple_protocol&search_path=custom,public&sslmode=disable"
// Single connection ParseConfig:
connCfg, err := pgx.ParseConfig(dsn)
if err != nil {
t.Fatalf("pgx.ParseConfig failed: %v", err)
}
// Recognized pgx options are handled
if connCfg.StatementCacheCapacity != 0 {
t.Fatalf("expected StatementCacheCapacity=0, got %d", connCfg.StatementCacheCapacity)
}
if connCfg.DefaultQueryExecMode != pgx.QueryExecModeSimpleProtocol {
t.Fatalf("expected DefaultQueryExecMode=simple_protocol, got %v", connCfg.DefaultQueryExecMode)
}
// Unrecognized pool options leak into RuntimeParams for single connection configs
if connCfg.RuntimeParams["pool_max_conns"] != "20" || connCfg.RuntimeParams["pool_min_conns"] != "5" {
t.Fatalf("expected pool options in RuntimeParams, got %v", connCfg.RuntimeParams)
}
if connCfg.RuntimeParams["search_path"] != "custom,public" {
t.Fatalf("expected search_path in RuntimeParams, got %v", connCfg.RuntimeParams)
}
// Pool ParseConfig:
poolCfg, err := pgxpool.ParseConfig(dsn)
if err != nil {
t.Fatalf("pgxpool.ParseConfig failed: %v", err)
}
if poolCfg.MaxConns != 20 || poolCfg.MinConns != 5 {
t.Fatalf("expected pool MaxConns=20, MinConns=5, got max=%d, min=%d", poolCfg.MaxConns, poolCfg.MinConns)
}
// Pool-specific settings are stripped from the inner ConnConfig RuntimeParams
if _, exists := poolCfg.ConnConfig.RuntimeParams["pool_max_conns"]; exists {
t.Fatalf("expected pool_max_conns NOT in pool ConnConfig.RuntimeParams")
}
if _, exists := poolCfg.ConnConfig.RuntimeParams["pool_min_conns"]; exists {
t.Fatalf("expected pool_min_conns NOT in pool ConnConfig.RuntimeParams")
}
}
// 2. Query execution modes: Default extended protocol rejects multi-statement strings, while simple protocol succeeds.
func TestQueryExecMode_MultiStatement(t *testing.T) {
// Default QueryExecModeCacheStatement (Extended Protocol)
connDefault, ctxDefault := NewMockConn(t, nil)
var valDefault int
err := connDefault.QueryRow(ctxDefault, "SELECT 1; SELECT 2;").Scan(&valDefault)
if err == nil || !strings.Contains(err.Error(), "cannot insert multiple commands into a prepared statement") {
t.Fatalf("expected extended protocol failure on multi-statement query, got: %v", err)
}
// QueryExecModeSimpleProtocol (Simple Protocol)
connSimple, ctxSimple := NewMockConn(t, func(c *pgx.ConnConfig) {
c.DefaultQueryExecMode = pgx.QueryExecModeSimpleProtocol
})
var valSimple int
err = connSimple.QueryRow(ctxSimple, "SELECT 1; SELECT 2;").Scan(&valSimple)
if err != nil || valSimple != 42 {
t.Fatalf("expected simple protocol success, got err=%v, val=%d", err, valSimple)
}
}
// 3. Array scanning: NULL elements in arrays fail on []T but succeed on []*T and []pgtype.Int4.
// SQL NULL array vs empty array {} are distinctly mapped to nil vs non-nil empty slices.
func TestArrayScanning_NullElementsAndSlices(t *testing.T) {
tm := pgtype.NewMap()
// Text format array containing NULL element: "{10,NULL,30}"
textNullArray := []byte("{10,NULL,30}")
// Scanning into []int32 fails because int32 cannot represent NULL
var ints []int32
err := tm.Scan(pgtype.Int4ArrayOID, pgtype.TextFormatCode, textNullArray, &ints)
if err == nil || !strings.Contains(err.Error(), "cannot scan NULL into *int32") {
t.Fatalf("expected error scanning array with NULL element into []int32, got: %v", err)
}
// Scanning into []*int32 succeeds and populates nil for NULL elements
var ptrInts []*int32
err = tm.Scan(pgtype.Int4ArrayOID, pgtype.TextFormatCode, textNullArray, &ptrInts)
if err != nil {
t.Fatalf("expected []*int32 scan to succeed, got: %v", err)
}
if len(ptrInts) != 3 || ptrInts[0] == nil || *ptrInts[0] != 10 || ptrInts[1] != nil || ptrInts[2] == nil || *ptrInts[2] != 30 {
t.Fatalf("unexpected ptrInts: %+v", ptrInts)
}
// Scanning into []pgtype.Int4 succeeds with Valid=false for NULL elements
var pgInts []pgtype.Int4
err = tm.Scan(pgtype.Int4ArrayOID, pgtype.TextFormatCode, textNullArray, &pgInts)
if err != nil {
t.Fatalf("expected []pgtype.Int4 scan to succeed, got: %v", err)
}
if len(pgInts) != 3 || !pgInts[0].Valid || pgInts[0].Int32 != 10 || pgInts[1].Valid || !pgInts[2].Valid || pgInts[2].Int32 != 30 {
t.Fatalf("unexpected pgInts: %+v", pgInts)
}
// SQL NULL array (nil raw bytes) yields nil slice
var sliceNull []string = []string{"initial"}
err = tm.Scan(pgtype.TextArrayOID, pgtype.TextFormatCode, nil, &sliceNull)
if err != nil || sliceNull != nil {
t.Fatalf("expected nil slice for SQL NULL, got err=%v, slice=%v", err, sliceNull)
}
// SQL empty array "{}" yields non-nil slice with length 0
var sliceEmpty []string = []string{"initial"}
err = tm.Scan(pgtype.TextArrayOID, pgtype.TextFormatCode, []byte("{}"), &sliceEmpty)
if err != nil || sliceEmpty == nil || len(sliceEmpty) != 0 {
t.Fatalf("expected empty non-nil slice for {}, got err=%v, slice=%v", err, sliceEmpty)
}
// Multi-dimensional array: "{{1,2},{3,4}}" flattened into 1D slice via pgtype.FlatArray
var flat pgtype.FlatArray[int32]
err = tm.Scan(pgtype.Int4ArrayOID, pgtype.TextFormatCode, []byte("{{1,2},{3,4}}"), &flat)
if err != nil {
t.Fatalf("expected FlatArray scan to succeed, got: %v", err)
}
if len(flat) != 4 || flat[0] != 1 || flat[1] != 2 || flat[2] != 3 || flat[3] != 4 {
t.Fatalf("unexpected flat array contents: %+v", flat)
}
}
// 4. Batch execution: Failed query halts execution of subsequent queued queries, and Close() returns first error.
func TestBatchExecution_ErrorPropagation(t *testing.T) {
conn, ctx := NewMockConn(t, nil)
// Clean batch execution
batch := &pgx.Batch{}
batch.Queue("SELECT 1")
batch.Queue("SELECT 2")
br := conn.SendBatch(ctx, batch)
var val1, val2 int
if err := br.QueryRow().Scan(&val1); err != nil || val1 != 42 {
t.Fatalf("expected val1=42, got err=%v, val=%d", err, val1)
}
if err := br.QueryRow().Scan(&val2); err != nil || val2 != 42 {
t.Fatalf("expected val2=42, got err=%v, val=%d", err, val2)
}
// Reading beyond queued query count returns error
var val3 int
err := br.QueryRow().Scan(&val3)
if err == nil || !strings.Contains(err.Error(), "no more results in batch") {
t.Fatalf("expected 'no more results in batch' error, got: %v", err)
}
if err := br.Close(); err != nil {
t.Fatalf("expected clean br.Close(), got: %v", err)
}
// Batch error propagation: First error halts subsequent queries
batchFail := &pgx.Batch{}
batchFail.Queue("SELECT FAIL")
batchFail.Queue("SELECT 2")
brFail := conn.SendBatch(ctx, batchFail)
var fval1, fval2 int
err1 := brFail.QueryRow().Scan(&fval1)
if err1 == nil || !strings.Contains(err1.Error(), "relation does not exist") {
t.Fatalf("expected relation error on first batch query, got: %v", err1)
}
err2 := brFail.QueryRow().Scan(&fval2)
if err2 == nil {
t.Fatalf("expected error on subsequent query after batch failure, got nil")
}
errClose := brFail.Close()
if errClose == nil || !strings.Contains(errClose.Error(), "relation does not exist") {
t.Fatalf("expected Close() to return first batch error, got: %v", errClose)
}
}
// 5. Numeric handling: pgtype.Numeric retains exact fractional decimals, supports NaN, and Infinity modifiers.
func TestNumeric_PrecisionAndSpecialValues(t *testing.T) {
tm := pgtype.NewMap()
// Text format numeric "123.45"
var num pgtype.Numeric
err := tm.Scan(pgtype.NumericOID, pgtype.TextFormatCode, []byte("123.45"), &num)
if err != nil {
t.Fatalf("scan numeric failed: %v", err)
}
if !num.Valid || num.Exp != -2 || num.Int.Cmp(big.NewInt(12345)) != 0 {
t.Fatalf("unexpected numeric representation: %+v", num)
}
// Int64Value fails on non-integer numeric (non-zero exponent)
_, err = num.Int64Value()
if err == nil {
t.Fatalf("expected error converting fractional numeric to int64, got nil")
}
// Float64Value succeeds
f, err := num.Float64Value()
if err != nil || !f.Valid || math.Abs(f.Float64-123.45) > 1e-6 {
t.Fatalf("expected 123.45 float, got err=%v, val=%v", err, f)
}
// Special numeric value: "NaN"
var numNaN pgtype.Numeric
err = tm.Scan(pgtype.NumericOID, pgtype.TextFormatCode, []byte("NaN"), &numNaN)
if err != nil || !numNaN.Valid || !numNaN.NaN {
t.Fatalf("expected NaN numeric, got err=%v, val=%+v", err, numNaN)
}
fNaN, err := numNaN.Float64Value()
if err != nil || !math.IsNaN(fNaN.Float64) {
t.Fatalf("expected NaN float, got err=%v, val=%v", err, fNaN)
}
// Special numeric value: "Infinity"
var numInf pgtype.Numeric
err = tm.Scan(pgtype.NumericOID, pgtype.TextFormatCode, []byte("Infinity"), &numInf)
if err != nil || !numInf.Valid || numInf.InfinityModifier != pgtype.Infinity {
t.Fatalf("expected Infinity numeric, got err=%v, val=%+v", err, numInf)
}
fInf, err := numInf.Float64Value()
if err != nil || !math.IsInf(fInf.Float64, 1) {
t.Fatalf("expected +Inf float, got err=%v, val=%v", err, fInf)
}
}