diff options
| author | 2020-12-31 15:28:26 +0100 | |
|---|---|---|
| committer | 2020-12-31 15:28:26 +0100 | |
| commit | 70ed4b3ae4c78adf5fbc5aada642158bf33d7c3e (patch) | |
| tree | 78d32ef16cc107bcb3eaadc1c168b385b0cd164c | |
| parent | document OpenFlags (diff) | |
| parent | fix readme (diff) | |
| download | zig-sqlite-70ed4b3ae4c78adf5fbc5aada642158bf33d7c3e.tar.gz zig-sqlite-70ed4b3ae4c78adf5fbc5aada642158bf33d7c3e.tar.xz zig-sqlite-70ed4b3ae4c78adf5fbc5aada642158bf33d7c3e.zip | |
Merge pull request #9 from vrischmann/split-alloc
Split allocating from non-allocating methods
Diffstat (limited to '')
| -rw-r--r-- | README.md | 181 | ||||
| -rw-r--r-- | sqlite.zig | 318 |
2 files changed, 331 insertions, 168 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,22 +239,19 @@ 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 | ||
| 225 | Here are the rules for bind parameters: | 251 | Here are the rules for bind parameters: |
| 226 | * any Zig `Int` or `ComptimeInt` is tread as a `INTEGER`. | 252 | * any Zig `Int` or `ComptimeInt` is tread as a `INTEGER`. |
| 227 | * any Zig `Float` or `ComptimeFloat` is treated as a `REAL`. | 253 | * any Zig `Float` or `ComptimeFloat` is treated as a `REAL`. |
| 228 | * `[]const u8`, `[]u8` or any array of `u8` is treated as a `TEXT`. | 254 | * `[]const u8`, `[]u8` is treated as a `TEXT`. |
| 229 | * The custom `sqlite.Blob` type is treated as a `BLOB`. | 255 | * The custom `sqlite.Blob` type is treated as a `BLOB`. |
| 230 | * The custom `sqlite.Text` type is treated as a `TEXT`. | 256 | * The custom `sqlite.Text` type is treated as a `TEXT`. |
| 231 | 257 | ||
| @@ -233,16 +259,16 @@ Here are the rules for resultset rows: | |||
| 233 | * `INTEGER` can be read into any Zig `Int` provided the data fits. | 259 | * `INTEGER` can be read into any Zig `Int` provided the data fits. |
| 234 | * `REAL` can be read into any Zig `Float` provided the data fits. | 260 | * `REAL` can be read into any Zig `Float` provided the data fits. |
| 235 | * `TEXT` can be read into a `[]const u8` or `[]u8`. | 261 | * `TEXT` can be read into a `[]const u8` or `[]u8`. |
| 236 | * `TEXT` can be read into any array of `u8` provided the data fits. | 262 | * `TEXT` can be read into any array of `u8` with a sentinel provided the data fits. |
| 237 | * `BLOB` follows the same rules as `TEXT`. | 263 | * `BLOB` follows the same rules as `TEXT`. |
| 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), |
| @@ -159,6 +159,30 @@ pub const Db = struct { | |||
| 159 | return getLastDetailedErrorFromDb(self.db); | 159 | return getLastDetailedErrorFromDb(self.db); |
| 160 | } | 160 | } |
| 161 | 161 | ||
| 162 | fn getPragmaQuery(comptime buf: []u8, comptime name: []const u8, comptime arg: anytype) []const u8 { | ||
| 163 | return if (arg.len == 1) blk: { | ||
| 164 | break :blk try std.fmt.bufPrint(buf, "PRAGMA {} = {}", .{ name, arg[0] }); | ||
| 165 | } else blk: { | ||
| 166 | break :blk try std.fmt.bufPrint(buf, "PRAGMA {}", .{name}); | ||
| 167 | }; | ||
| 168 | } | ||
| 169 | |||
| 170 | /// pragmaAlloc is like `pragma` but can allocate memory. | ||
| 171 | /// | ||
| 172 | /// Useful when the pragma command returns text, for example: | ||
| 173 | /// | ||
| 174 | /// const journal_mode = try db.pragma([]const u8, allocator, .{}, "journal_mode", .{}); | ||
| 175 | /// | ||
| 176 | pub fn pragmaAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: anytype, comptime name: []const u8, comptime arg: anytype) !?Type { | ||
| 177 | comptime var buf: [1024]u8 = undefined; | ||
| 178 | comptime var query = getPragmaQuery(&buf, name, arg); | ||
| 179 | |||
| 180 | var stmt = try self.prepare(query); | ||
| 181 | defer stmt.deinit(); | ||
| 182 | |||
| 183 | return try stmt.oneAlloc(Type, allocator, options, .{}); | ||
| 184 | } | ||
| 185 | |||
| 162 | /// pragma is a convenience function to use the PRAGMA statement. | 186 | /// pragma is a convenience function to use the PRAGMA statement. |
| 163 | /// | 187 | /// |
| 164 | /// Here is how to set a pragma value: | 188 | /// Here is how to set a pragma value: |
| @@ -167,21 +191,14 @@ pub const Db = struct { | |||
| 167 | /// | 191 | /// |
| 168 | /// Here is how to query a pragama value: | 192 | /// Here is how to query a pragama value: |
| 169 | /// | 193 | /// |
| 170 | /// const journal_mode = try db.pragma( | 194 | /// const journal_mode = try db.pragma([128:0]const u8, .{}, "journal_mode", .{}); |
| 171 | /// []const u8, | ||
| 172 | /// "journal_mode", | ||
| 173 | /// .{ .allocator = allocator }, | ||
| 174 | /// .{}, | ||
| 175 | /// ); | ||
| 176 | /// | 195 | /// |
| 177 | /// The pragma name must be known at comptime. | 196 | /// The pragma name must be known at comptime. |
| 178 | pub fn pragma(self: *Self, comptime Type: type, comptime name: []const u8, options: anytype, arg: anytype) !?Type { | 197 | /// |
| 198 | /// This cannot allocate memory. If your pragma command returns text you must use an array or call `pragmaAlloc`. | ||
| 199 | pub fn pragma(self: *Self, comptime Type: type, options: anytype, comptime name: []const u8, arg: anytype) !?Type { | ||
| 179 | comptime var buf: [1024]u8 = undefined; | 200 | comptime var buf: [1024]u8 = undefined; |
| 180 | comptime var query = if (arg.len == 1) blk: { | 201 | comptime var query = getPragmaQuery(&buf, name, arg); |
| 181 | break :blk try std.fmt.bufPrint(&buf, "PRAGMA {} = {}", .{ name, arg[0] }); | ||
| 182 | } else blk: { | ||
| 183 | break :blk try std.fmt.bufPrint(&buf, "PRAGMA {}", .{name}); | ||
| 184 | }; | ||
| 185 | 202 | ||
| 186 | var stmt = try self.prepare(query); | 203 | var stmt = try self.prepare(query); |
| 187 | defer stmt.deinit(); | 204 | defer stmt.deinit(); |
| @@ -203,6 +220,13 @@ pub const Db = struct { | |||
| 203 | return try stmt.one(Type, options, values); | 220 | return try stmt.one(Type, options, values); |
| 204 | } | 221 | } |
| 205 | 222 | ||
| 223 | /// oneAlloc is like `one` but can allocate memory. | ||
| 224 | pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, comptime query: []const u8, options: anytype, values: anytype) !?Type { | ||
| 225 | var stmt = try self.prepare(query); | ||
| 226 | defer stmt.deinit(); | ||
| 227 | return try stmt.oneAlloc(Type, allocator, options, values); | ||
| 228 | } | ||
| 229 | |||
| 206 | /// prepare prepares a statement for the `query` provided. | 230 | /// prepare prepares a statement for the `query` provided. |
| 207 | /// | 231 | /// |
| 208 | /// The query is analysed at comptime to search for bind markers. | 232 | /// The query is analysed at comptime to search for bind markers. |
| @@ -259,16 +283,55 @@ pub fn Iterator(comptime Type: type) type { | |||
| 259 | stmt: *c.sqlite3_stmt, | 283 | stmt: *c.sqlite3_stmt, |
| 260 | 284 | ||
| 261 | // next scans the next row using the prepared statement. | 285 | // next scans the next row using the prepared statement. |
| 262 | // | ||
| 263 | // If it returns null iterating is done. | 286 | // If it returns null iterating is done. |
| 287 | // | ||
| 288 | // This cannot allocate memory. If you need to read TEXT or BLOB columns you need to use arrays or alternatively call nextAlloc. | ||
| 264 | pub fn next(self: *Self, options: anytype) !?Type { | 289 | pub fn next(self: *Self, options: anytype) !?Type { |
| 265 | var result = c.sqlite3_step(self.stmt); | 290 | var result = c.sqlite3_step(self.stmt); |
| 266 | if (result == c.SQLITE_DONE) { | 291 | if (result == c.SQLITE_DONE) { |
| 267 | return null; | 292 | return null; |
| 268 | } | 293 | } |
| 294 | if (result != c.SQLITE_ROW) { | ||
| 295 | return error.SQLiteStepError; | ||
| 296 | } | ||
| 297 | |||
| 298 | const columns = c.sqlite3_column_count(self.stmt); | ||
| 269 | 299 | ||
| 300 | switch (TypeInfo) { | ||
| 301 | .Int => { | ||
| 302 | debug.assert(columns == 1); | ||
| 303 | return try self.readInt(Type, 0); | ||
| 304 | }, | ||
| 305 | .Float => { | ||
| 306 | debug.assert(columns == 1); | ||
| 307 | return try self.readFloat(Type, 0); | ||
| 308 | }, | ||
| 309 | .Bool => { | ||
| 310 | debug.assert(columns == 1); | ||
| 311 | return try self.readBool(0); | ||
| 312 | }, | ||
| 313 | .Void => { | ||
| 314 | debug.assert(columns == 1); | ||
| 315 | }, | ||
| 316 | .Array => { | ||
| 317 | debug.assert(columns == 1); | ||
| 318 | return try self.readArray(Type, 0); | ||
| 319 | }, | ||
| 320 | .Struct => { | ||
| 321 | std.debug.assert(columns == TypeInfo.Struct.fields.len); | ||
| 322 | return try self.readStruct(.{}); | ||
| 323 | }, | ||
| 324 | else => @compileError("cannot read into type " ++ @typeName(Type) ++ " ; if dynamic memory allocation is required use nextAlloc"), | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | // nextAlloc is like `next` but can allocate memory. | ||
| 329 | pub fn nextAlloc(self: *Self, allocator: *mem.Allocator, options: anytype) !?Type { | ||
| 330 | var result = c.sqlite3_step(self.stmt); | ||
| 331 | if (result == c.SQLITE_DONE) { | ||
| 332 | return null; | ||
| 333 | } | ||
| 270 | if (result != c.SQLITE_ROW) { | 334 | if (result != c.SQLITE_ROW) { |
| 271 | logger.err("unable to iterate, result: {}", .{result}); | ||
| 272 | return error.SQLiteStepError; | 335 | return error.SQLiteStepError; |
| 273 | } | 336 | } |
| 274 | 337 | ||
| @@ -277,15 +340,15 @@ pub fn Iterator(comptime Type: type) type { | |||
| 277 | switch (Type) { | 340 | switch (Type) { |
| 278 | []const u8, []u8 => { | 341 | []const u8, []u8 => { |
| 279 | debug.assert(columns == 1); | 342 | debug.assert(columns == 1); |
| 280 | return try self.readBytes(Type, options.allocator, 0, .Text); | 343 | return try self.readBytes(Type, allocator, 0, .Text); |
| 281 | }, | 344 | }, |
| 282 | Blob => { | 345 | Blob => { |
| 283 | debug.assert(columns == 1); | 346 | debug.assert(columns == 1); |
| 284 | return try self.readBytes(Blob, options.allocator, 0, .Blob); | 347 | return try self.readBytes(Blob, allocator, 0, .Blob); |
| 285 | }, | 348 | }, |
| 286 | Text => { | 349 | Text => { |
| 287 | debug.assert(columns == 1); | 350 | debug.assert(columns == 1); |
| 288 | return try self.readBytes(Text, options.allocator, 0, .Text); | 351 | return try self.readBytes(Text, allocator, 0, .Text); |
| 289 | }, | 352 | }, |
| 290 | else => {}, | 353 | else => {}, |
| 291 | } | 354 | } |
| @@ -312,11 +375,13 @@ pub fn Iterator(comptime Type: type) type { | |||
| 312 | }, | 375 | }, |
| 313 | .Pointer => { | 376 | .Pointer => { |
| 314 | debug.assert(columns == 1); | 377 | debug.assert(columns == 1); |
| 315 | return try self.readPointer(Type, 0, options); | 378 | return try self.readPointer(Type, allocator, 0); |
| 316 | }, | 379 | }, |
| 317 | .Struct => { | 380 | .Struct => { |
| 318 | std.debug.assert(columns == TypeInfo.Struct.fields.len); | 381 | std.debug.assert(columns == TypeInfo.Struct.fields.len); |
| 319 | return try self.readStruct(options); | 382 | return try self.readStruct(.{ |
| 383 | .allocator = allocator, | ||
| 384 | }); | ||
| 320 | }, | 385 | }, |
| 321 | else => @compileError("cannot read into type " ++ @typeName(Type)), | 386 | else => @compileError("cannot read into type " ++ @typeName(Type)), |
| 322 | } | 387 | } |
| @@ -458,7 +523,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 458 | } | 523 | } |
| 459 | } | 524 | } |
| 460 | 525 | ||
| 461 | fn readPointer(self: *Self, comptime PointerType: type, i: usize, options: anytype) !PointerType { | 526 | fn readPointer(self: *Self, comptime PointerType: type, allocator: *mem.Allocator, i: usize) !PointerType { |
| 462 | const type_info = @typeInfo(PointerType); | 527 | const type_info = @typeInfo(PointerType); |
| 463 | 528 | ||
| 464 | var ret: PointerType = undefined; | 529 | var ret: PointerType = undefined; |
| @@ -467,7 +532,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 467 | switch (ptr.size) { | 532 | switch (ptr.size) { |
| 468 | .One => unreachable, | 533 | .One => unreachable, |
| 469 | .Slice => switch (ptr.child) { | 534 | .Slice => switch (ptr.child) { |
| 470 | u8 => ret = try self.readBytes(PointerType, options.allocator, i, .Text), | 535 | u8 => ret = try self.readBytes(PointerType, allocator, i, .Text), |
| 471 | else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)), | 536 | else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)), |
| 472 | }, | 537 | }, |
| 473 | else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)), | 538 | else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)), |
| @@ -516,7 +581,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 516 | .Bool => try self.readBool(i), | 581 | .Bool => try self.readBool(i), |
| 517 | .Void => {}, | 582 | .Void => {}, |
| 518 | .Array => try self.readArray(field.field_type, i), | 583 | .Array => try self.readArray(field.field_type, i), |
| 519 | .Pointer => try self.readPointer(field.field_type, i, options), | 584 | .Pointer => try self.readPointer(field.field_type, options.allocator, i), |
| 520 | else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)), | 585 | else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)), |
| 521 | }, | 586 | }, |
| 522 | }; | 587 | }; |
| @@ -734,10 +799,10 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 734 | /// const row = try stmt.one( | 799 | /// const row = try stmt.one( |
| 735 | /// struct { | 800 | /// struct { |
| 736 | /// id: usize, | 801 | /// id: usize, |
| 737 | /// name: []const u8, | 802 | /// name: [400]u8, |
| 738 | /// age: usize, | 803 | /// age: usize, |
| 739 | /// }, | 804 | /// }, |
| 740 | /// .{ .allocator = allocator }, | 805 | /// .{}, |
| 741 | /// .{ .foo = "bar", .age = 500 }, | 806 | /// .{ .foo = "bar", .age = 500 }, |
| 742 | /// ); | 807 | /// ); |
| 743 | /// | 808 | /// |
| @@ -747,6 +812,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 747 | /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers | 812 | /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers |
| 748 | /// in the input query string. | 813 | /// in the input query string. |
| 749 | /// | 814 | /// |
| 815 | /// This cannot allocate memory. If you need to read TEXT or BLOB columns you need to use arrays or alternatively call `oneAlloc`. | ||
| 750 | pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type { | 816 | pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type { |
| 751 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | 817 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { |
| 752 | @compileError("options passed to iterator must be a struct"); | 818 | @compileError("options passed to iterator must be a struct"); |
| @@ -758,6 +824,18 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 758 | return row; | 824 | return row; |
| 759 | } | 825 | } |
| 760 | 826 | ||
| 827 | /// oneAlloc is like `one` but can allocate memory. | ||
| 828 | pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: anytype, values: anytype) !?Type { | ||
| 829 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | ||
| 830 | @compileError("options passed to iterator must be a struct"); | ||
| 831 | } | ||
| 832 | |||
| 833 | var iter = try self.iterator(Type, values); | ||
| 834 | |||
| 835 | const row = (try iter.nextAlloc(allocator, options)) orelse return null; | ||
| 836 | return row; | ||
| 837 | } | ||
| 838 | |||
| 761 | /// all reads all rows from the result set of this statement. | 839 | /// all reads all rows from the result set of this statement. |
| 762 | /// | 840 | /// |
| 763 | /// The data in each row is used to populate a value of the type `Type`. | 841 | /// The data in each row is used to populate a value of the type `Type`. |
| @@ -773,7 +851,8 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 773 | /// name: []const u8, | 851 | /// name: []const u8, |
| 774 | /// age: usize, | 852 | /// age: usize, |
| 775 | /// }, | 853 | /// }, |
| 776 | /// .{ .allocator = allocator }, | 854 | /// allocator, |
| 855 | /// .{}, | ||
| 777 | /// .{ .foo = "bar", .age = 500 }, | 856 | /// .{ .foo = "bar", .age = 500 }, |
| 778 | /// ); | 857 | /// ); |
| 779 | /// | 858 | /// |
| @@ -783,18 +862,16 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 783 | /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers | 862 | /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers |
| 784 | /// in the input query string. | 863 | /// in the input query string. |
| 785 | /// | 864 | /// |
| 786 | /// Note that this allocates all rows into a single slice: if you read a lot of data this can | 865 | /// Note that this allocates all rows into a single slice: if you read a lot of data this can use a lot of memory. |
| 787 | /// use a lot of memory. | 866 | pub fn all(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: anytype, values: anytype) ![]Type { |
| 788 | /// | ||
| 789 | pub fn all(self: *Self, comptime Type: type, options: anytype, values: anytype) ![]Type { | ||
| 790 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | 867 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { |
| 791 | @compileError("options passed to iterator must be a struct"); | 868 | @compileError("options passed to iterator must be a struct"); |
| 792 | } | 869 | } |
| 793 | var iter = try self.iterator(Type, values); | 870 | var iter = try self.iterator(Type, values); |
| 794 | 871 | ||
| 795 | var rows = std.ArrayList(Type).init(options.allocator); | 872 | var rows = std.ArrayList(Type).init(allocator); |
| 796 | while (true) { | 873 | while (true) { |
| 797 | const row = (try iter.next(options)) orelse break; | 874 | const row = (try iter.nextAlloc(allocator, options)) orelse break; |
| 798 | try rows.append(row); | 875 | try rows.append(row); |
| 799 | } | 876 | } |
| 800 | 877 | ||
| @@ -860,28 +937,36 @@ test "sqlite: db pragma" { | |||
| 860 | var db: Db = undefined; | 937 | var db: Db = undefined; |
| 861 | try db.init(initOptions()); | 938 | try db.init(initOptions()); |
| 862 | 939 | ||
| 863 | const foreign_keys = try db.pragma(usize, "foreign_keys", .{}, .{}); | 940 | const foreign_keys = try db.pragma(usize, .{}, "foreign_keys", .{}); |
| 864 | testing.expect(foreign_keys != null); | 941 | testing.expect(foreign_keys != null); |
| 865 | testing.expectEqual(@as(usize, 0), foreign_keys.?); | 942 | testing.expectEqual(@as(usize, 0), foreign_keys.?); |
| 866 | 943 | ||
| 944 | const arg = .{"wal"}; | ||
| 945 | |||
| 867 | if (build_options.in_memory) { | 946 | if (build_options.in_memory) { |
| 868 | const journal_mode = try db.pragma( | 947 | { |
| 869 | []const u8, | 948 | const journal_mode = try db.pragma([128:0]u8, .{}, "journal_mode", arg); |
| 870 | "journal_mode", | 949 | testing.expect(journal_mode != null); |
| 871 | .{ .allocator = &arena.allocator }, | 950 | testing.expectEqualStrings("memory", mem.spanZ(&journal_mode.?)); |
| 872 | .{"wal"}, | 951 | } |
| 873 | ); | 952 | |
| 874 | testing.expect(journal_mode != null); | 953 | { |
| 875 | testing.expectEqualStrings("memory", journal_mode.?); | 954 | const journal_mode = try db.pragmaAlloc([]const u8, &arena.allocator, .{}, "journal_mode", arg); |
| 955 | testing.expect(journal_mode != null); | ||
| 956 | testing.expectEqualStrings("memory", journal_mode.?); | ||
| 957 | } | ||
| 876 | } else { | 958 | } else { |
| 877 | const journal_mode = try db.pragma( | 959 | { |
| 878 | []const u8, | 960 | const journal_mode = try db.pragma([128:0]u8, .{}, "journal_mode", arg); |
| 879 | "journal_mode", | 961 | testing.expect(journal_mode != null); |
| 880 | .{ .allocator = &arena.allocator }, | 962 | testing.expectEqualStrings("wal", mem.spanZ(&journal_mode.?)); |
| 881 | .{"wal"}, | 963 | } |
| 882 | ); | 964 | |
| 883 | testing.expect(journal_mode != null); | 965 | { |
| 884 | testing.expectEqualStrings("wal", journal_mode.?); | 966 | const journal_mode = try db.pragmaAlloc([]const u8, &arena.allocator, .{}, "journal_mode", arg); |
| 967 | testing.expect(journal_mode != null); | ||
| 968 | testing.expectEqualStrings("wal", journal_mode.?); | ||
| 969 | } | ||
| 885 | } | 970 | } |
| 886 | } | 971 | } |
| 887 | 972 | ||
| @@ -920,11 +1005,9 @@ test "sqlite: read a single user into a struct" { | |||
| 920 | var stmt = try db.prepare("SELECT id, name, age, weight FROM user WHERE id = ?{usize}"); | 1005 | var stmt = try db.prepare("SELECT id, name, age, weight FROM user WHERE id = ?{usize}"); |
| 921 | defer stmt.deinit(); | 1006 | defer stmt.deinit(); |
| 922 | 1007 | ||
| 923 | var rows = try stmt.all( | 1008 | var rows = try stmt.all(TestUser, &arena.allocator, .{}, .{ |
| 924 | TestUser, | 1009 | .id = @as(usize, 20), |
| 925 | .{ .allocator = &arena.allocator }, | 1010 | }); |
| 926 | .{ .id = @as(usize, 20) }, | ||
| 927 | ); | ||
| 928 | for (rows) |row| { | 1011 | for (rows) |row| { |
| 929 | testing.expectEqual(test_users[0].id, row.id); | 1012 | testing.expectEqual(test_users[0].id, row.id); |
| 930 | testing.expectEqualStrings(test_users[0].name, row.name); | 1013 | testing.expectEqualStrings(test_users[0].name, row.name); |
| @@ -936,11 +1019,32 @@ test "sqlite: read a single user into a struct" { | |||
| 936 | var row = try db.one( | 1019 | var row = try db.one( |
| 937 | struct { | 1020 | struct { |
| 938 | id: usize, | 1021 | id: usize, |
| 1022 | name: [128:0]u8, | ||
| 1023 | age: usize, | ||
| 1024 | }, | ||
| 1025 | "SELECT id, name, age FROM user WHERE id = ?{usize}", | ||
| 1026 | .{}, | ||
| 1027 | .{@as(usize, 20)}, | ||
| 1028 | ); | ||
| 1029 | testing.expect(row != null); | ||
| 1030 | |||
| 1031 | const exp = test_users[0]; | ||
| 1032 | testing.expectEqual(exp.id, row.?.id); | ||
| 1033 | testing.expectEqualStrings(exp.name, mem.spanZ(&row.?.name)); | ||
| 1034 | testing.expectEqual(exp.age, row.?.age); | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | // Read a row with db.oneAlloc() | ||
| 1038 | { | ||
| 1039 | var row = try db.oneAlloc( | ||
| 1040 | struct { | ||
| 1041 | id: usize, | ||
| 939 | name: Text, | 1042 | name: Text, |
| 940 | age: usize, | 1043 | age: usize, |
| 941 | }, | 1044 | }, |
| 1045 | &arena.allocator, | ||
| 942 | "SELECT id, name, age FROM user WHERE id = ?{usize}", | 1046 | "SELECT id, name, age FROM user WHERE id = ?{usize}", |
| 943 | .{ .allocator = &arena.allocator }, | 1047 | .{}, |
| 944 | .{@as(usize, 20)}, | 1048 | .{@as(usize, 20)}, |
| 945 | ); | 1049 | ); |
| 946 | testing.expect(row != null); | 1050 | testing.expect(row != null); |
| @@ -963,11 +1067,7 @@ test "sqlite: read all users into a struct" { | |||
| 963 | var stmt = try db.prepare("SELECT id, name, age, weight FROM user"); | 1067 | var stmt = try db.prepare("SELECT id, name, age, weight FROM user"); |
| 964 | defer stmt.deinit(); | 1068 | defer stmt.deinit(); |
| 965 | 1069 | ||
| 966 | var rows = try stmt.all( | 1070 | var rows = try stmt.all(TestUser, &arena.allocator, .{}, .{}); |
| 967 | TestUser, | ||
| 968 | .{ .allocator = &arena.allocator }, | ||
| 969 | .{}, | ||
| 970 | ); | ||
| 971 | testing.expectEqual(@as(usize, 3), rows.len); | 1071 | testing.expectEqual(@as(usize, 3), rows.len); |
| 972 | for (rows) |row, i| { | 1072 | for (rows) |row, i| { |
| 973 | const exp = test_users[i]; | 1073 | const exp = test_users[i]; |
| @@ -988,7 +1088,7 @@ test "sqlite: read in an anonymous struct" { | |||
| 988 | var stmt = try db.prepare("SELECT id, name, name, age, id, weight FROM user WHERE id = ?{usize}"); | 1088 | var stmt = try db.prepare("SELECT id, name, name, age, id, weight FROM user WHERE id = ?{usize}"); |
| 989 | defer stmt.deinit(); | 1089 | defer stmt.deinit(); |
| 990 | 1090 | ||
| 991 | var row = try stmt.one( | 1091 | var row = try stmt.oneAlloc( |
| 992 | struct { | 1092 | struct { |
| 993 | id: usize, | 1093 | id: usize, |
| 994 | name: []const u8, | 1094 | name: []const u8, |
| @@ -997,7 +1097,8 @@ test "sqlite: read in an anonymous struct" { | |||
| 997 | is_id: bool, | 1097 | is_id: bool, |
| 998 | weight: f64, | 1098 | weight: f64, |
| 999 | }, | 1099 | }, |
| 1000 | .{ .allocator = &arena.allocator }, | 1100 | &arena.allocator, |
| 1101 | .{}, | ||
| 1001 | .{ .id = @as(usize, 20) }, | 1102 | .{ .id = @as(usize, 20) }, |
| 1002 | ); | 1103 | ); |
| 1003 | testing.expect(row != null); | 1104 | testing.expect(row != null); |
| @@ -1022,13 +1123,14 @@ test "sqlite: read in a Text struct" { | |||
| 1022 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); | 1123 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); |
| 1023 | defer stmt.deinit(); | 1124 | defer stmt.deinit(); |
| 1024 | 1125 | ||
| 1025 | var row = try stmt.one( | 1126 | var row = try stmt.oneAlloc( |
| 1026 | struct { | 1127 | struct { |
| 1027 | id: usize, | 1128 | id: usize, |
| 1028 | name: Text, | 1129 | name: Text, |
| 1029 | age: usize, | 1130 | age: usize, |
| 1030 | }, | 1131 | }, |
| 1031 | .{ .allocator = &arena.allocator }, | 1132 | &arena.allocator, |
| 1133 | .{}, | ||
| 1032 | .{@as(usize, 20)}, | 1134 | .{@as(usize, 20)}, |
| 1033 | ); | 1135 | ); |
| 1034 | testing.expect(row != null); | 1136 | testing.expect(row != null); |
| @@ -1069,11 +1171,9 @@ test "sqlite: read a single text value" { | |||
| 1069 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 1171 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 1070 | defer stmt.deinit(); | 1172 | defer stmt.deinit(); |
| 1071 | 1173 | ||
| 1072 | const name = try stmt.one( | 1174 | const name = try stmt.oneAlloc(typ, &arena.allocator, .{}, .{ |
| 1073 | typ, | 1175 | .id = @as(usize, 20), |
| 1074 | .{ .allocator = &arena.allocator }, | 1176 | }); |
| 1075 | .{ .id = @as(usize, 20) }, | ||
| 1076 | ); | ||
| 1077 | testing.expect(name != null); | 1177 | testing.expect(name != null); |
| 1078 | switch (typ) { | 1178 | switch (typ) { |
| 1079 | Text, Blob => { | 1179 | Text, Blob => { |
| @@ -1120,7 +1220,9 @@ test "sqlite: read a single integer value" { | |||
| 1120 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 1220 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 1121 | defer stmt.deinit(); | 1221 | defer stmt.deinit(); |
| 1122 | 1222 | ||
| 1123 | var age = try stmt.one(typ, .{}, .{ .id = @as(usize, 20) }); | 1223 | var age = try stmt.one(typ, .{}, .{ |
| 1224 | .id = @as(usize, 20), | ||
| 1225 | }); | ||
| 1124 | testing.expect(age != null); | 1226 | testing.expect(age != null); |
| 1125 | 1227 | ||
| 1126 | testing.expectEqual(@as(typ, 33), age.?); | 1228 | testing.expectEqual(@as(typ, 33), age.?); |
| @@ -1137,7 +1239,9 @@ test "sqlite: read a single value into void" { | |||
| 1137 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 1239 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 1138 | defer stmt.deinit(); | 1240 | defer stmt.deinit(); |
| 1139 | 1241 | ||
| 1140 | _ = try stmt.one(void, .{}, .{ .id = @as(usize, 20) }); | 1242 | _ = try stmt.one(void, .{}, .{ |
| 1243 | .id = @as(usize, 20), | ||
| 1244 | }); | ||
| 1141 | } | 1245 | } |
| 1142 | 1246 | ||
| 1143 | test "sqlite: read a single value into bool" { | 1247 | test "sqlite: read a single value into bool" { |
| @@ -1150,7 +1254,9 @@ test "sqlite: read a single value into bool" { | |||
| 1150 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 1254 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 1151 | defer stmt.deinit(); | 1255 | defer stmt.deinit(); |
| 1152 | 1256 | ||
| 1153 | const b = try stmt.one(bool, .{}, .{ .id = @as(usize, 20) }); | 1257 | const b = try stmt.one(bool, .{}, .{ |
| 1258 | .id = @as(usize, 20), | ||
| 1259 | }); | ||
| 1154 | testing.expect(b != null); | 1260 | testing.expect(b != null); |
| 1155 | testing.expect(b.?); | 1261 | testing.expect(b.?); |
| 1156 | } | 1262 | } |
| @@ -1171,7 +1277,9 @@ test "sqlite: insert bool and bind bool" { | |||
| 1171 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 1277 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 1172 | defer stmt.deinit(); | 1278 | defer stmt.deinit(); |
| 1173 | 1279 | ||
| 1174 | const b = try stmt.one(bool, .{}, .{ .is_published = true }); | 1280 | const b = try stmt.one(bool, .{}, .{ |
| 1281 | .is_published = true, | ||
| 1282 | }); | ||
| 1175 | testing.expect(b != null); | 1283 | testing.expect(b != null); |
| 1176 | testing.expect(b.?); | 1284 | testing.expect(b.?); |
| 1177 | } | 1285 | } |
| @@ -1232,30 +1340,60 @@ test "sqlite: statement iterator" { | |||
| 1232 | testing.expectEqual(@as(usize, 1), rows_inserted); | 1340 | testing.expectEqual(@as(usize, 1), rows_inserted); |
| 1233 | } | 1341 | } |
| 1234 | 1342 | ||
| 1235 | // Get the data with an iterator | 1343 | // Get data with a non-allocating iterator. |
| 1236 | var stmt2 = try db.prepare("SELECT name, age FROM user"); | 1344 | { |
| 1237 | defer stmt2.deinit(); | 1345 | var stmt2 = try db.prepare("SELECT name, age FROM user"); |
| 1346 | defer stmt2.deinit(); | ||
| 1238 | 1347 | ||
| 1239 | const Type = struct { | 1348 | const RowType = struct { |
| 1240 | name: Text, | 1349 | name: [128:0]u8, |
| 1241 | age: usize, | 1350 | age: usize, |
| 1242 | }; | 1351 | }; |
| 1352 | |||
| 1353 | var iter = try stmt2.iterator(RowType, .{}); | ||
| 1354 | |||
| 1355 | var rows = std.ArrayList(RowType).init(allocator); | ||
| 1356 | while (true) { | ||
| 1357 | const row = (try iter.next(.{})) orelse break; | ||
| 1358 | try rows.append(row); | ||
| 1359 | } | ||
| 1243 | 1360 | ||
| 1244 | var iter = try stmt2.iterator(Type, .{}); | 1361 | // Check the data |
| 1362 | testing.expectEqual(expected_rows.items.len, rows.items.len); | ||
| 1245 | 1363 | ||
| 1246 | var rows = std.ArrayList(Type).init(allocator); | 1364 | for (rows.items) |row, j| { |
| 1247 | while (true) { | 1365 | const exp_row = expected_rows.items[j]; |
| 1248 | const row = (try iter.next(.{ .allocator = allocator })) orelse break; | 1366 | testing.expectEqualStrings(exp_row.name, mem.spanZ(&row.name)); |
| 1249 | try rows.append(row); | 1367 | testing.expectEqual(exp_row.age, row.age); |
| 1368 | } | ||
| 1250 | } | 1369 | } |
| 1251 | 1370 | ||
| 1252 | // Check the data | 1371 | // Get data with an iterator |
| 1253 | testing.expectEqual(expected_rows.items.len, rows.items.len); | 1372 | { |
| 1373 | var stmt2 = try db.prepare("SELECT name, age FROM user"); | ||
| 1374 | defer stmt2.deinit(); | ||
| 1254 | 1375 | ||
| 1255 | for (rows.items) |row, j| { | 1376 | const RowType = struct { |
| 1256 | const exp_row = expected_rows.items[j]; | 1377 | name: Text, |
| 1257 | testing.expectEqualStrings(exp_row.name, row.name.data); | 1378 | age: usize, |
| 1258 | testing.expectEqual(exp_row.age, row.age); | 1379 | }; |
| 1380 | |||
| 1381 | var iter = try stmt2.iterator(RowType, .{}); | ||
| 1382 | |||
| 1383 | var rows = std.ArrayList(RowType).init(allocator); | ||
| 1384 | while (true) { | ||
| 1385 | const row = (try iter.nextAlloc(allocator, .{})) orelse break; | ||
| 1386 | try rows.append(row); | ||
| 1387 | } | ||
| 1388 | |||
| 1389 | // Check the data | ||
| 1390 | testing.expectEqual(expected_rows.items.len, rows.items.len); | ||
| 1391 | |||
| 1392 | for (rows.items) |row, j| { | ||
| 1393 | const exp_row = expected_rows.items[j]; | ||
| 1394 | testing.expectEqualStrings(exp_row.name, row.name.data); | ||
| 1395 | testing.expectEqual(exp_row.age, row.age); | ||
| 1396 | } | ||
| 1259 | } | 1397 | } |
| 1260 | } | 1398 | } |
| 1261 | 1399 | ||