Skip to content

Commit 78114d4

Browse files
authored
Fix decoding of very small Decimal values from NUMERIC data (#603)
When generating a string representation of a PostgresNumeric, we were misinterpreting the weight value for values with very small fractional parts. This caused decoding as Decimal to return incorrect values. Thanks to @patchthecode for reporting this issue!
1 parent db1eae1 commit 78114d4

File tree

4 files changed

+41
-12
lines changed

4 files changed

+41
-12
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
- name: Install curl for Codecov
3838
run: apt-get update -y -q && apt-get install -y curl
3939
- name: Check out package
40-
uses: actions/checkout@v5
40+
uses: actions/checkout@v6
4141
- name: Run unit tests with Thread Sanitizer
4242
run: |
4343
swift test --filter='^(PostgresNIOTests|ConnectionPoolModuleTests)' --sanitize=thread --enable-code-coverage
@@ -92,15 +92,15 @@ jobs:
9292
[[ -z "${SWIFT_VERSION}" ]] && SWIFT_VERSION="$(cat /.swift_tag 2>/dev/null || true)"
9393
printf 'OS: %s\nTag: %s\nVersion:\n' "${SWIFT_PLATFORM}-${RUNNER_ARCH}" "${SWIFT_VERSION}" && swift --version
9494
- name: Check out package
95-
uses: actions/checkout@v5
95+
uses: actions/checkout@v6
9696
with: { path: 'postgres-nio' }
9797
- name: Run integration tests
9898
run: swift test --package-path postgres-nio --filter=^IntegrationTests
9999
- name: Check out postgres-kit dependent
100-
uses: actions/checkout@v5
100+
uses: actions/checkout@v6
101101
with: { repository: 'vapor/postgres-kit', path: 'postgres-kit' }
102102
- name: Check out fluent-postgres-driver dependent
103-
uses: actions/checkout@v5
103+
uses: actions/checkout@v6
104104
with: { repository: 'vapor/fluent-postgres-driver', path: 'fluent-postgres-driver' }
105105
- name: Use local package in dependents
106106
run: |
@@ -136,7 +136,6 @@ jobs:
136136
POSTGRES_PASSWORD: 'test_password'
137137
POSTGRES_DB: 'postgres'
138138
POSTGRES_AUTH_METHOD: ${{ matrix.postgres-auth }}
139-
POSTGRES_SOCKET: '/tmp/.s.PGSQL.5432'
140139
POSTGRES_FORMULA: ${{ matrix.postgres-formula }}
141140
steps:
142141
- name: Select latest available Xcode
@@ -151,7 +150,7 @@ jobs:
151150
pg_ctl start --wait
152151
timeout-minutes: 15
153152
- name: Checkout code
154-
uses: actions/checkout@v5
153+
uses: actions/checkout@v6
155154
- name: Run all tests
156155
run: swift test --enable-code-coverage
157156
- name: Submit code coverage
@@ -165,7 +164,7 @@ jobs:
165164
container: swift:noble
166165
steps:
167166
- name: Checkout
168-
uses: actions/checkout@v5
167+
uses: actions/checkout@v6
169168
with:
170169
fetch-depth: 0
171170
# https://github.com/actions/checkout/issues/766

Sources/PostgresNIO/Data/PostgresData+Numeric.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import struct Foundation.Decimal
44
public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvertible, ExpressibleByStringLiteral {
55
/// The number of digits after this metadata
66
internal var ndigits: Int16
7-
/// How many of the digits are before the decimal point (always add 1)
7+
/// How many positions before or after the deicmal point the value is offset by
88
internal var weight: Int16
99
/// If 0x4000, this number is negative. See NUMERIC_NEG in
1010
/// https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/numeric.c
@@ -159,8 +159,10 @@ public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvert
159159
let offset: Int16
160160
if self.weight > 0 {
161161
offset = (self.weight + 1) - self.ndigits
162+
} else if self.weight < 0 {
163+
offset = abs(self.weight + 1)
162164
} else {
163-
offset = abs(self.weight) - self.ndigits
165+
offset = 0
164166
}
165167
if offset > 0 {
166168
for _ in 0..<offset {

Tests/IntegrationTests/PostgresNIOTests.swift

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -619,24 +619,52 @@ final class PostgresNIOTests: XCTestCase {
619619
let b = PostgresNumeric(string: "-123456.789123")!
620620
let c = PostgresNumeric(string: "3.14159265358979")!
621621
let d = PostgresNumeric(string: "1234567898765")!
622+
let e = PostgresNumeric(string: "0.00000080216390553684")!
623+
let f = PostgresNumeric(string: "0.0000080216390553684")!
624+
let g = PostgresNumeric(string: "0.000080216390553684")!
625+
let h = PostgresNumeric(string: "802163905536840000.0")!
626+
let i = PostgresNumeric(string: "8021639055368400000.0")!
627+
let j = PostgresNumeric(string: "80216390553684000000.0")!
628+
let k = PostgresNumeric(string: "802163905536840000.000080216390553684")!
622629
var rows: PostgresQueryResult?
623630
XCTAssertNoThrow(rows = try conn?.query("""
624631
select
625632
$1::numeric as a,
626633
$2::numeric as b,
627634
$3::numeric as c,
628-
$4::numeric as d
635+
$4::numeric as d,
636+
$5::numeric as e,
637+
$6::numeric as f,
638+
$7::numeric as g,
639+
$8::numeric as h,
640+
$9::numeric as i,
641+
$10::numeric as j,
642+
$11::numeric as k
629643
""", [
630644
.init(numeric: a),
631645
.init(numeric: b),
632646
.init(numeric: c),
633-
.init(numeric: d)
647+
.init(numeric: d),
648+
.init(numeric: e),
649+
.init(numeric: f),
650+
.init(numeric: g),
651+
.init(numeric: h),
652+
.init(numeric: i),
653+
.init(numeric: j),
654+
.init(numeric: k)
634655
]).wait())
635656
let row = rows?.first?.makeRandomAccess()
636657
XCTAssertEqual(row?[data: "a"].decimal, Decimal(string: "123456.789123")!)
637658
XCTAssertEqual(row?[data: "b"].decimal, Decimal(string: "-123456.789123")!)
638659
XCTAssertEqual(row?[data: "c"].decimal, Decimal(string: "3.14159265358979")!)
639660
XCTAssertEqual(row?[data: "d"].decimal, Decimal(string: "1234567898765")!)
661+
XCTAssertEqual(row?[data: "e"].decimal, Decimal(string: "0.00000080216390553684")!)
662+
XCTAssertEqual(row?[data: "f"].decimal, Decimal(string: "0.0000080216390553684")!)
663+
XCTAssertEqual(row?[data: "g"].decimal, Decimal(string: "0.000080216390553684")!)
664+
XCTAssertEqual(row?[data: "h"].decimal, Decimal(string: "802163905536840000.0")!)
665+
XCTAssertEqual(row?[data: "i"].decimal, Decimal(string: "8021639055368400000.0")!)
666+
XCTAssertEqual(row?[data: "j"].decimal, Decimal(string: "80216390553684000000.0")!)
667+
XCTAssertEqual(row?[data: "k"].decimal, Decimal(string: "802163905536840000.000080216390553684")!)
640668
}
641669

642670
func testDecimalStringSerialization() {

Tests/PostgresNIOTests/New/Data/Decimal+PSQLCodableTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import NIOCore
55
class Decimal_PSQLCodableTests: XCTestCase {
66

77
func testRoundTrip() {
8-
let values: [Decimal] = [1.1, .pi, -5e-12]
8+
let values: [Decimal] = [1.1, .pi, -5e-12, 0.00000080216390553684]
99

1010
for value in values {
1111
var buffer = ByteBuffer()

0 commit comments

Comments
 (0)