diff options
| -rw-r--r-- | sqlite.zig | 115 |
1 files changed, 88 insertions, 27 deletions
| @@ -186,6 +186,9 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 186 | }; | 186 | }; |
| 187 | } | 187 | } |
| 188 | 188 | ||
| 189 | /// deinit releases the prepared statement. | ||
| 190 | /// | ||
| 191 | /// After a call to `deinit` the statement must not be used. | ||
| 189 | pub fn deinit(self: *Self) void { | 192 | pub fn deinit(self: *Self) void { |
| 190 | const result = c.sqlite3_finalize(self.stmt); | 193 | const result = c.sqlite3_finalize(self.stmt); |
| 191 | if (result != c.SQLITE_OK) { | 194 | if (result != c.SQLITE_OK) { |
| @@ -193,6 +196,32 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 193 | } | 196 | } |
| 194 | } | 197 | } |
| 195 | 198 | ||
| 199 | /// reset resets the prepared statement to make it reusable. | ||
| 200 | pub fn reset(self: *Self) void { | ||
| 201 | const result = c.sqlite3_clear_bindings(self.stmt); | ||
| 202 | if (result != c.SQLITE_OK) { | ||
| 203 | logger.err("unable to clear prepared statement bindings, result: {}", .{result}); | ||
| 204 | } | ||
| 205 | |||
| 206 | const result2 = c.sqlite3_reset(self.stmt); | ||
| 207 | if (result2 != c.SQLITE_OK) { | ||
| 208 | logger.err("unable to reset prepared statement, result: {}", .{result2}); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | /// bind binds values to every bind marker in the prepared statement. | ||
| 213 | /// | ||
| 214 | /// The `values` variable must be a struct where each field has the type of the corresponding bind marker. | ||
| 215 | /// For example this query: | ||
| 216 | /// SELECT 1 FROM user WHERE name = ?{text} AND age < ?{u32} | ||
| 217 | /// | ||
| 218 | /// Has two bind markers, so `values` must have at least the following fields: | ||
| 219 | /// struct { | ||
| 220 | /// name: Text, | ||
| 221 | /// age: u32 | ||
| 222 | /// } | ||
| 223 | /// | ||
| 224 | /// The types are checked at comptime. | ||
| 196 | pub fn bind(self: *Self, values: anytype) void { | 225 | pub fn bind(self: *Self, values: anytype) void { |
| 197 | const StructType = @TypeOf(values); | 226 | const StructType = @TypeOf(values); |
| 198 | const StructTypeInfo = @typeInfo(StructType).Struct; | 227 | const StructTypeInfo = @typeInfo(StructType).Struct; |
| @@ -242,7 +271,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 242 | 271 | ||
| 243 | /// exec executes a statement which does not return data. | 272 | /// exec executes a statement which does not return data. |
| 244 | /// | 273 | /// |
| 245 | /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers | 274 | /// The `values` variable is used for the bind parameters. It must have as many fields as there are bind markers |
| 246 | /// in the input query string. | 275 | /// in the input query string. |
| 247 | /// | 276 | /// |
| 248 | pub fn exec(self: *Self, values: anytype) !void { | 277 | pub fn exec(self: *Self, values: anytype) !void { |
| @@ -461,6 +490,27 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 461 | }; | 490 | }; |
| 462 | } | 491 | } |
| 463 | 492 | ||
| 493 | const AllDDL = &[_][]const u8{ | ||
| 494 | \\CREATE TABLE user( | ||
| 495 | \\ id integer PRIMARY KEY, | ||
| 496 | \\ name text, | ||
| 497 | \\ age integer | ||
| 498 | \\) | ||
| 499 | , | ||
| 500 | \\CREATE TABLE article( | ||
| 501 | \\ id integer PRIMARY KEY, | ||
| 502 | \\ author_id integer, | ||
| 503 | \\ data text, | ||
| 504 | \\ FOREIGN KEY(author_id) REFERENCES user(id) | ||
| 505 | \\) | ||
| 506 | }; | ||
| 507 | |||
| 508 | const TestUser = struct { | ||
| 509 | id: usize, | ||
| 510 | name: []const u8, | ||
| 511 | age: usize, | ||
| 512 | }; | ||
| 513 | |||
| 464 | test "sqlite: db init" { | 514 | test "sqlite: db init" { |
| 465 | var db: Db = undefined; | 515 | var db: Db = undefined; |
| 466 | try db.init(testing.allocator, .{ .mode = dbMode() }); | 516 | try db.init(testing.allocator, .{ .mode = dbMode() }); |
| @@ -476,34 +526,12 @@ test "sqlite: statement exec" { | |||
| 476 | try db.init(testing.allocator, .{ .mode = dbMode() }); | 526 | try db.init(testing.allocator, .{ .mode = dbMode() }); |
| 477 | 527 | ||
| 478 | // Create the tables | 528 | // Create the tables |
| 479 | 529 | inline for (AllDDL) |ddl| { | |
| 480 | comptime const all_ddl = &[_][]const u8{ | ||
| 481 | \\CREATE TABLE user( | ||
| 482 | \\ id integer PRIMARY KEY, | ||
| 483 | \\ name text, | ||
| 484 | \\ age integer | ||
| 485 | \\) | ||
| 486 | , | ||
| 487 | \\CREATE TABLE article( | ||
| 488 | \\ id integer PRIMARY KEY, | ||
| 489 | \\ author_id integer, | ||
| 490 | \\ data text, | ||
| 491 | \\ FOREIGN KEY(author_id) REFERENCES user(id) | ||
| 492 | \\) | ||
| 493 | }; | ||
| 494 | inline for (all_ddl) |ddl| { | ||
| 495 | try db.exec(ddl, .{}); | 530 | try db.exec(ddl, .{}); |
| 496 | } | 531 | } |
| 497 | 532 | ||
| 498 | // Add data | 533 | // Add data |
| 499 | 534 | const users = &[_]TestUser{ | |
| 500 | const User = struct { | ||
| 501 | id: usize, | ||
| 502 | name: []const u8, | ||
| 503 | age: usize, | ||
| 504 | }; | ||
| 505 | |||
| 506 | const users = &[_]User{ | ||
| 507 | .{ .id = 20, .name = "Vincent", .age = 33 }, | 535 | .{ .id = 20, .name = "Vincent", .age = 33 }, |
| 508 | .{ .id = 40, .name = "Julien", .age = 35 }, | 536 | .{ .id = 40, .name = "Julien", .age = 35 }, |
| 509 | .{ .id = 60, .name = "José", .age = 40 }, | 537 | .{ .id = 60, .name = "José", .age = 40 }, |
| @@ -522,7 +550,7 @@ test "sqlite: statement exec" { | |||
| 522 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); | 550 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); |
| 523 | defer stmt.deinit(); | 551 | defer stmt.deinit(); |
| 524 | 552 | ||
| 525 | var rows = try stmt.all(User, .{ .allocator = allocator }, .{ .id = @as(usize, 20) }); | 553 | var rows = try stmt.all(TestUser, .{ .allocator = allocator }, .{ .id = @as(usize, 20) }); |
| 526 | for (rows) |row| { | 554 | for (rows) |row| { |
| 527 | testing.expectEqual(users[0].id, row.id); | 555 | testing.expectEqual(users[0].id, row.id); |
| 528 | testing.expectEqualStrings(users[0].name, row.name); | 556 | testing.expectEqualStrings(users[0].name, row.name); |
| @@ -536,7 +564,7 @@ test "sqlite: statement exec" { | |||
| 536 | var stmt = try db.prepare("SELECT id, name, age FROM user"); | 564 | var stmt = try db.prepare("SELECT id, name, age FROM user"); |
| 537 | defer stmt.deinit(); | 565 | defer stmt.deinit(); |
| 538 | 566 | ||
| 539 | var rows = try stmt.all(User, .{ .allocator = allocator }, .{}); | 567 | var rows = try stmt.all(TestUser, .{ .allocator = allocator }, .{}); |
| 540 | testing.expectEqual(@as(usize, 3), rows.len); | 568 | testing.expectEqual(@as(usize, 3), rows.len); |
| 541 | for (rows) |row, i| { | 569 | for (rows) |row, i| { |
| 542 | const exp = users[i]; | 570 | const exp = users[i]; |
| @@ -624,6 +652,39 @@ test "sqlite: statement exec" { | |||
| 624 | } | 652 | } |
| 625 | } | 653 | } |
| 626 | 654 | ||
| 655 | test "sqlite: statement reset" { | ||
| 656 | var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
| 657 | defer arena.deinit(); | ||
| 658 | var allocator = &arena.allocator; | ||
| 659 | |||
| 660 | var db: Db = undefined; | ||
| 661 | try db.init(testing.allocator, .{ .mode = dbMode() }); | ||
| 662 | |||
| 663 | // Create the tables | ||
| 664 | inline for (AllDDL) |ddl| { | ||
| 665 | try db.exec(ddl, .{}); | ||
| 666 | } | ||
| 667 | |||
| 668 | // Add data | ||
| 669 | |||
| 670 | var stmt = try db.prepare("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})"); | ||
| 671 | defer stmt.deinit(); | ||
| 672 | |||
| 673 | const users = &[_]TestUser{ | ||
| 674 | .{ .id = 20, .name = "Vincent", .age = 33 }, | ||
| 675 | .{ .id = 40, .name = "Julien", .age = 35 }, | ||
| 676 | .{ .id = 60, .name = "José", .age = 40 }, | ||
| 677 | }; | ||
| 678 | |||
| 679 | for (users) |user| { | ||
| 680 | stmt.reset(); | ||
| 681 | try stmt.exec(user); | ||
| 682 | |||
| 683 | const rows_inserted = db.rowsAffected(); | ||
| 684 | testing.expectEqual(@as(usize, 1), rows_inserted); | ||
| 685 | } | ||
| 686 | } | ||
| 687 | |||
| 627 | fn dbMode() Db.Mode { | 688 | fn dbMode() Db.Mode { |
| 628 | return if (build_options.is_ci) blk: { | 689 | return if (build_options.is_ci) blk: { |
| 629 | break :blk .{ .Memory = {} }; | 690 | break :blk .{ .Memory = {} }; |