summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2020-10-30 13:49:05 +0100
committerGravatar Vincent Rischmann2020-11-11 13:31:50 +0100
commitbe6902d0736178c113d6b11c9b056c4a33a966f3 (patch)
tree634b14cf2624f77aa4f79e808b9dae2a85ee8f2c /sqlite.zig
parentupdate requirements (diff)
downloadzig-sqlite-be6902d0736178c113d6b11c9b056c4a33a966f3.tar.gz
zig-sqlite-be6902d0736178c113d6b11c9b056c4a33a966f3.tar.xz
zig-sqlite-be6902d0736178c113d6b11c9b056c4a33a966f3.zip
add types to bind markers and check them at comptime
Diffstat (limited to 'sqlite.zig')
-rw-r--r--sqlite.zig76
1 files changed, 31 insertions, 45 deletions
diff --git a/sqlite.zig b/sqlite.zig
index 5844c34..a30da70 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -8,6 +8,8 @@ const c = @cImport({
8 @cInclude("sqlite3.h"); 8 @cInclude("sqlite3.h");
9}); 9});
10 10
11usingnamespace @import("query.zig");
12
11const logger = std.log.scoped(.sqlite); 13const logger = std.log.scoped(.sqlite);
12 14
13/// Db is a wrapper around a SQLite database, providing high-level functions for executing queries. 15/// Db is a wrapper around a SQLite database, providing high-level functions for executing queries.
@@ -106,8 +108,9 @@ pub const Db = struct {
106 /// The statement returned is only compatible with the number of bind markers in the input query. 108 /// The statement returned is only compatible with the number of bind markers in the input query.
107 /// 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.
108 /// 110 ///
109 pub fn prepare(self: *Self, comptime query: []const u8) !Statement(StatementOptions.from(query)) { 111 pub fn prepare(self: *Self, comptime query: []const u8) !Statement(.{}, ParsedQuery.from(query)) {
110 return Statement(comptime StatementOptions.from(query)).prepare(self, 0, query); 112 const parsed_query = ParsedQuery.from(query);
113 return Statement(.{}, comptime parsed_query).prepare(self, 0);
111 } 114 }
112 115
113 /// rowsAffected returns the number of rows affected by the last statement executed. 116 /// rowsAffected returns the number of rows affected by the last statement executed.
@@ -116,28 +119,7 @@ pub const Db = struct {
116 } 119 }
117}; 120};
118 121
119/// Bytes is used to represent a byte slice with its SQLite datatype. 122pub const StatementOptions = struct {};
120///
121/// Since Zig doesn't have strings we can't tell if a []u8 must be stored as a SQLite TEXT or BLOB,
122/// this type can be used to communicate this when executing a statement.
123///
124/// If a []u8 or []const u8 is passed as bind parameter it will be treated as TEXT.
125pub const Bytes = union(enum) {
126 Blob: []const u8,
127 Text: []const u8,
128};
129
130pub const StatementOptions = struct {
131 const Self = @This();
132
133 bind_markers: usize,
134
135 fn from(comptime query: []const u8) Self {
136 return Self{
137 .bind_markers = std.mem.count(u8, query, "?"),
138 };
139 }
140};
141 123
142/// Statement is a wrapper around a SQLite statement, providing high-level functions to execute 124/// Statement is a wrapper around a SQLite statement, providing high-level functions to execute
143/// a statement and retrieve rows for SELECT queries. 125/// a statement and retrieve rows for SELECT queries.
@@ -172,19 +154,21 @@ pub const StatementOptions = struct {
172/// 154///
173/// Look at aach function for more complete documentation. 155/// Look at aach function for more complete documentation.
174/// 156///
175pub fn Statement(comptime opts: StatementOptions) type { 157pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) type {
176 return struct { 158 return struct {
177 const Self = @This(); 159 const Self = @This();
178 160
179 stmt: *c.sqlite3_stmt, 161 stmt: *c.sqlite3_stmt,
180 162
181 fn prepare(db: *Db, flags: c_uint, comptime query: []const u8) !Self { 163 fn prepare(db: *Db, flags: c_uint) !Self {
182 var stmt = blk: { 164 var stmt = blk: {
165 const real_query = query.getQuery();
166
183 var tmp: ?*c.sqlite3_stmt = undefined; 167 var tmp: ?*c.sqlite3_stmt = undefined;
184 const result = c.sqlite3_prepare_v3( 168 const result = c.sqlite3_prepare_v3(
185 db.db, 169 db.db,
186 query.ptr, 170 real_query.ptr,
187 @intCast(c_int, query.len), 171 @intCast(c_int, real_query.len),
188 flags, 172 flags,
189 &tmp, 173 &tmp,
190 null, 174 null,
@@ -212,11 +196,15 @@ pub fn Statement(comptime opts: StatementOptions) type {
212 const StructType = @TypeOf(values); 196 const StructType = @TypeOf(values);
213 const StructTypeInfo = @typeInfo(StructType).Struct; 197 const StructTypeInfo = @typeInfo(StructType).Struct;
214 198
215 if (comptime opts.bind_markers != StructTypeInfo.fields.len) { 199 if (comptime query.nb_bind_markers != StructTypeInfo.fields.len) {
216 @compileError("number of bind markers not equal to number of fields"); 200 @compileError("number of bind markers not equal to number of fields");
217 } 201 }
218 202
219 inline for (StructTypeInfo.fields) |struct_field, _i| { 203 inline for (StructTypeInfo.fields) |struct_field, _i| {
204 if (struct_field.field_type != query.bind_markers[_i].Type) {
205 @compileError("value type " ++ @typeName(struct_field.field_type) ++ " is not the bind marker type " ++ @typeName(query.bind_markers[_i].Type));
206 }
207
220 const i = @as(usize, _i); 208 const i = @as(usize, _i);
221 const field_type_info = @typeInfo(struct_field.field_type); 209 const field_type_info = @typeInfo(struct_field.field_type);
222 const field_value = @field(values, struct_field.name); 210 const field_value = @field(values, struct_field.name);
@@ -226,10 +214,8 @@ pub fn Statement(comptime opts: StatementOptions) type {
226 []const u8, []u8 => { 214 []const u8, []u8 => {
227 _ = c.sqlite3_bind_text(self.stmt, column, field_value.ptr, @intCast(c_int, field_value.len), null); 215 _ = c.sqlite3_bind_text(self.stmt, column, field_value.ptr, @intCast(c_int, field_value.len), null);
228 }, 216 },
229 Bytes => switch (field_value) { 217 Text => _ = c.sqlite3_bind_text(self.stmt, column, field_value.data.ptr, @intCast(c_int, field_value.data.len), null),
230 .Text => |v| _ = c.sqlite3_bind_text(self.stmt, column, v.ptr, @intCast(c_int, v.len), null), 218 Blob => _ = c.sqlite3_bind_blob(self.stmt, column, field_value.data.ptr, @intCast(c_int, field_value.data.len), null),
231 .Blob => |v| _ = c.sqlite3_bind_blob(self.stmt, column, v.ptr, @intCast(c_int, v.len), null),
232 },
233 else => switch (field_type_info) { 219 else => switch (field_type_info) {
234 .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(self.stmt, column, @intCast(c_longlong, field_value)), 220 .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(self.stmt, column, @intCast(c_longlong, field_value)),
235 .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(self.stmt, column, field_value), 221 .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(self.stmt, column, field_value),
@@ -490,7 +476,7 @@ test "sqlite: statement exec" {
490 }; 476 };
491 477
492 for (users) |user| { 478 for (users) |user| {
493 try db.exec("INSERT INTO user(id, name, age) VALUES(?, ?, ?)", user); 479 try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})", user);
494 480
495 const rows_inserted = db.rowsAffected(); 481 const rows_inserted = db.rowsAffected();
496 testing.expectEqual(@as(usize, 1), rows_inserted); 482 testing.expectEqual(@as(usize, 1), rows_inserted);
@@ -499,10 +485,10 @@ test "sqlite: statement exec" {
499 // Read a single user 485 // Read a single user
500 486
501 { 487 {
502 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?"); 488 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}");
503 defer stmt.deinit(); 489 defer stmt.deinit();
504 490
505 var rows = try stmt.all(User, .{ .allocator = allocator }, .{ .id = 20 }); 491 var rows = try stmt.all(User, .{ .allocator = allocator }, .{ .id = @as(usize, 20) });
506 for (rows) |row| { 492 for (rows) |row| {
507 testing.expectEqual(users[0].id, row.id); 493 testing.expectEqual(users[0].id, row.id);
508 testing.expectEqualStrings(users[0].name, row.name); 494 testing.expectEqualStrings(users[0].name, row.name);
@@ -529,7 +515,7 @@ test "sqlite: statement exec" {
529 // Test with anonymous structs 515 // Test with anonymous structs
530 516
531 { 517 {
532 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?"); 518 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}");
533 defer stmt.deinit(); 519 defer stmt.deinit();
534 520
535 var row = try stmt.one( 521 var row = try stmt.one(
@@ -539,7 +525,7 @@ test "sqlite: statement exec" {
539 age: usize, 525 age: usize,
540 }, 526 },
541 .{ .allocator = allocator }, 527 .{ .allocator = allocator },
542 .{ .id = 20 }, 528 .{ .id = @as(usize, 20) },
543 ); 529 );
544 testing.expect(row != null); 530 testing.expect(row != null);
545 531
@@ -552,12 +538,12 @@ test "sqlite: statement exec" {
552 // Test with a single integer 538 // Test with a single integer
553 539
554 { 540 {
555 const query = "SELECT age FROM user WHERE id = ?"; 541 const query = "SELECT age FROM user WHERE id = ?{usize}";
556 542
557 var stmt: Statement(StatementOptions.from(query)) = try db.prepare(query); 543 var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query);
558 defer stmt.deinit(); 544 defer stmt.deinit();
559 545
560 var age = try stmt.one(usize, .{}, .{ .id = 20 }); 546 var age = try stmt.one(usize, .{}, .{ .id = @as(usize, 20) });
561 testing.expect(age != null); 547 testing.expect(age != null);
562 548
563 testing.expectEqual(@as(usize, 33), age.?); 549 testing.expectEqual(@as(usize, 33), age.?);
@@ -566,10 +552,10 @@ test "sqlite: statement exec" {
566 // Test with a Bytes struct 552 // Test with a Bytes struct
567 553
568 { 554 {
569 try db.exec("INSERT INTO user(id, name, age) VALUES(?, ?, ?)", .{ 555 try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{blob}, ?{u32})", .{
570 .id = 200, 556 .id = @as(usize, 200),
571 .name = Bytes{ .Text = "hello" }, 557 .name = Blob{ .data = "hello" },
572 .age = 20, 558 .age = @as(u32, 20),
573 }); 559 });
574 } 560 }
575} 561}