summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2020-12-20 23:55:24 +0100
committerGravatar Vincent Rischmann2020-12-21 00:18:06 +0100
commit5fb527a9032351a1a5611da2e717a1f19e9544d1 (patch)
treedd5c50587f449f080ab331848fad9e40f8f4e3cc /README.md
parentallow reading a bool field (diff)
downloadzig-sqlite-5fb527a9032351a1a5611da2e717a1f19e9544d1.tar.gz
zig-sqlite-5fb527a9032351a1a5611da2e717a1f19e9544d1.tar.xz
zig-sqlite-5fb527a9032351a1a5611da2e717a1f19e9544d1.zip
improve readme
Document the checks done at comptime
Diffstat (limited to 'README.md')
-rw-r--r--README.md216
1 files changed, 170 insertions, 46 deletions
diff --git a/README.md b/README.md
index 84fb96c..fd9799b 100644
--- a/README.md
+++ b/README.md
@@ -19,17 +19,23 @@ This package is a thin wrapper around [sqlite](https://sqlite.org/index.html)'s
19 19
20Since there's no package manager for Zig yet, the recommended way is to use a git submodule: 20Since there's no package manager for Zig yet, the recommended way is to use a git submodule:
21 21
22 $ git submodule add https://git.sr.ht/~vrischmann/zig-sqlite src/sqlite 22```bash
23$ git submodule add https://git.sr.ht/~vrischmann/zig-sqlite src/sqlite
24```
23 25
24Then add the following to your `build.zig` target(s): 26Then add the following to your `build.zig` target(s):
25 27
26 exe.linkLibC(); 28```zig
27 exe.linkSystemLibrary("sqlite3"); 29exe.linkLibC();
28 exe.addPackage(.{ .name = "sqlite", .path = "src/sqlite/sqlite.zig" }); 30exe.linkSystemLibrary("sqlite3");
31exe.addPackage(.{ .name = "sqlite", .path = "src/sqlite/sqlite.zig" });
32```
29 33
30Now you should be able to import sqlite like this: 34Now you should be able to import sqlite like this:
31 35
32 const sqlite = @import("sqlite"); 36```zig
37const sqlite = @import("sqlite");
38```
33 39
34## Usage 40## Usage
35 41
@@ -37,8 +43,10 @@ Now you should be able to import sqlite like this:
37 43
38You must create and initialize an instance of `sqlite.Db`: 44You must create and initialize an instance of `sqlite.Db`:
39 45
40 var db: sqlite.Db = undefined; 46```zig
41 try db.init(allocator, .{ .mode = sqlite.Db.Mode{ .File = "/home/vincent/mydata.db" } }); 47var db: sqlite.Db = undefined;
48try db.init(allocator, .{ .mode = sqlite.Db.Mode{ .File = "/home/vincent/mydata.db" } });
49```
42 50
43The `init` method takes an allocator and an optional tuple which will used to configure sqlite. 51The `init` method takes an allocator and an optional tuple which will used to configure sqlite.
44 52
@@ -48,12 +56,14 @@ Right now the only member used in that tuple is `mode` which defines if the sqli
48 56
49sqlite works exclusively by using prepared statements. The wrapper type is `sqlite.Statement`. Here is how you get one: 57sqlite works exclusively by using prepared statements. The wrapper type is `sqlite.Statement`. Here is how you get one:
50 58
51 const query = 59```zig
52 \\SELECT id, name, age, salary FROM employees WHERE age > ? AND age < ? 60const query =
53 ; 61 \\SELECT id, name, age, salary FROM employees WHERE age > ? AND age < ?
62;
54 63
55 var stmt = try db.prepare(query); 64var stmt = try db.prepare(query);
56 defer stmt.deinit(); 65defer stmt.deinit();
66```
57 67
58The `Db.prepare` method takes a `comptime` query string. 68The `Db.prepare` method takes a `comptime` query string.
59 69
@@ -61,17 +71,19 @@ The `Db.prepare` method takes a `comptime` query string.
61 71
62For queries which do not return data (`INSERT`, `UPDATE`) you can use the `exec` method: 72For queries which do not return data (`INSERT`, `UPDATE`) you can use the `exec` method:
63 73
64 const query = 74```zig
65 \\UPDATE foo SET salary = ? WHERE id = ? 75const query =
66 ; 76 \\UPDATE foo SET salary = ? WHERE id = ?
77;
67 78
68 var stmt = try db.prepare(query); 79var stmt = try db.prepare(query);
69 defer stmt.deinit(); 80defer stmt.deinit();
70 81
71 try stmt.exec({ 82try stmt.exec({
72 .salary = 20000, 83 .salary = 20000,
73 .id = 40, 84 .id = 40,
74 }); 85});
86```
75 87
76See the section "Bind parameters and resultset rows" for more information on the types mapping rules. 88See the section "Bind parameters and resultset rows" for more information on the types mapping rules.
77 89
@@ -79,26 +91,28 @@ See the section "Bind parameters and resultset rows" for more information on the
79 91
80For queries which do return data you can use the `all` method: 92For queries which do return data you can use the `all` method:
81 93
82 const query = 94```zig
83 \\SELECT id, name, age, salary FROM employees WHERE age > ? AND age < ? 95const query =
84 ; 96 \\SELECT id, name, age, salary FROM employees WHERE age > ? AND age < ?
85 97;
86 var stmt = try db.prepare(query); 98
87 defer stmt.deinit(); 99var stmt = try db.prepare(query);
88 100defer stmt.deinit();
89 const rows = try stmt.all( 101
90 struct { 102const rows = try stmt.all(
91 id: usize, 103 struct {
92 name: []const u8, 104 id: usize,
93 age: u16, 105 name: []const u8,
94 salary: u32, 106 age: u16,
95 }, 107 salary: u32,
96 .{ .allocator = allocator }, 108 },
97 .{ .age1 = 20, .age2 = 40 }, 109 .{ .allocator = allocator },
98 ); 110 .{ .age1 = 20, .age2 = 40 },
99 for (rows) |row| { 111);
100 std.log.debug("id: {} ; name: {}; age: {}; salary: {}", .{ row.id, row.name, row.age, row.salary }); 112for (rows) |row| {
101 } 113 std.log.debug("id: {} ; name: {}; age: {}; salary: {}", .{ row.id, row.name, row.age, row.salary });
114}
115```
102 116
103The `all` method takes a type and an optional tuple. 117The `all` method takes a type and an optional tuple.
104 118
@@ -127,8 +141,118 @@ Here are the rules for resultset rows:
127 141
128Note that arrays must have a sentinel because we need a way to communicate where the data actually stops in the array, so for example use `[200:0]u8` for a `TEXT` field. 142Note that arrays must have a sentinel because we need a way to communicate where the data actually stops in the array, so for example use `[200:0]u8` for a `TEXT` field.
129 143
130### Comptime checked statements 144## Comptime checks
131 145
132Prepared statements contain _comptime_ metadata which is used to validate that every call to `exec`, `one` and `all` provides the appropriate number of bind parameters. 146Prepared statements contain _comptime_ metadata which is used to validate every call to `exec`, `one` and `all` _at compile time_.
133 147
134Right now there's no _type_ checking of bind parameters but it could probably be done. 148### Check the number of bind parameters.
149
150The first check makes sure you provide the same number of bind parameters as there are bind markers in the query string.
151
152Take the following code:
153```zig
154var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?");
155defer stmt.deinit();
156
157const rows = try stmt.all(usize, .{ .allocator = allocator }, .{
158 .age_1 = 10,
159 .age_2 = 20,
160});
161_ = rows;
162```
163It fails with this compilation error:
164```
165/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:465:17: error: number of bind markers not equal to number of fields
166 @compileError("number of bind markers not equal to number of fields");
167 ^
168/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:543:22: note: called from here
169 self.bind(values);
170 ^
171/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:619:41: note: called from here
172 var iter = try self.iterator(Type, values);
173 ^
174./src/main.zig:16:30: note: called from here
175 const rows = try stmt.all(usize, .{ .allocator = allocator }, .{
176 ^
177./src/main.zig:5:29: note: called from here
178pub fn main() anyerror!void {
179```
180
181### Assign types to bind markers and check them.
182
183The second (and more interesting) check makes sure you provide appropriately typed values as bind parameters.
184
185This check is not automatic since with a standard SQL query we have no way to know the types of the bind parameters, to use it you must provide theses types in the SQL query with a custom syntax.
186
187For example, take the same code as above but now we also bind the last parameter:
188```zig
189var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?");
190defer stmt.deinit();
191
192const rows = try stmt.all(usize, .{ .allocator = allocator }, .{
193 .age_1 = 10,
194 .age_2 = 20,
195 .weight = false,
196});
197_ = rows;
198```
199
200This compiles correctly even if the `weight` field in our `user` table is of the type `INTEGER`.
201
202We can make sure the bind parameters have the right type if we rewrite the query like this:
203```zig
204var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?{usize}");
205defer stmt.deinit();
206
207const rows = try stmt.all(usize, .{ .allocator = allocator }, .{
208 .age_1 = 10,
209 .age_2 = 20,
210 .weight = false,
211});
212_ = rows;
213
214```
215Now this fails to compile:
216```
217/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:485:25: error: value type bool is not the bind marker type usize
218 @compileError("value type " ++ @typeName(struct_field.field_type) ++ " is not the bind marker type " ++ @typeName(typ));
219 ^
220/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:557:22: note: called from here
221 self.bind(values);
222 ^
223/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:633:41: note: called from here
224 var iter = try self.iterator(Type, values);
225 ^
226./src/main.zig:16:30: note: called from here
227 const rows = try stmt.all(usize, .{ .allocator = allocator }, .{
228 ^
229./src/main.zig:5:29: note: called from here
230pub fn main() anyerror!void {
231```
232The syntax is straightforward: a bind marker `?` followed by `{`, a Zig type name and finally `}`.
233
234There are a limited number of types allowed currently:
235 * all [integer](https://ziglang.org/documentation/master/#Primitive-Types) types.
236 * all [arbitrary bit-width integer](https://ziglang.org/documentation/master/#Primitive-Types) types.
237 * all [float](https://ziglang.org/documentation/master/#Primitive-Types) types.
238 * bool.
239 * strings with `[]const u8` or `[]u8`.
240 * strings with `sqlite.Text`.
241 * blobs with `sqlite.Blob`.
242
243It's probably possible to support arbitrary types if they can be marshaled to a sqlite type. This is something to investigate.
244
245**NOTE**: this is done at compile time and is quite CPU intensive, therefore it's possible you'll have to play with [@setEvalBranchQuota](https://ziglang.org/documentation/master/#setEvalBranchQuota) to make it compile.
246
247To finish our example, passing the proper type allows it compile:
248```zig
249var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?{usize}");
250defer stmt.deinit();
251
252const rows = try stmt.all(usize, .{ .allocator = allocator }, .{
253 .age_1 = 10,
254 .age_2 = 20,
255 .weight = @as(usize, 200),
256});
257_ = rows;
258```