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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,11 @@ $ cat events.csv \
| `--columns` | Read the CSV header row, print each column name on its own line, and exit 0. With `-v`/`--verbose`, also shows the inferred type per column (`name INTEGER`). Respects `--delimiter` and `--tsv`. Mutually exclusive with a query argument. |
| `--output <file>` | Write results to the given file instead of stdout. Creates or overwrites the file. Exits 1 if the file cannot be created. |
| `-v`, `--verbose` | Print `Loaded <n> rows in <t>s` to stderr after loading (always on TTY; forced with flag) |
| `-s`, `--silent` | Suppress `Loaded <n> rows in <t>s` and the progress counter from stderr unconditionally. Cannot be combined with `-v`/`--verbose` |
| `-h`, `--help` | Show usage help and exit |
| `-V`, `--version` | Print version and exit |

After loading, `sql-pipe` prints `Loaded <n> rows in <t>s` to stderr whenever stderr is a TTY (interactive terminal). The message is suppressed in scripts and pipes to keep them noise-free. Use `-v` / `--verbose` to force it regardless of TTY:
After loading, `sql-pipe` prints `Loaded <n> rows in <t>s` to stderr whenever stderr is a TTY (interactive terminal). The message is suppressed in scripts and pipes to keep them noise-free. Use `-v` / `--verbose` to force it regardless of TTY, or `-s` / `--silent` to suppress it unconditionally (e.g. when stderr is a TTY but you want clean output):

