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 @@ -211,6 +211,7 @@ $ cat events.csv \
| `-H`, `--header` | Print column names as the first output row |
| `--json` | Alias for `--output-format json` (mutually exclusive with `-H`) |
| `--max-rows <n>` | Stop if more than `n` data rows are read (exit 1) |
| `--validate` | Parse the entire input and print a summary (`OK: <n> rows, <m> columns (col TYPE, ...)`) to stdout. Exit 0 on success, exit 2 on parse error. No query required. Compatible with `--delimiter`, `--tsv`, `--no-type-inference`, `-I`/`--input-format` (csv, tsv, json, ndjson). JSON/NDJSON columns are reported as TEXT. |
| `--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) |
Expand Down
96 changes: 96 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,102 @@ pub fn build(b: *std.Build) void {
test_silent_v_conflict.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_silent_v_conflict.step);

// Integration test 75: --validate on valid CSV prints OK summary and exits 0
const test_validate_ok = b.addSystemCommand(&.{
"bash", "-c",
\\result=$(printf 'id,name,amount\n1,Alice,3.14\n2,Bob,2.72\n' | ./zig-out/bin/sql-pipe --validate)
\\expected='OK: 2 rows, 3 columns (id INTEGER, name TEXT, amount REAL)'
\\[ "$result" = "$expected" ]
});
test_validate_ok.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_validate_ok.step);

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

// Integration test 77: --validate with custom delimiter
const test_validate_delimiter = b.addSystemCommand(&.{
"bash", "-c",
\\result=$(printf 'id|name|amount\n1|Alice|3.14\n' | ./zig-out/bin/sql-pipe --validate --delimiter '|')
\\expected='OK: 1 rows, 3 columns (id INTEGER, name TEXT, amount REAL)'
\\[ "$result" = "$expected" ]
});
test_validate_delimiter.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_validate_delimiter.step);

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

// Integration test 79: --validate on valid JSON array
const test_validate_json = b.addSystemCommand(&.{
"bash", "-c",
\\result=$(printf '[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]' \
\\ | ./zig-out/bin/sql-pipe --validate -I json)
\\expected='OK: 2 rows, 2 columns (id TEXT, name TEXT)'
\\[ "$result" = "$expected" ]
});
test_validate_json.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_validate_json.step);

// Integration test 80: --validate on valid NDJSON
const test_validate_ndjson = b.addSystemCommand(&.{
"bash", "-c",
\\result=$(printf '{"id":1,"name":"Alice"}\n{"id":2,"name":"Bob"}\n' \
\\ | ./zig-out/bin/sql-pipe --validate -I ndjson)
\\expected='OK: 2 rows, 2 columns (id TEXT, name TEXT)'
\\[ "$result" = "$expected" ]
});
test_validate_ndjson.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_validate_ndjson.step);

// Integration test 81: --validate on invalid JSON exits 2
const test_validate_json_error = b.addSystemCommand(&.{
"bash", "-c",
\\msg=$(printf '[{"id":1, broken}]' | ./zig-out/bin/sql-pipe --validate -I json 2>&1 >/dev/null; echo "EXIT:$?")
\\echo "$msg" | grep -q 'EXIT:2'
});
test_validate_json_error.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_validate_json_error.step);

// Integration test 82: --validate --output exits 1 with error
const test_validate_output_conflict = b.addSystemCommand(&.{
"bash", "-c",
\\msg=$(printf 'a,b\n1,2\n' | ./zig-out/bin/sql-pipe --validate --output /tmp/x 2>&1 >/dev/null; echo "EXIT:$?")
\\echo "$msg" | grep -q 'error: --output cannot be combined with --validate' && echo "$msg" | grep -q 'EXIT:1'
});
test_validate_output_conflict.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_validate_output_conflict.step);

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

// Integration test 84: --validate on invalid NDJSON exits 2
const test_validate_ndjson_error = b.addSystemCommand(&.{
"bash", "-c",
\\msg=$(printf '{"id":1}\n{broken}\n' | ./zig-out/bin/sql-pipe --validate -I ndjson 2>&1 >/dev/null; echo "EXIT:$?")
\\echo "$msg" | grep -q 'EXIT:2'
});
test_validate_ndjson_error.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_validate_ndjson_error.step);

// Unit tests for the RFC 4180 CSV parser (src/csv.zig)
const unit_tests = b.addTest(.{
.root_module = b.createModule(.{
Expand Down
10 changes: 10 additions & 0 deletions docs/sql-pipe.1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ OPTIONS
stderr is a TTY. Useful for producing clean stderr in interactive
terminals. Cannot be combined with *-v* / *--verbose*.

*--validate*
Parse the entire input without executing a SQL query. On success,
prints a one-line summary to standard output:
*OK: <n> rows, <m> columns (<col> <TYPE>, ...)* and exits 0.
On parse error, prints the error message and exits 2. Compatible
with *--delimiter*, *--tsv*, *--no-type-inference*, and
*-I* / *--input-format* (csv, tsv, json, ndjson). JSON and NDJSON
columns are reported as TEXT. Mutually exclusive with a query
argument.

*--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
Loading
Loading