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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ $ cat events.csv \
| `-H`, `--header` | Print column names as the first output row |
| `--json` | Output results as a JSON array of objects (mutually exclusive with `-H`) |
| `--max-rows <n>` | Stop if more than `n` data rows are read (exit 1) |
| `--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. |
| `-v`, `--verbose` | Print `Loaded <n> rows in <t>s` to stderr after loading (always on TTY; forced with flag) |
| `-h`, `--help` | Show usage help and exit |
| `-V`, `--version` | Print version and exit |
Expand Down
74 changes: 74 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,80 @@ pub fn build(b: *std.Build) void {
test_verbose_short.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_verbose_short.step);

// Integration test 33: --columns prints column names one per line and exits 0
const test_columns_basic = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'id,region,amount\n1,east,100\n' | ./zig-out/bin/sql-pipe --columns | diff - <(printf 'id\nregion\namount\n')
});
test_columns_basic.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_columns_basic.step);

// Integration test 34: --columns --verbose prints "name TYPE" lines
const test_columns_verbose = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'id,name,amount\n1,Alice,3.14\n2,Bob,2.72\n' | ./zig-out/bin/sql-pipe --columns --verbose | diff - <(printf 'id INTEGER\nname TEXT\namount REAL\n')
});
test_columns_verbose.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_columns_verbose.step);

// Integration test 35: --columns works with --delimiter
const test_columns_delimiter = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'a|b|c\n1|2|3\n' | ./zig-out/bin/sql-pipe --columns -d '|' | diff - <(printf 'a\nb\nc\n')
});
test_columns_delimiter.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_columns_delimiter.step);

// Integration test 36: --columns works with --tsv
const test_columns_tsv = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'col1\tcol2\tcol3\n' | ./zig-out/bin/sql-pipe --columns --tsv | diff - <(printf 'col1\ncol2\ncol3\n')
});
test_columns_tsv.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_columns_tsv.step);

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

// Integration test 38: --columns --verbose with malformed CSV exits 2
const test_columns_verbose_bad_csv = b.addSystemCommand(&.{
"bash", "-c",
\\msg=$(printf 'a,b\n"unterminated' | ./zig-out/bin/sql-pipe --columns --verbose 2>&1 >/dev/null; echo "EXIT:$?")
\\echo "$msg" | grep -q 'row 2: unterminated quoted field' && echo "$msg" | grep -q 'EXIT:2'
});
test_columns_verbose_bad_csv.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_columns_verbose_bad_csv.step);

// Integration test 39: --columns --verbose with header-only (no data rows) → all TEXT
const test_columns_verbose_no_data = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'id,name\n' | ./zig-out/bin/sql-pipe --columns --verbose | diff - <(printf 'id TEXT\nname TEXT\n')
});
test_columns_verbose_no_data.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_columns_verbose_no_data.step);

// Integration test 40: --columns with empty stdin exits 2
const test_columns_empty_stdin = b.addSystemCommand(&.{
"bash", "-c",
\\printf '' | ./zig-out/bin/sql-pipe --columns 2>/dev/null; test $? -eq 2
});
test_columns_empty_stdin.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_columns_empty_stdin.step);

// Integration test 41: -v is a valid alias for --verbose with --columns
const test_columns_short_verbose = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'id,name\n1,Alice\n' | ./zig-out/bin/sql-pipe --columns -v | diff - <(printf 'id INTEGER\nname TEXT\n')
});
test_columns_short_verbose.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_columns_short_verbose.step);

// Unit tests for the RFC 4180 CSV parser (src/csv.zig)
const unit_tests = b.addTest(.{
.root_module = b.createModule(.{
Expand Down
8 changes: 8 additions & 0 deletions docs/sql-pipe.1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ NAME

SYNOPSIS
*sql-pipe* [OPTIONS] <query>
*sql-pipe* --columns [OPTIONS]

DESCRIPTION
sql-pipe reads CSV data from standard input, loads it into an in-memory SQLite
Expand Down Expand Up @@ -57,6 +58,13 @@ OPTIONS
code 1 and an error message. Use this to guard against
accidentally piping extremely large files into memory.

*--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* /
*--verbose*, also shows the inferred type (INTEGER, REAL, or TEXT)
for each column, using the first 100 data rows for inference. Respects
*--delimiter* and *--tsv*. Mutually exclusive with a query argument.

*-h, --help*
Print the help message and exit with code 0.

Expand Down
Loading
Loading