Implement sql-pipe: CSV-to-SQLite query CLI in Zig#1
Conversation
Co-authored-by: vmvarela <11040851+vmvarela@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds sql-pipe, a Zig CLI that ingests CSV from stdin into an in-memory SQLite database and runs a user-provided SQL query, printing results to stdout.
Changes:
- Implemented CSV ingestion → dynamic
CREATE TABLE→ batchedINSERTvia prepared statement → query execution viasqlite3_execcallback. - Added Zig build configuration linking against system
sqlite3and libc. - Added package manifest and basic build output cache ignores.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| src/main.zig | Core CLI implementation: CSV→SQLite ingestion, batched inserts, query execution, stdout printing |
| build.zig | Zig build script creating the executable and linking sqlite3 + libc |
| build.zig.zon | Package manifest (name/version/fingerprint/min Zig version) |
| .gitignore | Ignores Zig build/cache output directories |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try sql.appendSlice(allocator, "CREATE TABLE t ("); | ||
| for (cols.items, 0..) |col, i| { | ||
| if (i > 0) try sql.appendSlice(allocator, ", "); | ||
| try sql.appendSlice(allocator, col); | ||
| try sql.appendSlice(allocator, " TEXT"); | ||
| } |
There was a problem hiding this comment.
CSV headers are appended directly into the CREATE TABLE statement without quoting/escaping as identifiers. This breaks on headers with spaces/reserved words and enables SQL injection via crafted header values. Quote identifiers (e.g., double quotes) and escape embedded quotes, and reject empty/invalid column names before building SQL.
src/main.zig
Outdated
| var errmsg: [*c]u8 = null; | ||
| if (c.sqlite3_exec(db, sql.items.ptr, null, null, &errmsg) != c.SQLITE_OK) { | ||
| std.debug.print("CREATE TABLE failed: {s}\n", .{std.mem.span(errmsg)}); | ||
| c.sqlite3_free(errmsg); |
There was a problem hiding this comment.
If sqlite3_exec fails, errmsg is not guaranteed to be non-null; calling std.mem.span(errmsg) will crash when SQLite doesn't provide an error string. Guard for null (and still return a useful message, e.g., using sqlite3_errmsg(db)).
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ase drafter, workflows)
- Quote CSV column names as SQL identifiers ("col") in CREATE TABLE to
prevent SQL injection and support headers with spaces/reserved words
- Guard errmsg null check before std.mem.span in CREATE TABLE error path,
falling back to sqlite3_errmsg(db) for diagnostics
- Fix broken insert loop: remove duplicated variable declarations, add
missing sqlite3_step call with error handling, use SQLITE_STATIC
(safe since step is called before buffer is freed)
- Replace SQLITE_TRANSIENT with SQLITE_STATIC (null) to work around
Zig 0.15 alignment constraint on function pointer casts from usize
- build.zig: -Dbundle-sqlite=true compiles sqlite3.c from lib/ enabling
full cross-compilation with no system dependencies
- ci.yml: build + integration test on ubuntu/macos/windows
- release.yml: 16-target release matrix —
Linux: x86_64, aarch64, armv7, riscv64, x86 (musl/static)
macOS: x86_64, aarch64
Windows: x86_64, aarch64, x86 (MinGW)
FreeBSD: x86_64, aarch64
NetBSD: x86_64, aarch64
OpenBSD: x86_64
DragonFly: x86_64
Attaches binaries + sha256sums.txt to published releases
- .gitignore: exclude lib/ and sqlite.zip
- README: update build instructions; remove system sqlite3 limitation
Implements a zero-dependency CLI that pipes CSV from stdin into an in-memory SQLite database and executes a user-provided SQL query, printing results to stdout.
New files
src/main.zig— Core implementation:GeneralPurposeAllocatorfor memory;@cImport(@cInclude("sqlite3.h"))for SQLite bindingsCREATE TABLE t (col1 TEXT, col2 TEXT, ...)BEGIN TRANSACTION/COMMIT; statement prepared once viasqlite3_prepare_v2, values bound per row withsqlite3_bind_textusingSQLITE_TRANSIENTsqlite3_execwith a C callback printing comma-separated rows to stdoutdeferbuild.zig— Zig 0.15 build usingb.createModule()API; linkssqlite3+ libcbuild.zig.zon— Package manifest (fingerprint required by Zig 0.15).gitignore— Excludes.zig-cache/andzig-out/Usage
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
ziglang.org/usr/bin/curl curl -s REDACTED(dns block)/usr/bin/wget wget -q REDACTED -O zig.tar.xz(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.