summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2020-10-25 01:40:44 +0200
committerGravatar Vincent Rischmann2020-10-25 01:59:47 +0200
commit28bf7944043cad990f61e8e0e91d05ae3a8163cf (patch)
tree05b2433b77997a68e37ba5db8755f860874980eb
parentMerge branch 'text' (diff)
downloadzig-sqlite-28bf7944043cad990f61e8e0e91d05ae3a8163cf.tar.gz
zig-sqlite-28bf7944043cad990f61e8e0e91d05ae3a8163cf.tar.xz
zig-sqlite-28bf7944043cad990f61e8e0e91d05ae3a8163cf.zip
refactor API
It doesn't make sense to pass the bind parameters in prepare; instead pass them in `exec`, `one`, and `all`.
Diffstat (limited to '')
-rw-r--r--sqlite.zig474
1 files changed, 246 insertions, 228 deletions
diff --git a/sqlite.zig b/sqlite.zig
index 116807a..d140e56 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -88,9 +88,9 @@ pub const Db = struct {
88 88
89 /// exec is a convenience function which prepares a statement and executes it directly. 89 /// exec is a convenience function which prepares a statement and executes it directly.
90 pub fn exec(self: *Self, comptime query: []const u8, values: anytype) !void { 90 pub fn exec(self: *Self, comptime query: []const u8, values: anytype) !void {
91 var stmt = try self.prepare(query, values); 91 var stmt = try self.prepare(query);
92 defer stmt.deinit(); 92 defer stmt.deinit();
93 try stmt.exec(); 93 try stmt.exec(values);
94 } 94 }
95 95
96 /// prepare prepares a statement for the `query` provided. 96 /// prepare prepares a statement for the `query` provided.
@@ -107,8 +107,8 @@ pub const Db = struct {
107 /// defer stmt.deinit(); 107 /// defer stmt.deinit();
108 /// 108 ///
109 /// Note that the name of the fields in the tuple are irrelevant, only the types are. 109 /// Note that the name of the fields in the tuple are irrelevant, only the types are.
110 pub fn prepare(self: *Self, comptime query: []const u8, values: anytype) !Statement { 110 pub fn prepare(self: *Self, comptime query: []const u8) !Statement(StatementOptions.from(query)) {
111 return Statement.prepare(self, 0, query, values); 111 return Statement(comptime StatementOptions.from(query)).prepare(self, 0, query);
112 } 112 }
113 113
114 /// rowsAffected returns the number of rows affected by the last statement executed. 114 /// rowsAffected returns the number of rows affected by the last statement executed.
@@ -128,6 +128,18 @@ pub const Bytes = union(enum) {
128 Text: []const u8, 128 Text: []const u8,
129}; 129};
130 130
131pub const StatementOptions = struct {
132 const Self = @This();
133
134 bind_markers: usize,
135
136 fn from(comptime query: []const u8) Self {
137 return Self{
138 .bind_markers = std.mem.count(u8, query, "?"),
139 };
140 }
141};
142
131/// Statement is a wrapper around a SQLite statement, providing high-level functions to execute 143/// Statement is a wrapper around a SQLite statement, providing high-level functions to execute
132/// a statement and retrieve rows for SELECT queries. 144/// a statement and retrieve rows for SELECT queries.
133/// 145///
@@ -159,259 +171,264 @@ pub const Bytes = union(enum) {
159/// 171///
160/// Look at aach function for more complete documentation. 172/// Look at aach function for more complete documentation.
161/// 173///
162pub const Statement = struct { 174pub fn Statement(comptime opts: StatementOptions) type {
163 const Self = @This(); 175 return struct {
176 const Self = @This();
164 177
165 stmt: *c.sqlite3_stmt, 178 stmt: *c.sqlite3_stmt,
166 179
167 const BytesType = enum { 180 const BytesType = enum {
168 Text, 181 Text,
169 Blob, 182 Blob,
170 };
171
172 fn prepare(db: *Db, flags: c_uint, comptime query: []const u8, values: anytype) !Self {
173 const StructType = @TypeOf(values);
174 const StructTypeInfo = @typeInfo(StructType).Struct;
175 comptime {
176 const bind_parameter_count = std.mem.count(u8, query, "?");
177 if (bind_parameter_count != StructTypeInfo.fields.len) {
178 @compileError("bind parameter count != number of fields in tuple/struct");
179 }
180 }
181
182 // prepare
183
184 var stmt = blk: {
185 var tmp: ?*c.sqlite3_stmt = undefined;
186 const result = c.sqlite3_prepare_v3(
187 db.db,
188 query.ptr,
189 @intCast(c_int, query.len),
190 flags,
191 &tmp,
192 null,
193 );
194 if (result != c.SQLITE_OK) {
195 logger.warn("unable to prepare statement, result: {}", .{result});
196 return error.CannotPrepareStatement;
197 }
198 break :blk tmp.?;
199 }; 183 };
200 184
201 // Bind 185 fn prepare(db: *Db, flags: c_uint, comptime query: []const u8) !Self {
186 // prepare
187 var stmt = blk: {
188 var tmp: ?*c.sqlite3_stmt = undefined;
189 const result = c.sqlite3_prepare_v3(
190 db.db,
191 query.ptr,
192 @intCast(c_int, query.len),
193 flags,
194 &tmp,
195 null,
196 );
197 if (result != c.SQLITE_OK) {
198 logger.warn("unable to prepare statement, result: {}", .{result});
199 return error.CannotPrepareStatement;
200 }
201 break :blk tmp.?;
202 };
202 203
203 inline for (StructTypeInfo.fields) |struct_field, _i| { 204 return Self{
204 const i = @as(usize, _i); 205 .stmt = stmt,
205 const field_type_info = @typeInfo(struct_field.field_type); 206 };
206 const field_value = @field(values, struct_field.name); 207 }
207 const column = i + 1;
208 208
209 switch (struct_field.field_type) { 209 pub fn deinit(self: *Self) void {
210 []const u8, []u8 => { 210 const result = c.sqlite3_finalize(self.stmt);
211 _ = c.sqlite3_bind_text(stmt, column, field_value.ptr, @intCast(c_int, field_value.len), null); 211 if (result != c.SQLITE_OK) {
212 }, 212 logger.err("unable to finalize prepared statement, result: {}", .{result});
213 Bytes => switch (field_value) {
214 .Text => |v| _ = c.sqlite3_bind_text(stmt, column, v.ptr, @intCast(c_int, v.len), null),
215 .Blob => |v| _ = c.sqlite3_bind_blob(stmt, column, v.ptr, @intCast(c_int, v.len), null),
216 },
217 else => switch (field_type_info) {
218 .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(stmt, column, @intCast(c_longlong, field_value)),
219 .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(stmt, column, field_value),
220 .Array => |arr| {
221 switch (arr.child) {
222 u8 => {
223 const data: []const u8 = field_value[0..field_value.len];
224
225 _ = c.sqlite3_bind_text(stmt, column, data.ptr, @intCast(c_int, data.len), null);
226 },
227 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)),
228 }
229 },
230 else => @compileError("cannot bind field " ++ struct_field.name ++ " of type " ++ @typeName(struct_field.field_type)),
231 },
232 } 213 }
233 } 214 }
234 215
235 return Self{ 216 pub fn bind(self: *Self, values: anytype) void {
236 .stmt = stmt, 217 const StructType = @TypeOf(values);
237 }; 218 const StructTypeInfo = @typeInfo(StructType).Struct;
238 }
239 219
240 pub fn deinit(self: *Self) void { 220 if (comptime opts.bind_markers != StructTypeInfo.fields.len) {
241 const result = c.sqlite3_finalize(self.stmt); 221 @compileError("number of bind markers not equal to number of fields");
242 if (result != c.SQLITE_OK) { 222 }
243 logger.err("unable to finalize prepared statement, result: {}", .{result});
244 }
245 }
246 223
247 pub fn exec(self: *Self) !void { 224 inline for (StructTypeInfo.fields) |struct_field, _i| {
248 const result = c.sqlite3_step(self.stmt); 225 const i = @as(usize, _i);
249 switch (result) { 226 const field_type_info = @typeInfo(struct_field.field_type);
250 c.SQLITE_DONE => {}, 227 const field_value = @field(values, struct_field.name);
251 c.SQLITE_BUSY => return error.SQLiteBusy, 228 const column = i + 1;
252 else => std.debug.panic("invalid result {}", .{result}),
253 }
254 }
255 229
256 /// one reads a single row from the result set of this statement. 230 switch (struct_field.field_type) {
257 /// 231 []const u8, []u8 => {
258 /// The data in the row is used to populate a value of the type `Type`. 232 _ = c.sqlite3_bind_text(self.stmt, column, field_value.ptr, @intCast(c_int, field_value.len), null);
259 /// This means that `Type` must have as many fields as is returned in the query 233 },
260 /// executed by this statement. 234 Bytes => switch (field_value) {
261 /// This also means that the type of each field must be compatible with the SQLite type. 235 .Text => |v| _ = c.sqlite3_bind_text(self.stmt, column, v.ptr, @intCast(c_int, v.len), null),
262 /// 236 .Blob => |v| _ = c.sqlite3_bind_blob(self.stmt, column, v.ptr, @intCast(c_int, v.len), null),
263 /// Here is an example of how to use an anonymous struct type: 237 },
264 /// 238 else => switch (field_type_info) {
265 /// const row = try stmt.one( 239 .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(self.stmt, column, @intCast(c_longlong, field_value)),
266 /// struct { 240 .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(self.stmt, column, field_value),
267 /// id: usize, 241 .Array => |arr| {
268 /// name: []const u8, 242 switch (arr.child) {
269 /// age: usize, 243 u8 => {
270 /// }, 244 const data: []const u8 = field_value[0..field_value.len];
271 /// .{ .allocator = allocator }, 245
272 /// ); 246 _ = c.sqlite3_bind_text(self.stmt, column, data.ptr, @intCast(c_int, data.len), null);
273 /// 247 },
274 /// The `options` tuple is used to provide additional state in some cases, for example 248 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)),
275 /// an allocator used to read text and blobs. 249 }
276 /// 250 },
277 pub fn one(self: *Self, comptime Type: type, options: anytype) !?Type { 251 else => @compileError("cannot bind field " ++ struct_field.name ++ " of type " ++ @typeName(struct_field.field_type)),
278 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { 252 },
279 @compileError("options passed to all must be a struct"); 253 }
254 }
280 } 255 }
281 const TypeInfo = @typeInfo(Type);
282 256
283 var result = c.sqlite3_step(self.stmt); 257 pub fn exec(self: *Self, values: anytype) !void {
258 self.bind(values);
284 259
285 switch (TypeInfo) { 260 const result = c.sqlite3_step(self.stmt);
286 .Int => return switch (result) { 261 switch (result) {
287 c.SQLITE_ROW => try self.readInt(Type, options), 262 c.SQLITE_DONE => {},
288 c.SQLITE_DONE => null, 263 c.SQLITE_BUSY => return error.SQLiteBusy,
289 else => std.debug.panic("invalid result {}", .{result}),
290 },
291 .Struct => return switch (result) {
292 c.SQLITE_ROW => try self.readStruct(Type, options),
293 c.SQLITE_DONE => null,
294 else => std.debug.panic("invalid result {}", .{result}), 264 else => std.debug.panic("invalid result {}", .{result}),
295 }, 265 }
296 else => @compileError("cannot read into type " ++ @typeName(Type)),
297 } 266 }
298 }
299 267
300 /// all reads all rows from the result set of this statement. 268 /// one reads a single row from the result set of this statement.
301 /// 269 ///
302 /// The data in each row is used to populate a value of the type `Type`. 270 /// The data in the row is used to populate a value of the type `Type`.
303 /// This means that `Type` must have as many fields as is returned in the query 271 /// This means that `Type` must have as many fields as is returned in the query
304 /// executed by this statement. 272 /// executed by this statement.
305 /// This also means that the type of each field must be compatible with the SQLite type. 273 /// This also means that the type of each field must be compatible with the SQLite type.
306 /// 274 ///
307 /// Here is an example of how to use an anonymous struct type: 275 /// Here is an example of how to use an anonymous struct type:
308 /// 276 ///
309 /// const rows = try stmt.all( 277 /// const row = try stmt.one(
310 /// struct { 278 /// struct {
311 /// id: usize, 279 /// id: usize,
312 /// name: []const u8, 280 /// name: []const u8,
313 /// age: usize, 281 /// age: usize,
314 /// }, 282 /// },
315 /// .{ .allocator = allocator }, 283 /// .{ .allocator = allocator },
316 /// ); 284 /// );
317 /// 285 ///
318 /// The `options` tuple is used to provide additional state in some cases. 286 /// The `options` tuple is used to provide additional state in some cases, for example
319 /// Note that for this function the allocator is mandatory. 287 /// an allocator used to read text and blobs.
320 /// 288 ///
321 pub fn all(self: *Self, comptime Type: type, options: anytype) ![]Type { 289 pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type {
322 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { 290 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) {
323 @compileError("options passed to all must be a struct"); 291 @compileError("options passed to all must be a struct");
324 } 292 }
325 const TypeInfo = @typeInfo(Type); 293 const TypeInfo = @typeInfo(Type);
326 294
327 var rows = std.ArrayList(Type).init(options.allocator); 295 self.bind(values);
328 296
329 var result = c.sqlite3_step(self.stmt); 297 var result = c.sqlite3_step(self.stmt);
330 while (result == c.SQLITE_ROW) : (result = c.sqlite3_step(self.stmt)) {
331 const columns = c.sqlite3_column_count(self.stmt);
332 298
333 var value = switch (TypeInfo) { 299 switch (TypeInfo) {
334 .Int => blk: { 300 .Int => return switch (result) {
335 debug.assert(columns == 1); 301 c.SQLITE_ROW => try self.readInt(Type, options),
336 break :blk try self.readInt(Type, options); 302 c.SQLITE_DONE => null,
303 else => std.debug.panic("invalid result {}", .{result}),
337 }, 304 },
338 .Struct => blk: { 305 .Struct => return switch (result) {
339 std.debug.assert(columns == @typeInfo(Type).Struct.fields.len); 306 c.SQLITE_ROW => try self.readStruct(Type, options),
340 break :blk try self.readStruct(Type, options); 307 c.SQLITE_DONE => null,
308 else => std.debug.panic("invalid result {}", .{result}),
341 }, 309 },
342 else => @compileError("cannot read into type " ++ @typeName(Type)), 310 else => @compileError("cannot read into type " ++ @typeName(Type)),
343 }; 311 }
344
345 try rows.append(value);
346 } 312 }
347 313
348 if (result != c.SQLITE_DONE) { 314 /// all reads all rows from the result set of this statement.
349 logger.err("unable to iterate, result: {}", .{result}); 315 ///
350 return error.SQLiteStepError; 316 /// The data in each row is used to populate a value of the type `Type`.
351 } 317 /// This means that `Type` must have as many fields as is returned in the query
318 /// executed by this statement.
319 /// This also means that the type of each field must be compatible with the SQLite type.
320 ///
321 /// Here is an example of how to use an anonymous struct type:
322 ///
323 /// const rows = try stmt.all(
324 /// struct {
325 /// id: usize,
326 /// name: []const u8,
327 /// age: usize,
328 /// },
329 /// .{ .allocator = allocator },
330 /// );
331 ///
332 /// The `options` tuple is used to provide additional state in some cases.
333 /// Note that for this function the allocator is mandatory.
334 ///
335 pub fn all(self: *Self, comptime Type: type, options: anytype, values: anytype) ![]Type {
336 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) {
337 @compileError("options passed to all must be a struct");
338 }
339 const TypeInfo = @typeInfo(Type);
352 340
353 return rows.span(); 341 self.bind(values);
354 }
355 342
356 fn readInt(self: *Self, comptime Type: type, options: anytype) !Type { 343 var rows = std.ArrayList(Type).init(options.allocator);
357 const n = c.sqlite3_column_int64(self.stmt, 0);
358 return @intCast(Type, n);
359 }
360 344
361 fn readStruct(self: *Self, comptime Type: type, options: anytype) !Type { 345 var result = c.sqlite3_step(self.stmt);
362 var value: Type = undefined; 346 while (result == c.SQLITE_ROW) : (result = c.sqlite3_step(self.stmt)) {
347 const columns = c.sqlite3_column_count(self.stmt);
363 348
364 inline for (@typeInfo(Type).Struct.fields) |field, _i| { 349 var value = switch (TypeInfo) {
365 const i = @as(usize, _i); 350 .Int => blk: {
366 const field_type_info = @typeInfo(field.field_type); 351 debug.assert(columns == 1);
352 break :blk try self.readInt(Type, options);
353 },
354 .Struct => blk: {
355 std.debug.assert(columns == @typeInfo(Type).Struct.fields.len);
356 break :blk try self.readStruct(Type, options);
357 },
358 else => @compileError("cannot read into type " ++ @typeName(Type)),
359 };
367 360
368 switch (field.field_type) { 361 try rows.append(value);
369 []const u8, []u8 => { 362 }
370 const data = c.sqlite3_column_blob(self.stmt, i);
371 if (data == null) {
372 @field(value, field.name) = "";
373 } else {
374 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
375 363
376 var tmp = try options.allocator.alloc(u8, size); 364 if (result != c.SQLITE_DONE) {
377 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]); 365 logger.err("unable to iterate, result: {}", .{result});
366 return error.SQLiteStepError;
367 }
378 368
379 @field(value, field.name) = tmp; 369 return rows.span();
380 } 370 }
381 }, 371
382 else => switch (field_type_info) { 372 fn readInt(self: *Self, comptime Type: type, options: anytype) !Type {
383 .Int => { 373 const n = c.sqlite3_column_int64(self.stmt, 0);
384 const n = c.sqlite3_column_int64(self.stmt, i); 374 return @intCast(Type, n);
385 @field(value, field.name) = @intCast(field.field_type, n); 375 }
386 }, 376
387 .Float => { 377 fn readStruct(self: *Self, comptime Type: type, options: anytype) !Type {
388 const f = c.sqlite3_column_double(self.stmt, i); 378 var value: Type = undefined;
389 @field(value, field.name) = f; 379
390 }, 380 inline for (@typeInfo(Type).Struct.fields) |field, _i| {
391 .Void => { 381 const i = @as(usize, _i);
392 @field(value, field.name) = {}; 382 const field_type_info = @typeInfo(field.field_type);
393 }, 383
394 .Array => |arr| { 384 switch (field.field_type) {
395 switch (arr.child) { 385 []const u8, []u8 => {
396 u8 => { 386 const data = c.sqlite3_column_blob(self.stmt, i);
397 const data = c.sqlite3_column_blob(self.stmt, i); 387 if (data == null) {
398 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i)); 388 @field(value, field.name) = "";
389 } else {
390 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
399 391
400 if (size > @as(usize, arr.len)) return error.ArrayTooSmall; 392 var tmp = try options.allocator.alloc(u8, size);
393 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]);
401 394
402 mem.copy(u8, @field(value, field.name)[0..], @ptrCast([*c]const u8, data)[0..size]); 395 @field(value, field.name) = tmp;
403 },
404 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)),
405 } 396 }
406 }, 397 },
407 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)), 398 else => switch (field_type_info) {
408 }, 399 .Int => {
400 const n = c.sqlite3_column_int64(self.stmt, i);
401 @field(value, field.name) = @intCast(field.field_type, n);
402 },
403 .Float => {
404 const f = c.sqlite3_column_double(self.stmt, i);
405 @field(value, field.name) = f;
406 },
407 .Void => {
408 @field(value, field.name) = {};
409 },
410 .Array => |arr| {
411 switch (arr.child) {
412 u8 => {
413 const data = c.sqlite3_column_blob(self.stmt, i);
414 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
415
416 if (size > @as(usize, arr.len)) return error.ArrayTooSmall;
417
418 mem.copy(u8, @field(value, field.name)[0..], @ptrCast([*c]const u8, data)[0..size]);
419 },
420 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)),
421 }
422 },
423 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)),
424 },
425 }
409 } 426 }
410 }
411 427
412 return value; 428 return value;
413 } 429 }
414}; 430 };
431}
415 432
416test "sqlite: db init" { 433test "sqlite: db init" {
417 var db: Db = undefined; 434 var db: Db = undefined;
@@ -444,9 +461,7 @@ test "sqlite: statement exec" {
444 \\) 461 \\)
445 }; 462 };
446 inline for (all_ddl) |ddl| { 463 inline for (all_ddl) |ddl| {
447 var stmt = try db.prepare(ddl, .{}); 464 try db.exec(ddl, .{});
448 defer stmt.deinit();
449 try stmt.exec();
450 } 465 }
451 466
452 // Add data 467 // Add data
@@ -473,10 +488,10 @@ test "sqlite: statement exec" {
473 // Read a single user 488 // Read a single user
474 489
475 { 490 {
476 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?", .{ .id = 20 }); 491 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?");
477 defer stmt.deinit(); 492 defer stmt.deinit();
478 493
479 var rows = try stmt.all(User, .{ .allocator = allocator }); 494 var rows = try stmt.all(User, .{ .allocator = allocator }, .{ .id = 20 });
480 for (rows) |row| { 495 for (rows) |row| {
481 testing.expectEqual(users[0].id, row.id); 496 testing.expectEqual(users[0].id, row.id);
482 testing.expectEqualStrings(users[0].name, row.name); 497 testing.expectEqualStrings(users[0].name, row.name);
@@ -487,10 +502,10 @@ test "sqlite: statement exec" {
487 // Read all users 502 // Read all users
488 503
489 { 504 {
490 var stmt = try db.prepare("SELECT id, name, age FROM user", .{}); 505 var stmt = try db.prepare("SELECT id, name, age FROM user");
491 defer stmt.deinit(); 506 defer stmt.deinit();
492 507
493 var rows = try stmt.all(User, .{ .allocator = allocator }); 508 var rows = try stmt.all(User, .{ .allocator = allocator }, .{});
494 testing.expectEqual(@as(usize, 3), rows.len); 509 testing.expectEqual(@as(usize, 3), rows.len);
495 for (rows) |row, i| { 510 for (rows) |row, i| {
496 const exp = users[i]; 511 const exp = users[i];
@@ -503,7 +518,7 @@ test "sqlite: statement exec" {
503 // Test with anonymous structs 518 // Test with anonymous structs
504 519
505 { 520 {
506 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?", .{ .id = 20 }); 521 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?");
507 defer stmt.deinit(); 522 defer stmt.deinit();
508 523
509 var row = try stmt.one( 524 var row = try stmt.one(
@@ -513,6 +528,7 @@ test "sqlite: statement exec" {
513 age: usize, 528 age: usize,
514 }, 529 },
515 .{ .allocator = allocator }, 530 .{ .allocator = allocator },
531 .{ .id = 20 },
516 ); 532 );
517 testing.expect(row != null); 533 testing.expect(row != null);
518 534
@@ -525,10 +541,12 @@ test "sqlite: statement exec" {
525 // Test with a single integer 541 // Test with a single integer
526 542
527 { 543 {
528 var stmt = try db.prepare("SELECT age FROM user WHERE id = ?", .{ .id = 20 }); 544 const query = "SELECT age FROM user WHERE id = ?";
545
546 var stmt: Statement(StatementOptions.from(query)) = try db.prepare(query);
529 defer stmt.deinit(); 547 defer stmt.deinit();
530 548
531 var age = try stmt.one(usize, .{}); 549 var age = try stmt.one(usize, .{}, .{ .id = 20 });
532 testing.expect(age != null); 550 testing.expect(age != null);
533 551
534 testing.expectEqual(@as(usize, 33), age.?); 552 testing.expectEqual(@as(usize, 33), age.?);