-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.zig
More file actions
228 lines (198 loc) · 10.2 KB
/
build.zig
File metadata and controls
228 lines (198 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Use -Dbundle-sqlite=true to compile sqlite3.c from lib/ instead of
// linking the system library. Required for cross-compilation.
const bundle_sqlite = b.option(
bool,
"bundle-sqlite",
"Compile SQLite from lib/sqlite3.c (enables cross-compilation)",
) orelse false;
// Version: release CI injects from git tag with -Dversion=X.Y.Z
const version = b.option(
[]const u8,
"version",
"Override version string (default: from build.zig.zon)",
) orelse "0.2.0";
const exe = b.addExecutable(.{
.name = "sql-pipe",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
// Inject version string as a compile-time option accessible via @import("build_options")
const build_options = b.addOptions();
build_options.addOption([]const u8, "version", version);
exe.root_module.addOptions("build_options", build_options);
if (bundle_sqlite) {
exe.addIncludePath(b.path("lib"));
exe.addCSourceFile(.{
.file = b.path("lib/sqlite3.c"),
.flags = &.{"-DSQLITE_OMIT_LOAD_EXTENSION=1"},
});
} else {
exe.root_module.linkSystemLibrary("sqlite3", .{});
}
b.installArtifact(exe);
// Generate man page from scdoc source if scdoc (and gzip) are available (optional dependencies)
const man_step = b.step("man", "Generate man page with scdoc (optional)");
// Portable alternative: use a simple shell command that checks for scdoc/gzip availability
// The build step itself is lightweight and depends only on bash (standard on CI/Unix systems)
const build_man = b.addSystemCommand(&.{
"bash", "-c",
\\if command -v scdoc >/dev/null 2>&1 && command -v gzip >/dev/null 2>&1; then
\\ mkdir -p zig-out/share/man/man1
\\ scdoc < docs/sql-pipe.1.scd | gzip -c > zig-out/share/man/man1/sql-pipe.1.gz
\\ echo "✓ Generated man page: zig-out/share/man/man1/sql-pipe.1.gz"
\\else
\\ echo "⚠ scdoc and/or gzip not found. Install them to generate man page."
\\ echo " Manual source: docs/sql-pipe.1.scd"
\\fi
});
man_step.dependOn(&build_man.step);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
// Integration test 1: type inference — numeric comparisons work without CAST
const test_infer = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name,age\nAlice,30\nBob,25\nCarol,35' | ./zig-out/bin/sql-pipe 'SELECT name FROM t WHERE age > 27' | diff - <(printf 'Alice\nCarol\n')
});
test_infer.step.dependOn(b.getInstallStep());
// Integration test 2: --no-type-inference preserves legacy TEXT behavior
// With TEXT comparison: "9" > "2" is true, but "10" > "2" is false (string: "1" < "2")
// So only Alice is returned, proving string comparison is used
const test_no_infer = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name,val\nAlice,9\nBob,10\n' | ./zig-out/bin/sql-pipe --no-type-inference 'SELECT name FROM t WHERE val > 2 ORDER BY name' | diff - <(printf 'Alice\n')
});
test_no_infer.step.dependOn(b.getInstallStep());
// Integration test 3: max/min on REAL columns return numeric results
const test_real = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'item,price\nA,9.99\nB,3.00\nC,12.50\n' | ./zig-out/bin/sql-pipe 'SELECT max(price), min(price) FROM t' | diff - <(printf '12.5,3.0\n')
});
test_real.step.dependOn(b.getInstallStep());
const test_step = b.step("test", "Run integration tests");
test_step.dependOn(&test_infer.step);
test_step.dependOn(&test_no_infer.step);
test_step.dependOn(&test_real.step);
// Integration test 4: --help flag prints usage to stderr and exits 0
const test_help = b.addSystemCommand(&.{
"bash", "-c",
\\./zig-out/bin/sql-pipe --help 2>&1 >/dev/null | grep -q 'Usage: sql-pipe'
});
test_help.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_help.step);
// Integration test 5: --version flag prints version to stderr and exits 0
const test_version = b.addSystemCommand(&.{
"bash", "-c",
\\./zig-out/bin/sql-pipe --version 2>&1 >/dev/null | grep -q 'sql-pipe'
});
test_version.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_version.step);
// Integration test 6: missing query exits with code 1
const test_missing_query = b.addSystemCommand(&.{
"bash", "-c",
\\./zig-out/bin/sql-pipe 2>/dev/null; test $? -eq 1
});
test_missing_query.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_missing_query.step);
// Integration test 7: SQL error exits with code 3 and prefixes with "error:"
const test_sql_error = b.addSystemCommand(&.{
"bash", "-c",
\\msg=$(printf 'a\n1\n' | ./zig-out/bin/sql-pipe 'SELECT * FROM nonexistent' 2>&1 >/dev/null; echo "EXIT:$?"); echo "$msg" | grep -q '^error:' && echo "$msg" | grep -q 'EXIT:3'
});
test_sql_error.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_sql_error.step);
// Integration test 8: --delimiter "|" reads pipe-separated input; output is always CSV
const test_delimiter_pipe = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name|age\nAlice|30\nBob|25\n' | ./zig-out/bin/sql-pipe --delimiter '|' 'SELECT name, age FROM t ORDER BY age' | diff - <(printf 'Bob,25\nAlice,30\n')
});
test_delimiter_pipe.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_delimiter_pipe.step);
// Integration test 9: --delimiter "\t" reads tab-separated input
const test_delimiter_tab = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name\tage\nAlice\t30\nBob\t25\n' | ./zig-out/bin/sql-pipe --delimiter '\t' 'SELECT name, age FROM t ORDER BY age' | diff - <(printf 'Bob,25\nAlice,30\n')
});
test_delimiter_tab.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_delimiter_tab.step);
// Integration test 10: --tsv is an alias for --delimiter "\t"
const test_tsv = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name\tage\nAlice\t30\nBob\t25\n' | ./zig-out/bin/sql-pipe --tsv 'SELECT name, age FROM t ORDER BY age' | diff - <(printf 'Bob,25\nAlice,30\n')
});
test_tsv.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_tsv.step);
// Integration test 11: --header includes column names as the first output row
const test_header = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name,age\nAlice,30\nBob,25\n' | ./zig-out/bin/sql-pipe --header 'SELECT name, age FROM t ORDER BY age' | diff - <(printf 'name,age\nBob,25\nAlice,30\n')
});
test_header.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_header.step);
// Integration test 12: --header combined with --delimiter (pipe-separated input, CSV output with header)
const test_header_delimiter = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name|age\nAlice|30\nBob|25\n' | ./zig-out/bin/sql-pipe --header --delimiter '|' 'SELECT name, age FROM t ORDER BY age' | diff - <(printf 'name,age\nBob,25\nAlice,30\n')
});
test_header_delimiter.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_header_delimiter.step);
// Integration test 13: default behavior (no flags) produces CSV without header
const test_default_no_header = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name,age\nAlice,30\nBob,25\n' | ./zig-out/bin/sql-pipe 'SELECT name, age FROM t ORDER BY name' | diff - <(printf 'Alice,30\nBob,25\n')
});
test_default_no_header.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_default_no_header.step);
// Integration test 14: --json emits a JSON array of objects
const test_json = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name,age\nAlice,30\nBob,25\n' | ./zig-out/bin/sql-pipe --json 'SELECT name, age FROM t ORDER BY age' | diff - <(printf '[{"name":"Bob","age":25},{"name":"Alice","age":30}]\n')
});
test_json.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_json.step);
// Integration test 15: --json NULL values are rendered as JSON null
const test_json_null = b.addSystemCommand(&.{
"bash", "-c",
\\exp='[{"name":"Alice","score":null},{"name":"Bob","score":9.5}]'
\\printf 'name,score\nAlice,\nBob,9.5\n' | ./zig-out/bin/sql-pipe --json 'SELECT name, score FROM t ORDER BY name' | diff - <(printf '%s\n' "$exp")
});
test_json_null.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_json_null.step);
// Integration test 16: --json empty result set produces []
const test_json_empty = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name,age\n' | ./zig-out/bin/sql-pipe --json 'SELECT * FROM t' | diff - <(printf '[]\n')
});
test_json_empty.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_json_empty.step);
// Integration test 17: --json is mutually exclusive with --header (exits 1)
const test_json_incompatible = b.addSystemCommand(&.{
"bash", "-c",
\\printf 'name,age\nAlice,30\n' | ./zig-out/bin/sql-pipe --json --header 'SELECT * FROM t'; test $? -eq 1
});
test_json_incompatible.step.dependOn(b.getInstallStep());
test_step.dependOn(&test_json_incompatible.step);
// Unit tests for the RFC 4180 CSV parser (src/csv.zig)
const unit_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/csv.zig"),
.target = target,
.optimize = optimize,
}),
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const unit_test_step = b.step("unit-test", "Run CSV unit tests");
unit_test_step.dependOn(&run_unit_tests.step);
}