From 1330a9cb0fa3fda9aff704155e3d7427262f64d5 Mon Sep 17 00:00:00 2001 From: liqiyu Date: Tue, 16 Jun 2026 11:13:42 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=8E=8B=E7=BC=A9?= =?UTF-8?q?=E5=90=8Efloat4=E5=92=8Cts=E5=88=97=E7=9A=84=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- github.com/lib/pq/conn.go | 28 ++++++++++++++++++++++------ github.com/lib/pq/conn_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/github.com/lib/pq/conn.go b/github.com/lib/pq/conn.go index feb02b3..8074e43 100644 --- a/github.com/lib/pq/conn.go +++ b/github.com/lib/pq/conn.go @@ -1489,6 +1489,22 @@ func compressedVarValue(typ oid.Oid, s []byte) interface{} { } } +func compressedFormatTimestamp(t time.Time) []byte { + b := FormatTimestamp(t.UTC()) + if len(b) > 0 && b[len(b)-1] == 'Z' { + b = append(b[:len(b)-1], "+00:00"...) + } + return b +} + +func compressedFloat4Value(s []byte) interface{} { + v := math.Float32frombits(binary.LittleEndian.Uint32(s)) + if v == float32(math.MaxFloat32) || v == -float32(math.MaxFloat32) { + return float64(v) + } + return v +} + func (kdc *KwDataChunk) GetData(parameterStatus *parameterStatus, row uint32, col int, typ oid.Oid, f format, length *int64) interface{} { start := row*kdc.storageLen[col] + kdc.colBlockOffset[col] if int(start) > len(*kdc.data) { @@ -1498,15 +1514,15 @@ func (kdc *KwDataChunk) GetData(parameterStatus *parameterStatus, row uint32, co switch typ { case oid.T_timestamptz, oid.T_timestamp, oid.T_date: timestamp := int64(binary.LittleEndian.Uint64(s)) - t := time.Unix(0, timestamp*int64(time.Millisecond)) - return FormatTimestamp(t) + t := time.Unix(0, timestamp*int64(time.Millisecond)).UTC() + return compressedFormatTimestamp(t) case oid.T_time, oid.T_timetz: micros := int64(binary.LittleEndian.Uint64(s)) return time.Unix(0, micros*1000).UTC() // nanosecond precision case oid.T_bool: return s[0] == 1 case oid.T_float4: - return math.Float32frombits(binary.LittleEndian.Uint32(s)) + return compressedFloat4Value(s) case oid.T_float8: return math.Float64frombits(binary.LittleEndian.Uint64(s)) // Custom OIDs: 91002=NCHAR, 91004=NVARCHAR, 91008=GEOMETRY. @@ -1538,15 +1554,15 @@ func (kdc *KwDataChunk) DepressGetData(parameterStatus *parameterStatus, row uin switch typ { case oid.T_timestamptz, oid.T_timestamp, oid.T_date: timestamp := int64(binary.LittleEndian.Uint64(s)) - t := time.Unix(0, timestamp*int64(time.Millisecond)) - return FormatTimestamp(t) + t := time.Unix(0, timestamp*int64(time.Millisecond)).UTC() + return compressedFormatTimestamp(t) case oid.T_time, oid.T_timetz: micros := int64(binary.LittleEndian.Uint64(s)) return time.Unix(0, micros*1000).UTC() // nanosecond precision case oid.T_bool: return s[0] == 1 case oid.T_float4: - return math.Float32frombits(binary.LittleEndian.Uint32(s)) + return compressedFloat4Value(s) case oid.T_float8: return math.Float64frombits(binary.LittleEndian.Uint64(s)) // Custom OIDs: 91002=NCHAR, 91004=NVARCHAR, 91008=GEOMETRY. diff --git a/github.com/lib/pq/conn_test.go b/github.com/lib/pq/conn_test.go index dd49a33..0160a91 100644 --- a/github.com/lib/pq/conn_test.go +++ b/github.com/lib/pq/conn_test.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "math" "testing" + "time" "github.com/lib/pq/oid" ) @@ -47,3 +48,36 @@ func TestCompressedDecodeOIDNumericRawFloat64Layout(t *testing.T) { t.Fatalf("expected raw float64 layout to decode to 5.5, got %#v", got) } } + +func TestDecodeCompressedFloat4FormattingValues(t *testing.T) { + var payload [4]byte + binary.LittleEndian.PutUint32(payload[:], math.Float32bits(float32(1.1))) + + kdc := &KwDataChunk{} + got := kdc.DepressGetData(nil, 0, 0, oid.T_float4, formatBinary, nil, payload[:]) + if _, ok := got.(float32); !ok { + t.Fatalf("expected ordinary compressed float4 to decode as float32, got %T", got) + } + + binary.LittleEndian.PutUint32(payload[:], math.Float32bits(math.MaxFloat32)) + + got = kdc.DepressGetData(nil, 0, 0, oid.T_float4, formatBinary, nil, payload[:]) + if _, ok := got.(float64); !ok { + t.Fatalf("expected max compressed float4 to decode as float64, got %T", got) + } + if got != float64(float32(math.MaxFloat32)) { + t.Fatalf("expected max compressed float4 to decode to %v, got %#v", float64(float32(math.MaxFloat32)), got) + } +} + +func TestDecodeCompressedTimestampUsesUTC(t *testing.T) { + var payload [8]byte + ts := time.Date(2026, 6, 10, 8, 0, 0, 0, time.UTC).UnixMilli() + binary.LittleEndian.PutUint64(payload[:], uint64(ts)) + + kdc := &KwDataChunk{} + got := kdc.DepressGetData(nil, 0, 0, oid.T_timestamptz, formatBinary, nil, payload[:]) + if string(got.([]byte)) != "2026-06-10 08:00:00+00:00" { + t.Fatalf("expected UTC timestamp text, got %#v", got) + } +} -- Gitee