summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2020-11-13 16:43:26 +0100
committerGravatar Vincent Rischmann2020-11-13 16:43:27 +0100
commit0564d740b708f4c01d07248ae0dab676462ca1da (patch)
tree18c58fffc7319942dc8d6f585ddd3bd7174125ec
parentMerge branch 'iterator' into master (diff)
downloadzig-sqlite-0564d740b708f4c01d07248ae0dab676462ca1da.tar.gz
zig-sqlite-0564d740b708f4c01d07248ae0dab676462ca1da.tar.xz
zig-sqlite-0564d740b708f4c01d07248ae0dab676462ca1da.zip
allow reading a single string in one() and all()
Also refactor the tests.
-rw-r--r--sqlite.zig388
1 files changed, 234 insertions, 154 deletions
diff --git a/sqlite.zig b/sqlite.zig
index 3c2e924..1b50d36 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -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
552const 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
567const TestUser = struct { 574const 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
573test "sqlite: db init" { 580const 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
579test "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; 586fn 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
615test "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 621test "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 645test "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 668test "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 693test "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) }); 721test "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
749test "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 787test "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
730test "sqlite: statement reset" { 819test "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})");