diff options
Diffstat (limited to '')
| -rw-r--r-- | sqlite.zig | 388 |
1 files changed, 234 insertions, 154 deletions
| @@ -109,7 +109,7 @@ pub const Db = struct { | |||
| 109 | /// This is done because we type check the bind parameters when executing the statement later. | 109 | /// This is done because we type check the bind parameters when executing the statement later. |
| 110 | /// | 110 | /// |
| 111 | pub fn prepare(self: *Self, comptime query: []const u8) !Statement(.{}, ParsedQuery.from(query)) { | 111 | pub fn prepare(self: *Self, comptime query: []const u8) !Statement(.{}, ParsedQuery.from(query)) { |
| 112 | @setEvalBranchQuota(3000); | 112 | @setEvalBranchQuota(10000); |
| 113 | const parsed_query = ParsedQuery.from(query); | 113 | const parsed_query = ParsedQuery.from(query); |
| 114 | return Statement(.{}, comptime parsed_query).prepare(self, 0); | 114 | return Statement(.{}, comptime parsed_query).prepare(self, 0); |
| 115 | } | 115 | } |
| @@ -163,21 +163,43 @@ pub fn Iterator(comptime Type: type) type { | |||
| 163 | 163 | ||
| 164 | const columns = c.sqlite3_column_count(self.stmt); | 164 | const columns = c.sqlite3_column_count(self.stmt); |
| 165 | 165 | ||
| 166 | return switch (TypeInfo) { | 166 | switch (Type) { |
| 167 | .Int => blk: { | 167 | []const u8, []u8 => { |
| 168 | debug.assert(columns == 1); | 168 | debug.assert(columns == 1); |
| 169 | break :blk try self.readInt(options); | 169 | var ret: Type = undefined; |
| 170 | try self.readBytes(options, .Text, 0, &ret); | ||
| 171 | return ret; | ||
| 170 | }, | 172 | }, |
| 171 | .Float => blk: { | 173 | Blob => { |
| 172 | debug.assert(columns == 1); | 174 | debug.assert(columns == 1); |
| 173 | break :blk try self.readFloat(options); | 175 | var ret: Type = undefined; |
| 176 | try self.readBytes(options, .Blob, 0, &ret.data); | ||
| 177 | return ret; | ||
| 174 | }, | 178 | }, |
| 175 | .Struct => blk: { | 179 | Text => { |
| 180 | debug.assert(columns == 1); | ||
| 181 | var ret: Type = undefined; | ||
| 182 | try self.readBytes(options, .Text, 0, &ret.data); | ||
| 183 | return ret; | ||
| 184 | }, | ||
| 185 | else => {}, | ||
| 186 | } | ||
| 187 | |||
| 188 | switch (TypeInfo) { | ||
| 189 | .Int => { | ||
| 190 | debug.assert(columns == 1); | ||
| 191 | return try self.readInt(options); | ||
| 192 | }, | ||
| 193 | .Float => { | ||
| 194 | debug.assert(columns == 1); | ||
| 195 | return try self.readFloat(options); | ||
| 196 | }, | ||
| 197 | .Struct => { | ||
| 176 | std.debug.assert(columns == TypeInfo.Struct.fields.len); | 198 | std.debug.assert(columns == TypeInfo.Struct.fields.len); |
| 177 | break :blk try self.readStruct(options); | 199 | return try self.readStruct(options); |
| 178 | }, | 200 | }, |
| 179 | else => @compileError("cannot read into type " ++ @typeName(Type)), | 201 | else => @compileError("cannot read into type " ++ @typeName(Type)), |
| 180 | }; | 202 | } |
| 181 | } | 203 | } |
| 182 | 204 | ||
| 183 | fn readInt(self: *Self, options: anytype) !Type { | 205 | fn readInt(self: *Self, options: anytype) !Type { |
| @@ -549,196 +571,255 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 549 | }; | 571 | }; |
| 550 | } | 572 | } |
| 551 | 573 | ||
| 552 | const AllDDL = &[_][]const u8{ | ||
| 553 | \\CREATE TABLE user( | ||
| 554 | \\ id integer PRIMARY KEY, | ||
| 555 | \\ name text, | ||
| 556 | \\ age integer | ||
| 557 | \\) | ||
| 558 | , | ||
| 559 | \\CREATE TABLE article( | ||
| 560 | \\ id integer PRIMARY KEY, | ||
| 561 | \\ author_id integer, | ||
| 562 | \\ data text, | ||
| 563 | \\ FOREIGN KEY(author_id) REFERENCES user(id) | ||
| 564 | \\) | ||
| 565 | }; | ||
| 566 | |||
| 567 | const TestUser = struct { | 574 | const TestUser = struct { |
| 568 | id: usize, | 575 | id: usize, |
| 569 | name: []const u8, | 576 | name: []const u8, |
| 570 | age: usize, | 577 | age: usize, |
| 571 | }; | 578 | }; |
| 572 | 579 | ||
| 573 | test "sqlite: db init" { | 580 | const test_users = &[_]TestUser{ |
| 574 | var db: Db = undefined; | 581 | .{ .id = 20, .name = "Vincent", .age = 33 }, |
| 575 | try db.init(testing.allocator, .{ .mode = dbMode() }); | 582 | .{ .id = 40, .name = "Julien", .age = 35 }, |
| 576 | try db.init(testing.allocator, .{}); | 583 | .{ .id = 60, .name = "José", .age = 40 }, |
| 577 | } | 584 | }; |
| 578 | |||
| 579 | test "sqlite: statement exec" { | ||
| 580 | var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
| 581 | defer arena.deinit(); | ||
| 582 | var allocator = &arena.allocator; | ||
| 583 | 585 | ||
| 584 | var db: Db = undefined; | 586 | fn addTestData(db: *Db) !void { |
| 585 | try db.init(testing.allocator, .{ .mode = dbMode() }); | 587 | const AllDDL = &[_][]const u8{ |
| 588 | \\CREATE TABLE user( | ||
| 589 | \\ id integer PRIMARY KEY, | ||
| 590 | \\ name text, | ||
| 591 | \\ age integer | ||
| 592 | \\) | ||
| 593 | , | ||
| 594 | \\CREATE TABLE article( | ||
| 595 | \\ id integer PRIMARY KEY, | ||
| 596 | \\ author_id integer, | ||
| 597 | \\ data text, | ||
| 598 | \\ FOREIGN KEY(author_id) REFERENCES user(id) | ||
| 599 | \\) | ||
| 600 | }; | ||
| 586 | 601 | ||
| 587 | // Create the tables | 602 | // Create the tables |
| 588 | inline for (AllDDL) |ddl| { | 603 | inline for (AllDDL) |ddl| { |
| 589 | try db.exec(ddl, .{}); | 604 | try db.exec(ddl, .{}); |
| 590 | } | 605 | } |
| 591 | 606 | ||
| 592 | // Add data | 607 | for (test_users) |user| { |
| 593 | const users = &[_]TestUser{ | ||
| 594 | .{ .id = 20, .name = "Vincent", .age = 33 }, | ||
| 595 | .{ .id = 40, .name = "Julien", .age = 35 }, | ||
| 596 | .{ .id = 60, .name = "José", .age = 40 }, | ||
| 597 | }; | ||
| 598 | |||
| 599 | for (users) |user| { | ||
| 600 | try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})", user); | 608 | try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})", user); |
| 601 | 609 | ||
| 602 | const rows_inserted = db.rowsAffected(); | 610 | const rows_inserted = db.rowsAffected(); |
| 603 | testing.expectEqual(@as(usize, 1), rows_inserted); | 611 | testing.expectEqual(@as(usize, 1), rows_inserted); |
| 604 | } | 612 | } |
| 613 | } | ||
| 614 | |||
| 615 | test "sqlite: db init" { | ||
| 616 | var db: Db = undefined; | ||
| 617 | try db.init(testing.allocator, .{ .mode = dbMode() }); | ||
| 618 | try db.init(testing.allocator, .{}); | ||
| 619 | } | ||
| 605 | 620 | ||
| 606 | // Read a single user | 621 | test "sqlite: statement exec" { |
| 622 | var db: Db = undefined; | ||
| 623 | try db.init(testing.allocator, .{ .mode = dbMode() }); | ||
| 624 | try addTestData(&db); | ||
| 607 | 625 | ||
| 626 | // Test with a Blob struct | ||
| 608 | { | 627 | { |
| 609 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); | 628 | try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{blob}, ?{u32})", .{ |
| 610 | defer stmt.deinit(); | 629 | .id = @as(usize, 200), |
| 630 | .name = Blob{ .data = "hello" }, | ||
| 631 | .age = @as(u32, 20), | ||
| 632 | }); | ||
| 633 | } | ||
| 611 | 634 | ||
| 612 | var rows = try stmt.all(TestUser, .{ .allocator = allocator }, .{ .id = @as(usize, 20) }); | 635 | // Test with a Text struct |
| 613 | for (rows) |row| { | 636 | { |
| 614 | testing.expectEqual(users[0].id, row.id); | 637 | try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{text}, ?{u32})", .{ |
| 615 | testing.expectEqualStrings(users[0].name, row.name); | 638 | .id = @as(usize, 201), |
| 616 | testing.expectEqual(users[0].age, row.age); | 639 | .name = Text{ .data = "hello" }, |
| 617 | } | 640 | .age = @as(u32, 20), |
| 641 | }); | ||
| 618 | } | 642 | } |
| 643 | } | ||
| 619 | 644 | ||
| 620 | // Read all users | 645 | test "sqlite: read a single user into a struct" { |
| 646 | var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
| 647 | defer arena.deinit(); | ||
| 621 | 648 | ||
| 622 | { | 649 | var db: Db = undefined; |
| 623 | var stmt = try db.prepare("SELECT id, name, age FROM user"); | 650 | try db.init(testing.allocator, .{ .mode = dbMode() }); |
| 624 | defer stmt.deinit(); | 651 | try addTestData(&db); |
| 625 | 652 | ||
| 626 | var rows = try stmt.all(TestUser, .{ .allocator = allocator }, .{}); | 653 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); |
| 627 | testing.expectEqual(@as(usize, 3), rows.len); | 654 | defer stmt.deinit(); |
| 628 | for (rows) |row, i| { | 655 | |
| 629 | const exp = users[i]; | 656 | var rows = try stmt.all( |
| 630 | testing.expectEqual(exp.id, row.id); | 657 | TestUser, |
| 631 | testing.expectEqualStrings(exp.name, row.name); | 658 | .{ .allocator = &arena.allocator }, |
| 632 | testing.expectEqual(exp.age, row.age); | 659 | .{ .id = @as(usize, 20) }, |
| 633 | } | 660 | ); |
| 661 | for (rows) |row| { | ||
| 662 | testing.expectEqual(test_users[0].id, row.id); | ||
| 663 | testing.expectEqualStrings(test_users[0].name, row.name); | ||
| 664 | testing.expectEqual(test_users[0].age, row.age); | ||
| 634 | } | 665 | } |
| 666 | } | ||
| 635 | 667 | ||
| 636 | // Test with anonymous structs | 668 | test "sqlite: read all users into a struct" { |
| 669 | var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
| 670 | defer arena.deinit(); | ||
| 637 | 671 | ||
| 638 | { | 672 | var db: Db = undefined; |
| 639 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); | 673 | try db.init(testing.allocator, .{ .mode = dbMode() }); |
| 640 | defer stmt.deinit(); | 674 | try addTestData(&db); |
| 641 | 675 | ||
| 642 | var row = try stmt.one( | 676 | var stmt = try db.prepare("SELECT id, name, age FROM user"); |
| 643 | struct { | 677 | defer stmt.deinit(); |
| 644 | id: usize, | ||
| 645 | name: []const u8, | ||
| 646 | age: usize, | ||
| 647 | }, | ||
| 648 | .{ .allocator = allocator }, | ||
| 649 | .{ .id = @as(usize, 20) }, | ||
| 650 | ); | ||
| 651 | testing.expect(row != null); | ||
| 652 | 678 | ||
| 653 | const exp = users[0]; | 679 | var rows = try stmt.all( |
| 654 | testing.expectEqual(exp.id, row.?.id); | 680 | TestUser, |
| 655 | testing.expectEqualStrings(exp.name, row.?.name); | 681 | .{ .allocator = &arena.allocator }, |
| 656 | testing.expectEqual(exp.age, row.?.age); | 682 | .{}, |
| 683 | ); | ||
| 684 | testing.expectEqual(@as(usize, 3), rows.len); | ||
| 685 | for (rows) |row, i| { | ||
| 686 | const exp = test_users[i]; | ||
| 687 | testing.expectEqual(exp.id, row.id); | ||
| 688 | testing.expectEqualStrings(exp.name, row.name); | ||
| 689 | testing.expectEqual(exp.age, row.age); | ||
| 657 | } | 690 | } |
| 691 | } | ||
| 658 | 692 | ||
| 659 | // Test with a single integer or float | 693 | test "sqlite: read in an anonymous struct" { |
| 694 | var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
| 695 | defer arena.deinit(); | ||
| 660 | 696 | ||
| 661 | { | 697 | var db: Db = undefined; |
| 662 | const types = &[_]type{ | 698 | try db.init(testing.allocator, .{ .mode = dbMode() }); |
| 663 | u8, | 699 | try addTestData(&db); |
| 664 | u16, | ||
| 665 | u32, | ||
| 666 | u64, | ||
| 667 | u128, | ||
| 668 | usize, | ||
| 669 | f16, | ||
| 670 | f32, | ||
| 671 | f64, | ||
| 672 | f128, | ||
| 673 | }; | ||
| 674 | 700 | ||
| 675 | inline for (types) |typ| { | 701 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); |
| 676 | const query = "SELECT age FROM user WHERE id = ?{usize}"; | 702 | defer stmt.deinit(); |
| 677 | 703 | ||
| 678 | @setEvalBranchQuota(5000); | 704 | var row = try stmt.one( |
| 679 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 705 | struct { |
| 680 | defer stmt.deinit(); | 706 | id: usize, |
| 707 | name: []const u8, | ||
| 708 | age: usize, | ||
| 709 | }, | ||
| 710 | .{ .allocator = &arena.allocator }, | ||
| 711 | .{ .id = @as(usize, 20) }, | ||
| 712 | ); | ||
| 713 | testing.expect(row != null); | ||
| 714 | |||
| 715 | const exp = test_users[0]; | ||
| 716 | testing.expectEqual(exp.id, row.?.id); | ||
| 717 | testing.expectEqualStrings(exp.name, row.?.name); | ||
| 718 | testing.expectEqual(exp.age, row.?.age); | ||
| 719 | } | ||
| 681 | 720 | ||
| 682 | var age = try stmt.one(typ, .{}, .{ .id = @as(usize, 20) }); | 721 | test "sqlite: read in a Text struct" { |
| 683 | testing.expect(age != null); | 722 | var arena = std.heap.ArenaAllocator.init(testing.allocator); |
| 723 | defer arena.deinit(); | ||
| 684 | 724 | ||
| 685 | testing.expectEqual(@as(typ, 33), age.?); | 725 | var db: Db = undefined; |
| 726 | try db.init(testing.allocator, .{ .mode = dbMode() }); | ||
| 727 | try addTestData(&db); | ||
| 728 | |||
| 729 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); | ||
| 730 | defer stmt.deinit(); | ||
| 731 | |||
| 732 | var row = try stmt.one( | ||
| 733 | struct { | ||
| 734 | id: usize, | ||
| 735 | name: Text, | ||
| 736 | age: usize, | ||
| 737 | }, | ||
| 738 | .{ .allocator = &arena.allocator }, | ||
| 739 | .{@as(usize, 20)}, | ||
| 740 | ); | ||
| 741 | testing.expect(row != null); | ||
| 742 | |||
| 743 | const exp = test_users[0]; | ||
| 744 | testing.expectEqual(exp.id, row.?.id); | ||
| 745 | testing.expectEqualStrings(exp.name, row.?.name.data); | ||
| 746 | testing.expectEqual(exp.age, row.?.age); | ||
| 747 | } | ||
| 748 | |||
| 749 | test "sqlite: read a single text value" { | ||
| 750 | var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
| 751 | defer arena.deinit(); | ||
| 752 | |||
| 753 | var db: Db = undefined; | ||
| 754 | try db.init(testing.allocator, .{ .mode = dbMode() }); | ||
| 755 | try addTestData(&db); | ||
| 756 | |||
| 757 | const types = &[_]type{ | ||
| 758 | []const u8, | ||
| 759 | []u8, | ||
| 760 | Text, | ||
| 761 | Blob, | ||
| 762 | }; | ||
| 763 | |||
| 764 | inline for (types) |typ| { | ||
| 765 | const query = "SELECT name FROM user WHERE id = ?{usize}"; | ||
| 766 | |||
| 767 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | ||
| 768 | defer stmt.deinit(); | ||
| 769 | |||
| 770 | const name = try stmt.one( | ||
| 771 | typ, | ||
| 772 | .{ .allocator = &arena.allocator }, | ||
| 773 | .{ .id = @as(usize, 20) }, | ||
| 774 | ); | ||
| 775 | testing.expect(name != null); | ||
| 776 | switch (typ) { | ||
| 777 | Text, Blob => { | ||
| 778 | testing.expectEqualStrings("Vincent", name.?.data); | ||
| 779 | }, | ||
| 780 | else => { | ||
| 781 | testing.expectEqualStrings("Vincent", name.?); | ||
| 782 | }, | ||
| 686 | } | 783 | } |
| 687 | } | 784 | } |
| 785 | } | ||
| 688 | 786 | ||
| 689 | // Test with a Blob struct | 787 | test "sqlite: read a single integer value" { |
| 690 | { | 788 | var db: Db = undefined; |
| 691 | try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{blob}, ?{u32})", .{ | 789 | try db.init(testing.allocator, .{ .mode = dbMode() }); |
| 692 | .id = @as(usize, 200), | 790 | try addTestData(&db); |
| 693 | .name = Blob{ .data = "hello" }, | 791 | |
| 694 | .age = @as(u32, 20), | 792 | const types = &[_]type{ |
| 695 | }); | 793 | u8, |
| 696 | } | 794 | u16, |
| 795 | u32, | ||
| 796 | u64, | ||
| 797 | u128, | ||
| 798 | usize, | ||
| 799 | f16, | ||
| 800 | f32, | ||
| 801 | f64, | ||
| 802 | f128, | ||
| 803 | }; | ||
| 697 | 804 | ||
| 698 | // Test with a Text struct | 805 | inline for (types) |typ| { |
| 699 | { | 806 | const query = "SELECT age FROM user WHERE id = ?{usize}"; |
| 700 | try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{text}, ?{u32})", .{ | ||
| 701 | .id = @as(usize, 201), | ||
| 702 | .name = Text{ .data = "hello" }, | ||
| 703 | .age = @as(u32, 20), | ||
| 704 | }); | ||
| 705 | } | ||
| 706 | 807 | ||
| 707 | // Read in a Text struct | 808 | @setEvalBranchQuota(5000); |
| 708 | { | 809 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 709 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); | ||
| 710 | defer stmt.deinit(); | 810 | defer stmt.deinit(); |
| 711 | 811 | ||
| 712 | var row = try stmt.one( | 812 | var age = try stmt.one(typ, .{}, .{ .id = @as(usize, 20) }); |
| 713 | struct { | 813 | testing.expect(age != null); |
| 714 | id: usize, | ||
| 715 | name: Text, | ||
| 716 | age: usize, | ||
| 717 | }, | ||
| 718 | .{ .allocator = allocator }, | ||
| 719 | .{@as(usize, 20)}, | ||
| 720 | ); | ||
| 721 | testing.expect(row != null); | ||
| 722 | 814 | ||
| 723 | const exp = users[0]; | 815 | testing.expectEqual(@as(typ, 33), age.?); |
| 724 | testing.expectEqual(exp.id, row.?.id); | ||
| 725 | testing.expectEqualStrings(exp.name, row.?.name.data); | ||
| 726 | testing.expectEqual(exp.age, row.?.age); | ||
| 727 | } | 816 | } |
| 728 | } | 817 | } |
| 729 | 818 | ||
| 730 | test "sqlite: statement reset" { | 819 | test "sqlite: statement reset" { |
| 731 | var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
| 732 | defer arena.deinit(); | ||
| 733 | var allocator = &arena.allocator; | ||
| 734 | |||
| 735 | var db: Db = undefined; | 820 | var db: Db = undefined; |
| 736 | try db.init(testing.allocator, .{ .mode = dbMode() }); | 821 | try db.init(testing.allocator, .{ .mode = dbMode() }); |
| 737 | 822 | try addTestData(&db); | |
| 738 | // Create the tables | ||
| 739 | inline for (AllDDL) |ddl| { | ||
| 740 | try db.exec(ddl, .{}); | ||
| 741 | } | ||
| 742 | 823 | ||
| 743 | // Add data | 824 | // Add data |
| 744 | 825 | ||
| @@ -746,9 +827,9 @@ test "sqlite: statement reset" { | |||
| 746 | defer stmt.deinit(); | 827 | defer stmt.deinit(); |
| 747 | 828 | ||
| 748 | const users = &[_]TestUser{ | 829 | const users = &[_]TestUser{ |
| 749 | .{ .id = 20, .name = "Vincent", .age = 33 }, | 830 | .{ .id = 200, .name = "Vincent", .age = 33 }, |
| 750 | .{ .id = 40, .name = "Julien", .age = 35 }, | 831 | .{ .id = 400, .name = "Julien", .age = 35 }, |
| 751 | .{ .id = 60, .name = "José", .age = 40 }, | 832 | .{ .id = 600, .name = "José", .age = 40 }, |
| 752 | }; | 833 | }; |
| 753 | 834 | ||
| 754 | for (users) |user| { | 835 | for (users) |user| { |
| @@ -767,11 +848,10 @@ test "sqlite: statement iterator" { | |||
| 767 | 848 | ||
| 768 | var db: Db = undefined; | 849 | var db: Db = undefined; |
| 769 | try db.init(testing.allocator, .{ .mode = dbMode() }); | 850 | try db.init(testing.allocator, .{ .mode = dbMode() }); |
| 851 | try addTestData(&db); | ||
| 770 | 852 | ||
| 771 | // Create the tables | 853 | // Cleanup first |
| 772 | inline for (AllDDL) |ddl| { | 854 | try db.exec("DELETE FROM user", .{}); |
| 773 | try db.exec(ddl, .{}); | ||
| 774 | } | ||
| 775 | 855 | ||
| 776 | // Add data | 856 | // Add data |
| 777 | var stmt = try db.prepare("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})"); | 857 | var stmt = try db.prepare("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})"); |