Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2874,6 +2874,29 @@ pub fn build(b: *std.Build) void {
const run_loader_unit_tests = b.addRunArtifact(loader_unit_tests);
unit_test_step.dependOn(&run_loader_unit_tests.step);

// Unit tests for the Parquet loader (src/parquet.zig) — decimalToText sign handling
const parquet_unit_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/parquet.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
parquet_unit_tests.root_module.addImport("c", translate_c.createModule());
parquet_unit_tests.root_module.addImport("zig_parquet", zig_parquet.module("parquet"));
if (bundle_sqlite) {
parquet_unit_tests.root_module.addIncludePath(b.path("lib"));
parquet_unit_tests.root_module.addCSourceFile(.{
.file = b.path("lib/sqlite3.c"),
.flags = &.{ "-DSQLITE_OMIT_LOAD_EXTENSION=1", "-DSQLITE_ENABLE_MATH_FUNCTIONS=1" },
});
} else {
parquet_unit_tests.root_module.linkSystemLibrary("sqlite3", .{});
}
const run_parquet_unit_tests = b.addRunArtifact(parquet_unit_tests);
unit_test_step.dependOn(&run_parquet_unit_tests.step);

// ─── --stats / --profile integration tests ──────────────────────────

// Integration test: --stats on basic CSV with mixed types
Expand Down
23 changes: 19 additions & 4 deletions src/parquet.zig
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,19 @@ fn decimalToText(value: i64, scale: i32, buf: *[64]u8) []const u8 {
// ponytail: manual int-to-text avoids bufPrint FixedWriter issues
return intToBuf(value, buf);
}
var pow10: i64 = 1;
var pow10: u64 = 1;
for (0..@as(usize, @intCast(scale))) |_| pow10 *= 10;
const int_part = @divTrunc(value, pow10);
const prefix = intToBuf(int_part, buf);
// ponytail: sign tracked independently (Issue #234) — @divTrunc(-1, 100)
// is 0, so intToBuf(int_part) alone drops the sign of |value| < pow10
const neg = value < 0;
const mag: u64 = @intCast(@abs(value));
if (neg) buf[0] = '-';
const off: usize = @intFromBool(neg);
const digits = intToBuf(@as(i64, @intCast(mag / pow10)), buf[off..]);
const prefix = buf[0 .. off + digits.len];
buf[prefix.len] = '.';
const dot_pos = prefix.len + 1;
var f = @as(u64, @intCast(@abs(@rem(value, pow10))));
var f = mag % pow10;
var pos: usize = @intCast(scale);
while (pos > 0) {
pos -= 1;
Expand Down Expand Up @@ -137,6 +143,15 @@ fn intToBuf(value: i64, buf: []u8) []const u8 {
return buf[0..end];
}

test "decimalToText: negative sub-unit value keeps its sign (Issue #234)" {
var buf: [64]u8 = undefined;
try std.testing.expectEqualStrings("-0.01", decimalToText(-1, 2, &buf));
try std.testing.expectEqualStrings("0.01", decimalToText(1, 2, &buf));
try std.testing.expectEqualStrings("-123.45", decimalToText(-12345, 2, &buf));
try std.testing.expectEqualStrings("-5", decimalToText(-5, 0, &buf));
try std.testing.expectEqualStrings("0.00", decimalToText(0, 2, &buf));
}

/// Map a Parquet physical type to a SQLite ColumnType (no logical type mapping).
fn physicalToAffinity(phys: parquet.format.PhysicalType) sqlite_mod.ColumnType {
return switch (phys) {
Expand Down
Loading