```sh
$ cat sales.csv | sql-pipe --verbose 'SELECT region, SUM(revenue) FROM t GROUP BY region'
Expand Down
37 changes: 37 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,43 @@ pub fn build(b: *std.Build) void {
test_tsv_quoted_tab.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_tsv_quoted_tab.step);

// Integration test 71: --silent suppresses row count output to stderr
const test_silent = b.addSystemCommand(&.{
"bash", "-c",
\\out=$(printf 'name,age\nAlice,30\n' | ./zig-out/bin/sql-pipe --silent 'SELECT name FROM t' 2>&1 >/dev/null)
\\test -z "$out"
});
test_silent.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_silent.step);

// Integration test 72: -s is an alias for --silent
const test_silent_short = b.addSystemCommand(&.{
"bash", "-c",
\\result=$(printf 'name,age\nAlice,30\n' | ./zig-out/bin/sql-pipe -s 'SELECT name FROM t')
\\expected=$(printf 'Alice\n')
\\[ "$result" = "$expected" ]
});
test_silent_short.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_silent_short.step);

// Integration test 73: --silent combined with --verbose exits 1 with error
const test_silent_verbose_conflict = b.addSystemCommand(&.{
"bash", "-c",
\\msg=$(printf 'a\n1\n' | ./zig-out/bin/sql-pipe --silent --verbose 'SELECT * FROM t' 2>&1 >/dev/null; echo "EXIT:$?")
\\echo "$msg" | grep -q 'error: --silent cannot be combined with --verbose' && echo "$msg" | grep -q 'EXIT:1'
});
test_silent_verbose_conflict.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_silent_verbose_conflict.step);

// Integration test 74: --silent combined with -v exits 1 with error
const test_silent_v_conflict = b.addSystemCommand(&.{
"bash", "-c",
\\msg=$(printf 'a\n1\n' | ./zig-out/bin/sql-pipe --silent -v 'SELECT * FROM t' 2>&1 >/dev/null; echo "EXIT:$?")
\\echo "$msg" | grep -q 'error: --silent cannot be combined with --verbose' && echo "$msg" | grep -q 'EXIT:1'
});
test_silent_v_conflict.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_silent_v_conflict.step);

// Unit tests for the RFC 4180 CSV parser (src/csv.zig)
const unit_tests = b.addTest(.{
.root_module = b.createModule(.{
Expand Down
12 changes: 12 additions & 0 deletions docs/sql-pipe.1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ OPTIONS
code 1 and an error message. Use this to guard against
accidentally piping extremely large files into memory.

*-v, --verbose*
Print "Loaded <n> rows in <t>s" to standard error after all rows
are inserted. The message is shown automatically when stderr is a
TTY; this flag forces it unconditionally (e.g. in scripts or
pipes). Cannot be combined with *-s* / *--silent*.

*-s, --silent*
Suppress the "Loaded <n> rows in <t>s" message and the loading
progress counter from standard error unconditionally, even when
stderr is a TTY. Useful for producing clean stderr in interactive
terminals. Cannot be combined with *-v* / *--verbose*.

*--columns*
Read the CSV header row, print each column name on its own line to
standard output, and exit with code 0. When combined with *-v* /
Expand Down
23 changes: 21 additions & 2 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const SqlPipeError = error{
MissingQuery,
InvalidDelimiter,
IncompatibleFlags,
SilentVerboseConflict,
ColumnsWithQuery,
InvalidMaxRows,
InvalidInputFormat,
Expand All @@ -31,7 +32,6 @@ const SqlPipeError = error{
PrepareInsertFailed,
BindFailed,
StepFailed,
CommitFailed,
PrepareQueryFailed,
InvalidOutputPath,
OutputWithColumns,
Expand Down Expand Up @@ -85,6 +85,8 @@ const ParsedArgs = struct {
/// Print "Loaded <n> rows" to stderr after all rows are inserted when true.
/// When false, the message is still shown automatically when stderr is a TTY.
verbose: bool,
/// Suppress "Loaded <n> rows" unconditionally.
silent: bool,
/// Write results to this file path instead of stdout; null = write to stdout.
output: ?[]const u8,
};
Expand Down Expand Up @@ -134,6 +136,8 @@ fn printUsage(writer: *std.Io.Writer) !void {
\\ --max-rows <n> Stop if more than <n> data rows are read (exit 1)
\\ -v, --verbose Force row count to stderr (shown automatically on TTY)
\\ With --columns: show inferred type per column
\\ -s, --silent Suppress row count output unconditionally
\\ Cannot be combined with -v/--verbose
\\ --columns List column names from input header (one per line) and exit
\\ Combine with -v/--verbose to include inferred types
\\ Cannot be combined with --output or a query argument
Expand Down Expand Up @@ -212,6 +216,7 @@ fn parseArgs(args: []const [:0]const u8) SqlPipeError!ArgsResult {

var max_rows: ?usize = null;
var verbose = false;
var silent = false;
var list_columns = false;
var output: ?[]const u8 = null;

Expand Down Expand Up @@ -273,6 +278,8 @@ fn parseArgs(args: []const [:0]const u8) SqlPipeError!ArgsResult {
if (max_rows.? == 0) return error.InvalidMaxRows;
} else if (std.mem.eql(u8, arg, "--verbose") or std.mem.eql(u8, arg, "-v")) {
verbose = true;
} else if (std.mem.eql(u8, arg, "--silent") or std.mem.eql(u8, arg, "-s")) {
silent = true;
} else if (std.mem.eql(u8, arg, "--columns")) {
list_columns = true;
} else if (std.mem.eql(u8, arg, "--output")) {
Expand Down Expand Up @@ -302,6 +309,10 @@ fn parseArgs(args: []const [:0]const u8) SqlPipeError!ArgsResult {
if (list_columns and query != null)
return error.ColumnsWithQuery;

// --silent and --verbose are mutually exclusive
if (silent and verbose)
return error.SilentVerboseConflict;

// --columns mode: list headers and exit
if (list_columns)
return .{ .columns = ColumnsArgs{
Expand All @@ -319,6 +330,7 @@ fn parseArgs(args: []const [:0]const u8) SqlPipeError!ArgsResult {
.output_format = output_format,
.max_rows = max_rows,
.verbose = verbose,
.silent = silent,
.output = output,
} };
}
Expand Down Expand Up @@ -1355,7 +1367,7 @@ fn run(

// Print row count and elapsed time to stderr when stderr is a TTY or --verbose is set.
const is_tty = std.Io.File.isTty(std.Io.File.stderr(), io) catch false;
if (parsed.verbose or is_tty) {
if (!parsed.silent and (parsed.verbose or is_tty)) {
const end_ts = std.Io.Timestamp.now(io, .awake);
const elapsed_ns: i96 = end_ts.nanoseconds - start_ts.nanoseconds;
const elapsed_ms: u64 = @intCast(@max(@as(i96, 0), @divTrunc(elapsed_ns, std.time.ns_per_ms)));
Expand Down Expand Up @@ -1407,6 +1419,13 @@ pub fn main(init: std.process.Init.Minimal) void {
stderr_writer.flush() catch |ferr| std.log.err("failed to flush: {}", .{ferr});
std.process.exit(@intFromEnum(ExitCode.usage));
},
error.SilentVerboseConflict => {
stderr_writer.writeAll(
"error: --silent cannot be combined with --verbose\n",
) catch |werr| std.log.err("failed to write error message: {}", .{werr});
stderr_writer.flush() catch |ferr| std.log.err("failed to flush: {}", .{ferr});
std.process.exit(@intFromEnum(ExitCode.usage));
},
error.InvalidMaxRows => {
stderr_writer.writeAll("error: --max-rows must be a positive integer\n") catch |werr| {
std.log.err("failed to write error message: {}", .{werr});
Expand Down
Loading