From db22b7f402849ceca0da73073044c2c13e6f6e05 Mon Sep 17 00:00:00 2001 From: Ciro Spaciari Date: Tue, 23 Sep 2025 20:14:19 -0700 Subject: [PATCH] fix(Bun.sql) handle numeric correctly (#22925) ### What does this PR do? Fixes https://github.com/oven-sh/bun/issues/21225 ### How did you verify your code works? Tests --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/sql/postgres/DataCell.zig | 8 +++++++- test/js/sql/sql.test.ts | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/sql/postgres/DataCell.zig b/src/sql/postgres/DataCell.zig index 4ca56895c0..98a721a735 100644 --- a/src/sql/postgres/DataCell.zig +++ b/src/sql/postgres/DataCell.zig @@ -750,6 +750,7 @@ fn parseBinaryNumeric(input: []const u8, result: *std.ArrayList(u8)) !PGNummeric const weight = try reader.readInt(i16, .big); const sign = try reader.readInt(u16, .big); const dscale = try reader.readInt(i16, .big); + log("ndigits: {d}, weight: {d}, sign: {d}, dscale: {d}", .{ ndigits, weight, sign, dscale }); // Handle special cases switch (sign) { @@ -786,6 +787,7 @@ fn parseBinaryNumeric(input: []const u8, result: *std.ArrayList(u8)) !PGNummeric while (idx <= weight) : (idx += 1) { const digit = if (idx < ndigits) try reader.readInt(u16, .big) else 0; + log("digit: {d}", .{digit}); var digit_str: [4]u8 = undefined; const digit_len = std.fmt.formatIntBuf(&digit_str, digit, 10, .lower, .{ .width = 4, .fill = '0' }); if (!first_non_zero) { @@ -810,12 +812,14 @@ fn parseBinaryNumeric(input: []const u8, result: *std.ArrayList(u8)) !PGNummeric var idx: isize = scale_start; const end: usize = result.items.len + @as(usize, @intCast(dscale)); while (idx < dscale) : (idx += 4) { - if (idx >= 0 and idx < ndigits) { + if (idx >= 0 and idx < dscale) { const digit = reader.readInt(u16, .big) catch 0; + log("dscale digit: {d}", .{digit}); var digit_str: [4]u8 = undefined; const digit_len = std.fmt.formatIntBuf(&digit_str, digit, 10, .lower, .{ .width = 4, .fill = '0' }); try result.appendSlice(digit_str[0..digit_len]); } else { + log("dscale digit: 0000", .{}); try result.appendSlice("0000"); } } @@ -987,6 +991,8 @@ const debug = bun.Output.scoped(.Postgres, .visible); extern fn Postgres__formatTime(microseconds: i64, buffer: [*]u8, bufferSize: usize) usize; extern fn Postgres__formatTimeTz(microseconds: i64, tzOffsetSeconds: i32, buffer: [*]u8, bufferSize: usize) usize; +const log = bun.Output.scoped(.PostgresDataCell, .visible); + const PostgresCachedStructure = @import("../shared/CachedStructure.zig"); const protocol = @import("./PostgresProtocol.zig"); const std = @import("std"); diff --git a/test/js/sql/sql.test.ts b/test/js/sql/sql.test.ts index b0297def05..f7d1d5d77a 100644 --- a/test/js/sql/sql.test.ts +++ b/test/js/sql/sql.test.ts @@ -118,6 +118,27 @@ if (isDockerEnabled()) { } }); + test("should handle numeric values with many digits", async () => { + await using sql = postgres(options); + // handle numbers big than 10,4 with zeros at the end and start, starting with 0. or not + for (let value of [ + "1234.00005678912345670000", + "1234.12345678912345670000", + "1234.12345678912345678912", + "1234.12345678912345678900", + "0.00005678912345670000", + "0.12345678912345670000", + "0.12345678912345678912", + "0.12345678912345678900", + ]) { + const [{ x }] = await sql`select CAST(${value} as NUMERIC(30,20)) as x`; + expect(x).toBe(value); + } + // zero specifically + const [{ x }] = await sql`select CAST(${"0.00000000000000000000"} as NUMERIC(30,20)) as x`; + expect(x).toBe("0"); + }); + describe("Time/TimeZ", () => { test("PostgreSQL TIME and TIMETZ types are handled correctly", async () => { const db = postgres(options);