diff options
| author | 2020-12-28 23:23:24 +0100 | |
|---|---|---|
| committer | 2020-12-31 14:42:10 +0100 | |
| commit | faad7cb2da89a49082a25854b945314960f506c9 (patch) | |
| tree | 147034fa2224e0a3f6993cc1cd55e850e1506db7 /README.md | |
| parent | add pragmaAlloc (diff) | |
| download | zig-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.md | 177 |
1 files changed, 101 insertions, 76 deletions
| @@ -2,13 +2,13 @@ | |||
| 2 | 2 | ||
| 3 | This package is a thin wrapper around [sqlite](https://sqlite.org/index.html)'s C API. | 3 | This package is a thin wrapper around [sqlite](https://sqlite.org/index.html)'s C API. |
| 4 | 4 | ||
| 5 | ## Status | 5 | # Status |
| 6 | 6 | ||
| 7 | While the core functionality works right now, the API is still subject to changes. | 7 | While the core functionality works right now, the API is still subject to changes. |
| 8 | 8 | ||
| 9 | If you use this library, expect to have to make changes when you update the code. | 9 | If 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 | ||
| 26 | Since there's no package manager for Zig yet, the recommended way is to use a git submodule: | 26 | Since 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: | |||
| 43 | const sqlite = @import("sqlite"); | 43 | const sqlite = @import("sqlite"); |
| 44 | ``` | 44 | ``` |
| 45 | 45 | ||
| 46 | ## Usage | 46 | # Usage |
| 47 | 47 | ||
| 48 | ### Initialization | 48 | ## Initialization |
| 49 | 49 | ||
| 50 | You must create and initialize an instance of `sqlite.Db`: | 50 | You 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 | ||
| 66 | Only the `mode` field is mandatory, the other fields have sane default values. | 66 | Only 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 | ||
| 70 | sqlite works exclusively by using prepared statements. The wrapper type is `sqlite.Statement`. Here is how you get one: | 72 | sqlite 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 | ||
| 81 | The `Db.prepare` method takes a `comptime` query string. | 83 | The `Db.prepare` method takes a `comptime` query string. |
| 82 | 84 | ||
| 83 | ### Executing a statement | 85 | ## Executing a statement |
| 84 | 86 | ||
| 85 | For queries which do not return data (`INSERT`, `UPDATE`) you can use the `exec` method: | 87 | For 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 | ||
| 101 | See the section "Bind parameters and resultset rows" for more information on the types mapping rules. | 103 | See the section "Bind parameters and resultset rows" for more information on the types mapping rules. |
| 102 | 104 | ||
| 103 | ### Reading data | 105 | ## Reading data |
| 106 | |||
| 107 | For 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 | |||
| 114 | All these methods take a type as first parameter. | ||
| 104 | 115 | ||
| 105 | For queries which do return data you can use the `all` method: | 116 | The 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 | |||
| 120 | Not 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 | |||
| 124 | Using `one`: | ||
| 106 | 125 | ||
| 107 | ```zig | 126 | ```zig |
| 108 | const query = | 127 | const 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 | ||
| 112 | var stmt = try db.prepare(query); | 131 | var stmt = try db.prepare(query); |
| 113 | defer stmt.deinit(); | 132 | defer stmt.deinit(); |
| 114 | 133 | ||
| 115 | const rows = try stmt.all( | 134 | const 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 | ); |
| 125 | for (rows) |row| { | 142 | if (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 | ``` |
| 146 | Notice 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 | ||
| 130 | The `all` method takes a type and an options tuple. | 148 | The sentinel is mandatory: without one there would be no way to know where the data ends in the array. |
| 131 | 149 | ||
| 132 | The type represents a "row", it can be: | 150 | The 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 | ||
| 136 | Not all types are allowed, see the section "Bind parameters and resultset rows" for more information on the types mapping rules. | 152 | ```zig |
| 153 | const query = | ||
| 154 | \\SELECT age FROM employees WHERE id = ? | ||
| 155 | ; | ||
| 156 | |||
| 157 | const row = try db.one(usize, query, .{}, .{ .id = 20 }); | ||
| 158 | if (row) |age| { | ||
| 159 | std.log.debug("age: {}", .{age}); | ||
| 160 | } | ||
| 161 | ``` | ||
| 137 | 162 | ||
| 138 | The options tuple is used to pass additional state required for some queries, usually it will be an allocator. | 163 | ### Allocating |
| 139 | Not all queries require an allocator, hence why it's not required for every call. | ||
| 140 | 164 | ||
| 141 | The `one` method on a statement works the same way except it returns the first row of the result set: | 165 | Using `all`: |
| 142 | 166 | ||
| 143 | ```zig | 167 | ```zig |
| 144 | const query = | 168 | const query = |
| 145 | \\SELECT age FROM employees WHERE id = ? | 169 | \\SELECT name FROM employees WHERE age > ? AND age < ? |
| 146 | ; | 170 | ; |
| 147 | 171 | ||
| 148 | var stmt = try db.prepare(query); | 172 | var stmt = try db.prepare(query); |
| 149 | defer stmt.deinit(); | 173 | defer stmt.deinit(); |
| 150 | 174 | ||
| 151 | const row = try stmt.one(usize, .{}, .{ .id = 20 }); | 175 | const rows = try stmt.all([]const u8, allocator, .{}, .{ |
| 152 | if (row) |age| { | 176 | .age1 = 20, |
| 153 | std.log.debug("age: {}", .{age}); | 177 | .age2 = 40, |
| 178 | }); | ||
| 179 | for (rows) |row| { | ||
| 180 | std.log.debug("id: {} ; name: {}; age: {}; salary: {}", .{ row.id, row.name, row.age, row.salary }); | ||
| 154 | } | 181 | } |
| 155 | ``` | 182 | ``` |
| 156 | 183 | ||
| 157 | The convienence function `sqlite.Db.one` works exactly the same way: | 184 | Using `oneAlloc`: |
| 158 | 185 | ||
| 159 | ```zig | 186 | ```zig |
| 160 | const query = | 187 | const query = |
| 161 | \\SELECT age FROM employees WHERE id = ? | 188 | \\SELECT name FROM employees WHERE id = ? |
| 162 | ; | 189 | ; |
| 163 | 190 | ||
| 164 | const row = try db.one(usize, query, .{}, .{ .id = 20 }); | 191 | var stmt = try db.prepare(query); |
| 165 | if (row) |age| { | 192 | defer stmt.deinit(); |
| 166 | std.log.debug("age: {}", .{age}); | 193 | |
| 167 | } | 194 | const name = try stmt.oneAlloc([]const u8, allocator, .{}, .{ |
| 195 | .id = 200, | ||
| 196 | }); | ||
| 197 | std.log.debug("name: {}", .{name}); | ||
| 168 | ``` | 198 | ``` |
| 169 | 199 | ||
| 170 | ### Iterating | 200 | ## Iterating |
| 171 | 201 | ||
| 172 | Another way to get the data returned by a query is to use the `sqlite.Iterator` type. | 202 | Another way to get the data returned by a query is to use the `sqlite.Iterator` type. |
| 173 | 203 | ||
| 174 | You can only get one by calling the `iterator` method on a statement: | 204 | You can only get one by calling the `iterator` method on a statement. |
| 205 | |||
| 206 | The `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 | |||
| 208 | Iterating 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 |
| 177 | var stmt = try db.prepare("SELECT name FROM user WHERE age < ?"); | 215 | var stmt = try db.prepare("SELECT age FROM user WHERE age < ?"); |
| 178 | defer stmt.deinit(); | 216 | defer stmt.deinit(); |
| 179 | 217 | ||
| 180 | var iter = try stmt.iterator([]const u8, .{ | 218 | var iter = try stmt.iterator(usize, .{ |
| 181 | .age = 20, | 219 | .age = 20, |
| 182 | }); | 220 | }); |
| 183 | 221 | ||
| 184 | var names = std.ArrayList([]const u8).init(allocator); | ||
| 185 | while (true) { | 222 | while (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 | ||
| 191 | The `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 | |||
| 193 | Using 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 | ||
| 195 | The `next` method takes an options tuple which serves the same function as the one in `all` or `one`. | ||
| 196 | |||
| 197 | The 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 | ||
| 198 | sequentially without needing to store all the resultset in memory at the same time. | ||
| 199 | |||
| 200 | Here's an example: | ||
| 201 | ```zig | 230 | ```zig |
| 202 | var stmt = try db.prepare("SELECT name FROM user WHERE age < ?"); | 231 | var stmt = try db.prepare("SELECT name FROM user WHERE age < ?"); |
| 203 | defer stmt.deinit(); | 232 | defer 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 | ||
| 219 | Used 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 | ||
| 223 | Since 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. | 249 | Since 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 | ||
| 239 | Note 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. | 265 | Note 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 | ||
| 243 | Prepared statements contain _comptime_ metadata which is used to validate every call to `exec`, `one` and `all` _at compile time_. | 269 | Prepared 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 | ||
| 247 | The first check makes sure you provide the same number of bind parameters as there are bind markers in the query string. | 273 | The 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: | |||
| 251 | var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?"); | 277 | var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?"); |
| 252 | defer stmt.deinit(); | 278 | defer stmt.deinit(); |
| 253 | 279 | ||
| 254 | const rows = try stmt.all(usize, .{ .allocator = allocator }, .{ | 280 | const 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 | ``` |
| 260 | It fails with this compilation error: | 286 | It 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 |
| 275 | pub fn main() anyerror!void { | 301 | pub 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 | ||
| 280 | The second (and more interesting) check makes sure you provide appropriately typed values as bind parameters. | 306 | The 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 | ``` |
| 312 | Now this fails to compile: | 337 | Now 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 |
| 327 | pub fn main() anyerror!void { | 352 | pub fn main() anyerror!void { |
| @@ -346,7 +371,7 @@ To finish our example, passing the proper type allows it compile: | |||
| 346 | var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?{usize}"); | 371 | var stmt = try db.prepare("SELECT id FROM user WHERE age > ? AND age < ? AND weight > ?{usize}"); |
| 347 | defer stmt.deinit(); | 372 | defer stmt.deinit(); |
| 348 | 373 | ||
| 349 | const rows = try stmt.all(usize, .{ .allocator = allocator }, .{ | 374 | const 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), |