summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2020-12-31 15:28:26 +0100
committerGravatar GitHub2020-12-31 15:28:26 +0100
commit70ed4b3ae4c78adf5fbc5aada642158bf33d7c3e (patch)
tree78d32ef16cc107bcb3eaadc1c168b385b0cd164c
parentdocument OpenFlags (diff)
parentfix readme (diff)
downloadzig-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
-rw-r--r--README.md181
-rw-r--r--sqlite.zig318
2 files changed, 331 insertions, 168 deletions
diff --git a/README.md b/README.md
index 452e3e7..dea6990 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,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
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
225Here are the rules for bind parameters: 251Here 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
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),
diff --git a/sqlite.zig b/sqlite.zig
index e72c2d5..6abedc4 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -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
1143test "sqlite: read a single value into bool" { 1247test "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