From 0564d740b708f4c01d07248ae0dab676462ca1da Mon Sep 17 00:00:00 2001 From: Vincent Rischmann Date: Fri, 13 Nov 2020 16:43:26 +0100 Subject: allow reading a single string in one() and all() Also refactor the tests. --- sqlite.zig | 388 +++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 234 insertions(+), 154 deletions(-) (limited to 'sqlite.zig') 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 { /// This is done because we type check the bind parameters when executing the statement later. /// pub fn prepare(self: *Self, comptime query: []const u8) !Statement(.{}, ParsedQuery.from(query)) { - @setEvalBranchQuota(3000); + @setEvalBranchQuota(10000); const parsed_query = ParsedQuery.from(query); return Statement(.{}, comptime parsed_query).prepare(self, 0); } @@ -163,21 +163,43 @@ pub fn Iterator(comptime Type: type) type { const columns = c.sqlite3_column_count(self.stmt); - return switch (TypeInfo) { - .Int => blk: { + switch (Type) { + []const u8, []u8 => { debug.assert(columns == 1); - break :blk try self.readInt(options); + var ret: Type = undefined; + try self.readBytes(options, .Text, 0, &ret); + return ret; }, - .Float => blk: { + Blob => { debug.assert(columns == 1); - break :blk try self.readFloat(options); + var ret: Type = undefined; + try self.readBytes(options, .Blob, 0, &ret.data); + return ret; }, - .Struct => blk: { + Text => { + debug.assert(columns == 1); + var ret: Type = undefined; + try self.readBytes(options, .Text, 0, &ret.data); + return ret; + }, + else => {}, + } + + switch (TypeInfo) { + .Int => { + debug.assert(columns == 1); + return try self.readInt(options); + }, + .Float => { + debug.assert(columns == 1); + return try self.readFloat(options); + }, + .Struct => { std.debug.assert(columns == TypeInfo.Struct.fields.len); - break :blk try self.readStruct(options); + return try self.readStruct(options); }, else => @compileError("cannot read into type " ++ @typeName(Type)), - }; + } } fn readInt(self: *Self, options: anytype) !Type { @@ -549,196 +571,255 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t }; } -const AllDDL = &[_][]const u8{ - \\CREATE TABLE user( - \\ id integer PRIMARY KEY, - \\ name text, - \\ age integer - \\) - , - \\CREATE TABLE article( - \\ id integer PRIMARY KEY, - \\ author_id integer, - \\ data text, - \\ FOREIGN KEY(author_id) REFERENCES user(id) - \\) -}; - const TestUser = struct { id: usize, name: []const u8, age: usize, }; -test "sqlite: db init" { - var db: Db = undefined; - try db.init(testing.allocator, .{ .mode = dbMode() }); - try db.init(testing.allocator, .{}); -} - -test "sqlite: statement exec" { - var arena = std.heap.ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - var allocator = &arena.allocator; +const test_users = &[_]TestUser{ + .{ .id = 20, .name = "Vincent", .age = 33 }, + .{ .id = 40, .name = "Julien", .age = 35 }, + .{ .id = 60, .name = "José", .age = 40 }, +}; - var db: Db = undefined; - try db.init(testing.allocator, .{ .mode = dbMode() }); +fn addTestData(db: *Db) !void { + const AllDDL = &[_][]const u8{ + \\CREATE TABLE user( + \\ id integer PRIMARY KEY, + \\ name text, + \\ age integer + \\) + , + \\CREATE TABLE article( + \\ id integer PRIMARY KEY, + \\ author_id integer, + \\ data text, + \\ FOREIGN KEY(author_id) REFERENCES user(id) + \\) + }; // Create the tables inline for (AllDDL) |ddl| { try db.exec(ddl, .{}); } - // Add data - const users = &[_]TestUser{ - .{ .id = 20, .name = "Vincent", .age = 33 }, - .{ .id = 40, .name = "Julien", .age = 35 }, - .{ .id = 60, .name = "José", .age = 40 }, - }; - - for (users) |user| { + for (test_users) |user| { try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})", user); const rows_inserted = db.rowsAffected(); testing.expectEqual(@as(usize, 1), rows_inserted); } +} + +test "sqlite: db init" { + var db: Db = undefined; + try db.init(testing.allocator, .{ .mode = dbMode() }); + try db.init(testing.allocator, .{}); +} - // Read a single user +test "sqlite: statement exec" { + var db: Db = undefined; + try db.init(testing.allocator, .{ .mode = dbMode() }); + try addTestData(&db); + // Test with a Blob struct { - var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); - defer stmt.deinit(); + try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{blob}, ?{u32})", .{ + .id = @as(usize, 200), + .name = Blob{ .data = "hello" }, + .age = @as(u32, 20), + }); + } - var rows = try stmt.all(TestUser, .{ .allocator = allocator }, .{ .id = @as(usize, 20) }); - for (rows) |row| { - testing.expectEqual(users[0].id, row.id); - testing.expectEqualStrings(users[0].name, row.name); - testing.expectEqual(users[0].age, row.age); - } + // Test with a Text struct + { + try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{text}, ?{u32})", .{ + .id = @as(usize, 201), + .name = Text{ .data = "hello" }, + .age = @as(u32, 20), + }); } +} - // Read all users +test "sqlite: read a single user into a struct" { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); - { - var stmt = try db.prepare("SELECT id, name, age FROM user"); - defer stmt.deinit(); + var db: Db = undefined; + try db.init(testing.allocator, .{ .mode = dbMode() }); + try addTestData(&db); - var rows = try stmt.all(TestUser, .{ .allocator = allocator }, .{}); - testing.expectEqual(@as(usize, 3), rows.len); - for (rows) |row, i| { - const exp = users[i]; - testing.expectEqual(exp.id, row.id); - testing.expectEqualStrings(exp.name, row.name); - testing.expectEqual(exp.age, row.age); - } + var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); + defer stmt.deinit(); + + var rows = try stmt.all( + TestUser, + .{ .allocator = &arena.allocator }, + .{ .id = @as(usize, 20) }, + ); + for (rows) |row| { + testing.expectEqual(test_users[0].id, row.id); + testing.expectEqualStrings(test_users[0].name, row.name); + testing.expectEqual(test_users[0].age, row.age); } +} - // Test with anonymous structs +test "sqlite: read all users into a struct" { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); - { - var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); - defer stmt.deinit(); + var db: Db = undefined; + try db.init(testing.allocator, .{ .mode = dbMode() }); + try addTestData(&db); - var row = try stmt.one( - struct { - id: usize, - name: []const u8, - age: usize, - }, - .{ .allocator = allocator }, - .{ .id = @as(usize, 20) }, - ); - testing.expect(row != null); + var stmt = try db.prepare("SELECT id, name, age FROM user"); + defer stmt.deinit(); - const exp = users[0]; - testing.expectEqual(exp.id, row.?.id); - testing.expectEqualStrings(exp.name, row.?.name); - testing.expectEqual(exp.age, row.?.age); + var rows = try stmt.all( + TestUser, + .{ .allocator = &arena.allocator }, + .{}, + ); + testing.expectEqual(@as(usize, 3), rows.len); + for (rows) |row, i| { + const exp = test_users[i]; + testing.expectEqual(exp.id, row.id); + testing.expectEqualStrings(exp.name, row.name); + testing.expectEqual(exp.age, row.age); } +} - // Test with a single integer or float +test "sqlite: read in an anonymous struct" { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); - { - const types = &[_]type{ - u8, - u16, - u32, - u64, - u128, - usize, - f16, - f32, - f64, - f128, - }; + var db: Db = undefined; + try db.init(testing.allocator, .{ .mode = dbMode() }); + try addTestData(&db); - inline for (types) |typ| { - const query = "SELECT age FROM user WHERE id = ?{usize}"; + var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); + defer stmt.deinit(); - @setEvalBranchQuota(5000); - var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); - defer stmt.deinit(); + var row = try stmt.one( + struct { + id: usize, + name: []const u8, + age: usize, + }, + .{ .allocator = &arena.allocator }, + .{ .id = @as(usize, 20) }, + ); + testing.expect(row != null); + + const exp = test_users[0]; + testing.expectEqual(exp.id, row.?.id); + testing.expectEqualStrings(exp.name, row.?.name); + testing.expectEqual(exp.age, row.?.age); +} - var age = try stmt.one(typ, .{}, .{ .id = @as(usize, 20) }); - testing.expect(age != null); +test "sqlite: read in a Text struct" { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); - testing.expectEqual(@as(typ, 33), age.?); + var db: Db = undefined; + try db.init(testing.allocator, .{ .mode = dbMode() }); + try addTestData(&db); + + var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); + defer stmt.deinit(); + + var row = try stmt.one( + struct { + id: usize, + name: Text, + age: usize, + }, + .{ .allocator = &arena.allocator }, + .{@as(usize, 20)}, + ); + testing.expect(row != null); + + const exp = test_users[0]; + testing.expectEqual(exp.id, row.?.id); + testing.expectEqualStrings(exp.name, row.?.name.data); + testing.expectEqual(exp.age, row.?.age); +} + +test "sqlite: read a single text value" { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + + var db: Db = undefined; + try db.init(testing.allocator, .{ .mode = dbMode() }); + try addTestData(&db); + + const types = &[_]type{ + []const u8, + []u8, + Text, + Blob, + }; + + inline for (types) |typ| { + const query = "SELECT name FROM user WHERE id = ?{usize}"; + + var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); + defer stmt.deinit(); + + const name = try stmt.one( + typ, + .{ .allocator = &arena.allocator }, + .{ .id = @as(usize, 20) }, + ); + testing.expect(name != null); + switch (typ) { + Text, Blob => { + testing.expectEqualStrings("Vincent", name.?.data); + }, + else => { + testing.expectEqualStrings("Vincent", name.?); + }, } } +} - // Test with a Blob struct - { - try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{blob}, ?{u32})", .{ - .id = @as(usize, 200), - .name = Blob{ .data = "hello" }, - .age = @as(u32, 20), - }); - } +test "sqlite: read a single integer value" { + var db: Db = undefined; + try db.init(testing.allocator, .{ .mode = dbMode() }); + try addTestData(&db); + + const types = &[_]type{ + u8, + u16, + u32, + u64, + u128, + usize, + f16, + f32, + f64, + f128, + }; - // Test with a Text struct - { - try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{text}, ?{u32})", .{ - .id = @as(usize, 201), - .name = Text{ .data = "hello" }, - .age = @as(u32, 20), - }); - } + inline for (types) |typ| { + const query = "SELECT age FROM user WHERE id = ?{usize}"; - // Read in a Text struct - { - var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); + @setEvalBranchQuota(5000); + var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); defer stmt.deinit(); - var row = try stmt.one( - struct { - id: usize, - name: Text, - age: usize, - }, - .{ .allocator = allocator }, - .{@as(usize, 20)}, - ); - testing.expect(row != null); + var age = try stmt.one(typ, .{}, .{ .id = @as(usize, 20) }); + testing.expect(age != null); - const exp = users[0]; - testing.expectEqual(exp.id, row.?.id); - testing.expectEqualStrings(exp.name, row.?.name.data); - testing.expectEqual(exp.age, row.?.age); + testing.expectEqual(@as(typ, 33), age.?); } } test "sqlite: statement reset" { - var arena = std.heap.ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - var allocator = &arena.allocator; - var db: Db = undefined; try db.init(testing.allocator, .{ .mode = dbMode() }); - - // Create the tables - inline for (AllDDL) |ddl| { - try db.exec(ddl, .{}); - } + try addTestData(&db); // Add data @@ -746,9 +827,9 @@ test "sqlite: statement reset" { defer stmt.deinit(); const users = &[_]TestUser{ - .{ .id = 20, .name = "Vincent", .age = 33 }, - .{ .id = 40, .name = "Julien", .age = 35 }, - .{ .id = 60, .name = "José", .age = 40 }, + .{ .id = 200, .name = "Vincent", .age = 33 }, + .{ .id = 400, .name = "Julien", .age = 35 }, + .{ .id = 600, .name = "José", .age = 40 }, }; for (users) |user| { @@ -767,11 +848,10 @@ test "sqlite: statement iterator" { var db: Db = undefined; try db.init(testing.allocator, .{ .mode = dbMode() }); + try addTestData(&db); - // Create the tables - inline for (AllDDL) |ddl| { - try db.exec(ddl, .{}); - } + // Cleanup first + try db.exec("DELETE FROM user", .{}); // Add data var stmt = try db.prepare("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})"); -- cgit v1.2.3