From 8944c3fd59ad3c3264542bf6b99e4da90b0c6151 Mon Sep 17 00:00:00 2001 From: Victor M Varela Date: Sat, 12 Sep 2026 10:15:42 +0200 Subject: [PATCH 1/2] fix: panic on empty no-op query (Issue #228) --- src/format.zig | 6 +++--- src/main.zig | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/format.zig b/src/format.zig index b11caeb..eb3202c 100644 --- a/src/format.zig +++ b/src/format.zig @@ -143,11 +143,11 @@ pub const OutputWriter = struct { /// XML: writes the XML declaration and opening root element /// CSV/TSV: writes an optional header row (when opts.header = true) /// - /// Pre: stmt is a valid prepared statement; col_count = sqlite3_column_count(stmt) + /// Pre: stmt is a valid prepared statement or null when query compiled to no statement (e.g. ";"); col_count = 0 in that case pub fn begin( self: *OutputWriter, allocator: std.mem.Allocator, - stmt: *c.sqlite3_stmt, + stmt: ?*c.sqlite3_stmt, col_count: c_int, writer: *std.Io.Writer, ) !void { @@ -166,7 +166,7 @@ pub const OutputWriter = struct { }, .csv, .tsv => { if (self.opts.header and col_count > 0) - try csvPrintHeaderRow(stmt, col_count, writer, self.csvDelimiter()); + try csvPrintHeaderRow(stmt.?, col_count, writer, self.csvDelimiter()); }, .markdown => unreachable, // handled before OutputWriter in execQuery } diff --git a/src/main.zig b/src/main.zig index 03265c7..17193fb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -103,13 +103,15 @@ fn writeWithChecksum( fn writeStreaming( out_writer: *format.OutputWriter, allocator: std.mem.Allocator, - stmt: *c.sqlite3_stmt, + stmt: ?*c.sqlite3_stmt, col_count: c_int, writer: *std.Io.Writer, ) !void { try out_writer.begin(allocator, stmt, col_count, writer); - while (c.sqlite3_step(stmt) == c.SQLITE_ROW) { - try out_writer.writeRow(stmt, writer); + if (stmt) |s| { + while (c.sqlite3_step(s) == c.SQLITE_ROW) { + try out_writer.writeRow(s, writer); + } } try out_writer.end(writer); } @@ -258,7 +260,13 @@ pub fn execQuery( return error.PrepareQueryFailed; defer _ = c.sqlite3_finalize(stmt); - const col_count = c.sqlite3_column_count(stmt); + // ponytail: prepare ";" succeeds with NULL stmt — empty result, no panic + if (stmt == null) { + if (use_table) return; + if (output_format == .markdown) return; + } + + const col_count: c_int = if (stmt) |s| c.sqlite3_column_count(s) else 0; // Table mode: buffer all rows and print a formatted table if (use_table) { @@ -282,7 +290,7 @@ pub fn execQuery( }); defer out_writer.deinit(allocator); - try writeWithChecksum(allocator, writer, stderr_writer, checksum, .{ &out_writer, allocator, stmt.?, col_count }, writeStreaming); + try writeWithChecksum(allocator, writer, stderr_writer, checksum, .{ &out_writer, allocator, stmt, col_count }, writeStreaming); } /// loadInput(allocator, io, db, table_name, input_format, reader, parsed, stderr_writer) → usize From c27fb837069f16b7bacde385ea663d70961842db Mon Sep 17 00:00:00 2001 From: Victor M Varela Date: Sat, 12 Sep 2026 10:18:12 +0200 Subject: [PATCH 2/2] shrink: collapse null guard to one line --- src/main.zig | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main.zig b/src/main.zig index 17193fb..de4435b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -261,10 +261,7 @@ pub fn execQuery( defer _ = c.sqlite3_finalize(stmt); // ponytail: prepare ";" succeeds with NULL stmt — empty result, no panic - if (stmt == null) { - if (use_table) return; - if (output_format == .markdown) return; - } + if (stmt == null and (use_table or output_format == .markdown)) return; const col_count: c_int = if (stmt) |s| c.sqlite3_column_count(s) else 0;