summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2020-12-28 23:23:24 +0100
committerGravatar Vincent Rischmann2020-12-31 14:42:10 +0100
commitfaad7cb2da89a49082a25854b945314960f506c9 (patch)
tree147034fa2224e0a3f6993cc1cd55e850e1506db7 /README.md
parentadd pragmaAlloc (diff)
downloadzig-sqlite-faad7cb2da89a49082a25854b945314960f506c9.tar.gz
zig-sqlite-faad7cb2da89a49082a25854b945314960f506c9.tar.xz
zig-sqlite-faad7cb2da89a49082a25854b945314960f506c9.zip
readme: document both allocating and non-allocating methods
Diffstat (limited to '')
-rw-r--r--README.md177
1 files changed, 101 insertions, 76 deletions
diff --git a/README.md b/README.md
index 452e3e7..cdebc3c 100644
--- a/README.md
+++ b/README.md
@@ -2,13 +2,13 @@
2 2
3This package is a thin wrapper around [sqlite](https://sqlite.org/index.html)'s C API. 3This package is a thin wrapper around [sqlite](https://sqlite.org/index.html)'s C API.
4 4
5## Status 5# Status
6 6
7While the core functionality works right now, the API is still subject to changes. 7While the core functionality works right now, the API is still subject to changes.
8 8
9If you use this library, expect to have to make changes when you update the code. 9If you use this library, expect to have to make changes when you update the code.
10 10
11## Requirements 11# Requirements
12 12
13* [Zig master](https://ziglang.org/download/) 13* [Zig master](https://ziglang.org/download/)
14* Linux 14* Linux
@@ -16,12 +16,12 @@ If you use this library, expect to have to make changes when you update the code
16 * `libsqlite3-dev` for Debian and derivatives 16 * `libsqlite3-dev` for Debian and derivatives
17 * `sqlite3-devel` for Fedora 17 * `sqlite3-devel` for Fedora
18 18
19## Features 19# Features
20 20
21* Preparing, executing statements 21* Preparing, executing statements
22* comptime checked bind parameters 22* comptime checked bind parameters
23 23
24## Installation 24# Installation
25 25
26Since there's no package manager for Zig yet, the recommended way is to use a git submodule: 26Since there's no package manager for Zig yet, the recommended way is to use a git submodule:
27 27
@@ -43,9 +43,9 @@ Now you should be able to import sqlite like this:
43const sqlite = @import("sqlite"); 43const sqlite = @import("sqlite");
44``` 44```
45 45
46## Usage 46# Usage
47 47
48### Initialization 48## Initialization
49 49
50You must create and initialize an instance of `sqlite.Db`: 50You must create and initialize an instance of `sqlite.Db`:
51 51
@@ -65,7 +65,9 @@ The `init` method takes an allocator and a `InitOptions` struct which will be us
65 65
66Only the `mode` field is mandatory, the other fields have sane default values. 66Only the `mode` field is mandatory, the other fields have sane default values.
67 67
68### Preparing a statement 68## Preparing a statement
69
70### Common use
69 71
70sqlite works exclusively by using prepared statements. The wrapper type is `sqlite.Statement`. Here is how you get one: 72sqlite works exclusively by using prepared statements. The wrapper type is `sqlite.Statement`. Here is how you get one:
71 73
@@ -80,7 +82,7 @@ defer stmt.deinit();
80 82
81The `Db.prepare` method takes a `comptime` query string. 83The `Db.prepare` method takes a `comptime` query string.
82 84
83### Executing a statement 85## Executing a statement
84 86
85For queries which do not return data (`INSERT`, `UPDATE`) you can use the `exec` method: 87For queries which do not return data (`INSERT`, `UPDATE`) you can use the `exec` method:
86 88
@@ -100,104 +102,131 @@ try stmt.exec({
100 102
101See the section "Bind parameters and resultset rows" for more information on the types mapping rules. 103See the section "Bind parameters and resultset rows" for more information on the types mapping rules.
102 104
103### Reading data 105## Reading data
106
107For queries which return data you have multiple options:
108* `Statement.all` which takes an allocator and can allocate memory.
109* `Statement.one` which does not take an allocator and cannot allocate memory (aside from what SQLite allocates itself).
110* `Statement.oneAlloc` which takes an allocator and can allocate memory.
111
112### Type parameter
113
114All these methods take a type as first parameter.
104 115
105For queries which do return data you can use the `all` method: 116The type represents a "row", it can be:
117* a struct where each field maps to the corresponding column in the resultset (so field 0 must map to field 1 and so on).
118* a single type, in that case the resultset must only return one column.
119
120Not all types are allowed, see the section "Bind parameters and resultset rows" for more information on the types mapping rules.
121
122### Non allocating
123
124Using `one`:
106 125
107```zig 126```zig
108const query = 127const query =
109 \\SELECT id, name, age, salary FROM employees WHERE age > ? AND age < ? 128 \\SELECT name, age FROM employees WHERE id = ?
110; 129;
111 130
112var stmt = try db.prepare(query); 131var stmt = try db.prepare(query);
113defer stmt.deinit(); 132defer stmt.deinit();
114 133
115const rows = try stmt.all( 134const row = try stmt.one(
116 struct { 135 struct {
117 id: usize, 136 name: [128:0]u8,
118 name: []const u8, 137 age: usize,
119 age: u16,
120 salary: u32,
121 }, 138 },
122 .{ .allocator = allocator }, 139 .{},
123 .{ .age1 = 20, .age2 = 40 }, 140 .{ .id = 20 },
124); 141);
125for (rows) |row| { 142if (row) |age| {
126 std.log.debug("id: {} ; name: {}; age: {}; salary: {}", .{ row.id, row.name, row.age, row.salary }); 143 std.log.debug("age: {}", .{age});
127} 144}
128``` 145```
146Notice that to read text we need to use a 0-terminated array; if the `name` column is bigger than 127 bytes the call to `one` will fail.
129 147
130The `all` method takes a type and an options tuple. 148The sentinel is mandatory: without one there would be no way to know where the data ends in the array.
131 149
132The type represents a "row", it can be: 150The convenience function `sqlite.Db.one` works exactly the same way:
133* a struct where each field maps to the corresponding column in the resultset (so field 0 must map to field 1 and so on).
134* a single type, in that case the resultset must only return one column.
135 151
136Not all types are allowed, see the section "Bind parameters and resultset rows" for more information on the types mapping rules. 152```zig
153const query =
154 \\SELECT age FROM employees WHERE id = ?
155;
156
157const row = try db.one(usize, query, .{}, .{ .id = 20 });
158if (row) |age| {
159 std.log.debug("age: {}", .{age});
160}
161```
137 162
138The options tuple is used to pass additional state required for some queries, usually it will be an allocator. 163### Allocating
139Not all queries require an allocator, hence why it's not required for every call.
140 164
141The `one` method on a statement works the same way except it returns the first row of the result set: 165Using `all`:
142 166
143```zig 167```zig
144const query = 168const query =
145 \\SELECT age FROM employees WHERE id = ? 169 \\SELECT name FROM employees WHERE age > ? AND age < ?
146; 170;
147 171
148var stmt = try db.prepare(query); 172var stmt = try db.prepare(query);
149defer stmt.deinit(); 173defer stmt.deinit();
150 174
151const row = try stmt.one(usize, .{}, .{ .id = 20 }); 175const rows = try stmt.all([]const u8, allocator, .{}, .{
152if (row) |age| { 176 .age1 = 20,
153 std.log.debug("age: {}", .{age}); 177 .age2 = 40,
178});
179for (rows) |row| {
180 std.log.debug("id: {} ; name: {}; age: {}; salary: {}", .{ row.id, row.name, row.age, row.salary });
154} 181}
155``` 182```
156 183
157The convienence function `sqlite.Db.one` works exactly the same way: 184Using `oneAlloc`:
158 185
159```zig 186```zig
160const query = 187const query =
161 \\SELECT age FROM employees WHERE id = ? 188 \\SELECT name FROM employees WHERE id = ?
162; 189;
163 190
164const row = try db.one(usize, query, .{}, .{ .id = 20 }); 191var stmt = try db.prepare(query);
165if (row) |age| { 192defer stmt.deinit();
166 std.log.debug("age: {}", .{age}); 193
167} 194const name = try stmt.oneAlloc([]const u8, allocator, .{}, .{
195 .id = 200,
196});
197std.log.debug("name: {}", .{name});
168``` 198```
169 199
170### Iterating 200## Iterating
171 201
172Another way to get the data returned by a query is to use the `sqlite.Iterator` type. 202Another way to get the data returned by a query is to use the `sqlite.Iterator` type.
173 203
174You can only get one by calling the `iterator` method on a statement: 204You can only get one by calling the `iterator` method on a statement.
205
206The `iterator` method takes a type which is the same as with `all`, `one` or `oneAlloc`: every row retrieved by calling `next` or `nextAlloc` will have this type.
207
208Iterating is done by calling the `next` or `nextAlloc` method on an iterator. Just like before, `next` cannot allocate memory while `nextAlloc` can allocate memory.
209
210`next` or `nextAlloc` will either return an optional value or an error; you should keep iterating until `null` is returned.
211
212### Non allocating
175 213
176```zig 214```zig
177var stmt = try db.prepare("SELECT name FROM user WHERE age < ?"); 215var stmt = try db.prepare("SELECT age FROM user WHERE age < ?");
178defer stmt.deinit(); 216defer stmt.deinit();
179 217
180var iter = try stmt.iterator([]const u8, .{ 218var iter = try stmt.iterator(usize, .{
181 .age = 20, 219 .age = 20,
182}); 220});
183 221
184var names = std.ArrayList([]const u8).init(allocator);
185while (true) { 222while (true) {
186 const row = (try iter.next(.{ .allocator = allocator })) orelse break; 223 const age = (try iter.next(.{})) orelse break;
187 try rows.append(row); 224 std.debug.print("age: {}\n", .{age});
188} 225}
189``` 226```
190 227
191The `iterator` method takes a type which is the same as with `all` or `one`: every row retrieved by calling `next` will have this type. 228### Allocating
192
193Using the iterator is straightforward: call `next` on it in a loop; it can either fail with an error or return an optional value: if that optional is null, iterating is done.
194 229
195The `next` method takes an options tuple which serves the same function as the one in `all` or `one`.
196
197The code example above uses the iterator but it's no different than just calling `all` used like this; the real benefit of the iterator is to be able to process each row
198sequentially without needing to store all the resultset in memory at the same time.
199
200Here's an example:
201```zig 230```zig
202var stmt = try db.prepare("SELECT name FROM user WHERE age < ?"); 231var stmt = try db.prepare("SELECT name FROM user WHERE age < ?");
203defer stmt.deinit(); 232defer stmt.deinit();
@@ -210,15 +239,12 @@ while (true) {
210 var arena = std.heap.ArenaAllocator.init(allocator); 239 var arena = std.heap.ArenaAllocator.init(allocator);
211 defer arena.deinit(); 240 defer arena.deinit();
212 241
213 const name = (try iter.next(.{ .allocator = &arena.allocator })) orelse break; 242 const name = (try iter.nextAlloc(&arena.allocator, .{})) orelse break;
214 243 std.debug.print("name: {}\n", .{name});
215 // do stuff with name here
216} 244}
217``` 245```
218 246
219Used like this the memory required for the row is only used for one iteration. You can imagine this is especially useful if your resultset contains millions of rows. 247## Bind parameters and resultset rows
220
221### Bind parameters and resultset rows
222 248
223Since sqlite doesn't have many [types](https://www.sqlite.org/datatype3.html) only a small number of Zig types are allowed in binding parameters and in resultset mapping types. 249Since sqlite doesn't have many [types](https://www.sqlite.org/datatype3.html) only a small number of Zig types are allowed in binding parameters and in resultset mapping types.
224 250
@@ -238,11 +264,11 @@ Here are the rules for resultset rows:
238 264
239Note 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. 265Note 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.
240 266
241## Comptime checks 267# Comptime checks
242 268
243Prepared statements contain _comptime_ metadata which is used to validate every call to `exec`, `one` and `all` _at compile time_. 269Prepared statements contain _comptime_ metadata which is used to validate every call to `exec`, `one` and `all` _at compile time_.
244 270
245### Check the number of bind parameters. 271## Check the number of bind parameters.
246 272
247The first check makes sure you provide the same number of bind parameters as there are bind markers in the query string. 273The first check makes sure you provide the same number of bind parameters as there are bind markers in the query string.
248 274
@@ -251,7 +277,7 @@ Take the following code:
251var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?"); 277var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?");
252defer stmt.deinit(); 278defer stmt.deinit();
253 279
254const rows = try stmt.all(usize, .{ .allocator = allocator }, .{ 280const rows = try stmt.all(usize, .{}, .{
255 .age_1 = 10, 281 .age_1 = 10,
256 .age_2 = 20, 282 .age_2 = 20,
257}); 283});
@@ -259,23 +285,23 @@ _ = rows;
259``` 285```
260It fails with this compilation error: 286It fails with this compilation error:
261``` 287```
262/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:465:17: error: number of bind markers not equal to number of fields 288/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:738:17: error: number of bind markers not equal to number of fields
263 @compileError("number of bind markers not equal to number of fields"); 289 @compileError("number of bind markers not equal to number of fields");
264 ^ 290 ^
265/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:543:22: note: called from here 291/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:817:22: note: called from here
266 self.bind(values); 292 self.bind(values);
267 ^ 293 ^
268/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:619:41: note: called from here 294/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:905:41: note: called from here
269 var iter = try self.iterator(Type, values); 295 var iter = try self.iterator(Type, values);
270 ^ 296 ^
271./src/main.zig:16:30: note: called from here 297./src/main.zig:19:30: note: called from here
272 const rows = try stmt.all(usize, .{ .allocator = allocator }, .{ 298 const rows = try stmt.all(usize, allocator, .{}, .{
273 ^ 299 ^
274./src/main.zig:5:29: note: called from here 300./src/main.zig:5:29: note: called from here
275pub fn main() anyerror!void { 301pub fn main() anyerror!void {
276``` 302```
277 303
278### Assign types to bind markers and check them. 304## Assign types to bind markers and check them.
279 305
280The second (and more interesting) check makes sure you provide appropriately typed values as bind parameters. 306The second (and more interesting) check makes sure you provide appropriately typed values as bind parameters.
281 307
@@ -307,21 +333,20 @@ const rows = try stmt.all(usize, .{ .allocator = allocator }, .{
307 .weight = false, 333 .weight = false,
308}); 334});
309_ = rows; 335_ = rows;
310
311``` 336```
312Now this fails to compile: 337Now this fails to compile:
313``` 338```
314/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:485:25: error: value type bool is not the bind marker type usize 339/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:745:25: error: value type bool is not the bind marker type usize
315 @compileError("value type " ++ @typeName(struct_field.field_type) ++ " is not the bind marker type " ++ @typeName(typ)); 340 @compileError("value type " ++ @typeName(struct_field.field_type) ++ " is not the bind marker type " ++ @typeName(typ));
316 ^ 341 ^
317/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:557:22: note: called from here 342/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:817:22: note: called from here
318 self.bind(values); 343 self.bind(values);
319 ^ 344 ^
320/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:633:41: note: called from here 345/home/vincent/dev/perso/libs/zig-sqlite/sqlite.zig:905:41: note: called from here
321 var iter = try self.iterator(Type, values); 346 var iter = try self.iterator(Type, values);
322 ^ 347 ^
323./src/main.zig:16:30: note: called from here 348./src/main.zig:19:30: note: called from here
324 const rows = try stmt.all(usize, .{ .allocator = allocator }, .{ 349 const rows = try stmt.all(usize, allocator, .{}, .{
325 ^ 350 ^
326./src/main.zig:5:29: note: called from here 351./src/main.zig:5:29: note: called from here
327pub fn main() anyerror!void { 352pub fn main() anyerror!void {
@@ -346,7 +371,7 @@ To finish our example, passing the proper type allows it compile:
346var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?{usize}"); 371var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?{usize}");
347defer stmt.deinit(); 372defer stmt.deinit();
348 373
349const rows = try stmt.all(usize, .{ .allocator = allocator }, .{ 374const rows = try stmt.all(usize, .{}, .{
350 .age_1 = 10, 375 .age_1 = 10,
351 .age_2 = 20, 376 .age_2 = 20,
352 .weight = @as(usize, 200), 377 .weight = @as(usize, 200),