summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
Diffstat (limited to 'sqlite.zig')
-rw-r--r--sqlite.zig359
1 files changed, 211 insertions, 148 deletions
diff --git a/sqlite.zig b/sqlite.zig
index d08717b..c37b972 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -120,6 +120,139 @@ pub const Db = struct {
120 } 120 }
121}; 121};
122 122
123pub fn Iterator(comptime Type: type) type {
124 return struct {
125 const Self = @This();
126
127 const TypeInfo = @typeInfo(Type);
128
129 stmt: *c.sqlite3_stmt,
130
131 pub fn next(self: *Self, options: anytype) !?Type {
132 var result = c.sqlite3_step(self.stmt);
133 if (result == c.SQLITE_DONE) {
134 return null;
135 }
136
137 if (result != c.SQLITE_ROW) {
138 logger.err("unable to iterate, result: {}", .{result});
139 return error.SQLiteStepError;
140 }
141
142 const columns = c.sqlite3_column_count(self.stmt);
143
144 return switch (TypeInfo) {
145 .Int => blk: {
146 debug.assert(columns == 1);
147 break :blk try self.readInt(options);
148 },
149 .Float => blk: {
150 debug.assert(columns == 1);
151 break :blk try self.readFloat(options);
152 },
153 .Struct => blk: {
154 std.debug.assert(columns == TypeInfo.Struct.fields.len);
155 break :blk try self.readStruct(options);
156 },
157 else => @compileError("cannot read into type " ++ @typeName(Type)),
158 };
159 }
160
161 fn readInt(self: *Self, options: anytype) !Type {
162 const n = c.sqlite3_column_int64(self.stmt, 0);
163 return @intCast(Type, n);
164 }
165
166 fn readFloat(self: *Self, options: anytype) !Type {
167 const d = c.sqlite3_column_double(self.stmt, 0);
168 return @floatCast(Type, d);
169 }
170
171 const ReadBytesMode = enum {
172 Blob,
173 Text,
174 };
175
176 fn readBytes(self: *Self, options: anytype, mode: ReadBytesMode, _i: usize, ptr: *[]const u8) !void {
177 const i = @intCast(c_int, _i);
178 switch (mode) {
179 .Blob => {
180 const data = c.sqlite3_column_blob(self.stmt, i);
181 if (data == null) ptr.* = "";
182
183 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
184
185 var tmp = try options.allocator.alloc(u8, size);
186 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]);
187
188 ptr.* = tmp;
189 },
190 .Text => {
191 const data = c.sqlite3_column_text(self.stmt, i);
192 if (data == null) ptr.* = "";
193
194 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
195
196 var tmp = try options.allocator.alloc(u8, size);
197 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]);
198
199 ptr.* = tmp;
200 },
201 }
202 }
203
204 fn readStruct(self: *Self, options: anytype) !Type {
205 var value: Type = undefined;
206
207 inline for (@typeInfo(Type).Struct.fields) |field, _i| {
208 const i = @as(usize, _i);
209 const field_type_info = @typeInfo(field.field_type);
210
211 switch (field.field_type) {
212 []const u8, []u8 => {
213 try self.readBytes(options, .Blob, i, &@field(value, field.name));
214 },
215 Blob => {
216 try self.readBytes(options, .Blob, i, &@field(value, field.name).data);
217 },
218 Text => {
219 try self.readBytes(options, .Text, i, &@field(value, field.name).data);
220 },
221 else => switch (field_type_info) {
222 .Int => {
223 const n = c.sqlite3_column_int64(self.stmt, i);
224 @field(value, field.name) = @intCast(field.field_type, n);
225 },
226 .Float => {
227 const f = c.sqlite3_column_double(self.stmt, i);
228 @field(value, field.name) = f;
229 },
230 .Void => {
231 @field(value, field.name) = {};
232 },
233 .Array => |arr| {
234 switch (arr.child) {
235 u8 => {
236 const data = c.sqlite3_column_blob(self.stmt, i);
237 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
238
239 if (size > @as(usize, arr.len)) return error.ArrayTooSmall;
240
241 mem.copy(u8, @field(value, field.name)[0..], @ptrCast([*c]const u8, data)[0..size]);
242 },
243 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)),
244 }
245 },
246 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)),
247 },
248 }
249 }
250
251 return value;
252 }
253 };
254}
255
123pub const StatementOptions = struct {}; 256pub const StatementOptions = struct {};
124 257
125/// Statement is a wrapper around a SQLite statement, providing high-level functions to execute 258/// Statement is a wrapper around a SQLite statement, providing high-level functions to execute
@@ -222,7 +355,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
222 /// } 355 /// }
223 /// 356 ///
224 /// The types are checked at comptime. 357 /// The types are checked at comptime.
225 pub fn bind(self: *Self, values: anytype) void { 358 fn bind(self: *Self, values: anytype) void {
226 const StructType = @TypeOf(values); 359 const StructType = @TypeOf(values);
227 const StructTypeInfo = @typeInfo(StructType).Struct; 360 const StructTypeInfo = @typeInfo(StructType).Struct;
228 361
@@ -285,6 +418,15 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
285 } 418 }
286 } 419 }
287 420
421 pub fn iterator(self: *Self, comptime Type: type, values: anytype) !Iterator(Type) {
422 self.bind(values);
423
424 var res: Iterator(Type) = undefined;
425 res.stmt = self.stmt;
426
427 return res;
428 }
429
288 /// one reads a single row from the result set of this statement. 430 /// one reads a single row from the result set of this statement.
289 /// 431 ///
290 /// The data in the row is used to populate a value of the type `Type`. 432 /// The data in the row is used to populate a value of the type `Type`.
@@ -312,32 +454,13 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
312 /// 454 ///
313 pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type { 455 pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type {
314 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { 456 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) {
315 @compileError("options passed to all must be a struct"); 457 @compileError("options passed to iterator must be a struct");
316 } 458 }
317 const TypeInfo = @typeInfo(Type);
318
319 self.bind(values);
320 459
321 var result = c.sqlite3_step(self.stmt); 460 var iter = try self.iterator(Type, values);
322 461
323 switch (TypeInfo) { 462 const row = (try iter.next(options)) orelse return null;
324 .Int => return switch (result) { 463 return row;
325 c.SQLITE_ROW => try self.readInt(Type, options),
326 c.SQLITE_DONE => null,
327 else => std.debug.panic("invalid result {}", .{result}),
328 },
329 .Float => return switch (result) {
330 c.SQLITE_ROW => try self.readFloat(Type, options),
331 c.SQLITE_DONE => null,
332 else => std.debug.panic("invalid result {}", .{result}),
333 },
334 .Struct => return switch (result) {
335 c.SQLITE_ROW => try self.readStruct(Type, options),
336 c.SQLITE_DONE => null,
337 else => std.debug.panic("invalid result {}", .{result}),
338 },
339 else => @compileError("cannot read into type " ++ @typeName(Type)),
340 }
341 } 464 }
342 465
343 /// all reads all rows from the result set of this statement. 466 /// all reads all rows from the result set of this statement.
@@ -370,137 +493,18 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
370 /// 493 ///
371 pub fn all(self: *Self, comptime Type: type, options: anytype, values: anytype) ![]Type { 494 pub fn all(self: *Self, comptime Type: type, options: anytype, values: anytype) ![]Type {
372 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { 495 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) {
373 @compileError("options passed to all must be a struct"); 496 @compileError("options passed to iterator must be a struct");
374 } 497 }
375 const TypeInfo = @typeInfo(Type); 498 var iter = try self.iterator(Type, values);
376
377 self.bind(values);
378 499
379 var rows = std.ArrayList(Type).init(options.allocator); 500 var rows = std.ArrayList(Type).init(options.allocator);
380 501 while (true) {
381 var result = c.sqlite3_step(self.stmt); 502 const row = (try iter.next(options)) orelse break;
382 while (result == c.SQLITE_ROW) : (result = c.sqlite3_step(self.stmt)) { 503 try rows.append(row);
383 const columns = c.sqlite3_column_count(self.stmt);
384
385 var value = switch (TypeInfo) {
386 .Int => blk: {
387 debug.assert(columns == 1);
388 break :blk try self.readInt(Type, options);
389 },
390 .Float => blk: {
391 debug.assert(columns == 1);
392 break :blk try self.readFloat(Type, options);
393 },
394 .Struct => blk: {
395 std.debug.assert(columns == @typeInfo(Type).Struct.fields.len);
396 break :blk try self.readStruct(Type, options);
397 },
398 else => @compileError("cannot read into type " ++ @typeName(Type)),
399 };
400
401 try rows.append(value);
402 }
403
404 if (result != c.SQLITE_DONE) {
405 logger.err("unable to iterate, result: {}", .{result});
406 return error.SQLiteStepError;
407 } 504 }
408 505
409 return rows.span(); 506 return rows.span();
410 } 507 }
411
412 fn readInt(self: *Self, comptime Type: type, options: anytype) !Type {
413 const n = c.sqlite3_column_int64(self.stmt, 0);
414 return @intCast(Type, n);
415 }
416
417 fn readFloat(self: *Self, comptime Type: type, options: anytype) !Type {
418 const d = c.sqlite3_column_double(self.stmt, 0);
419 return @floatCast(Type, d);
420 }
421
422 const ReadBytesMode = enum {
423 Blob,
424 Text,
425 };
426
427 fn readBytes(self: *Self, allocator: *mem.Allocator, mode: ReadBytesMode, _i: usize, ptr: *[]const u8) !void {
428 const i = @intCast(c_int, _i);
429 switch (mode) {
430 .Blob => {
431 const data = c.sqlite3_column_blob(self.stmt, i);
432 if (data == null) ptr.* = "";
433
434 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
435
436 var tmp = try allocator.alloc(u8, size);
437 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]);
438
439 ptr.* = tmp;
440 },
441 .Text => {
442 const data = c.sqlite3_column_text(self.stmt, i);
443 if (data == null) ptr.* = "";
444
445 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
446
447 var tmp = try allocator.alloc(u8, size);
448 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]);
449
450 ptr.* = tmp;
451 },
452 }
453 }
454
455 fn readStruct(self: *Self, comptime Type: type, options: anytype) !Type {
456 var value: Type = undefined;
457
458 inline for (@typeInfo(Type).Struct.fields) |field, _i| {
459 const i = @as(usize, _i);
460 const field_type_info = @typeInfo(field.field_type);
461
462 switch (field.field_type) {
463 []const u8, []u8 => {
464 try self.readBytes(options.allocator, .Blob, i, &@field(value, field.name));
465 },
466 Blob => {
467 try self.readBytes(options.allocator, .Blob, i, &@field(value, field.name).data);
468 },
469 Text => {
470 try self.readBytes(options.allocator, .Text, i, &@field(value, field.name).data);
471 },
472 else => switch (field_type_info) {
473 .Int => {
474 const n = c.sqlite3_column_int64(self.stmt, i);
475 @field(value, field.name) = @intCast(field.field_type, n);
476 },
477 .Float => {
478 const f = c.sqlite3_column_double(self.stmt, i);
479 @field(value, field.name) = f;
480 },
481 .Void => {
482 @field(value, field.name) = {};
483 },
484 .Array => |arr| {
485 switch (arr.child) {
486 u8 => {
487 const data = c.sqlite3_column_blob(self.stmt, i);
488 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
489
490 if (size > @as(usize, arr.len)) return error.ArrayTooSmall;
491
492 mem.copy(u8, @field(value, field.name)[0..], @ptrCast([*c]const u8, data)[0..size]);
493 },
494 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)),
495 }
496 },
497 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)),
498 },
499 }
500 }
501
502 return value;
503 }
504 }; 508 };
505} 509}
506 510
@@ -715,6 +719,65 @@ test "sqlite: statement reset" {
715 } 719 }
716} 720}
717 721
722test "sqlite: statement iterator" {
723 var arena = std.heap.ArenaAllocator.init(testing.allocator);
724 defer arena.deinit();
725 var allocator = &arena.allocator;
726
727 var db: Db = undefined;
728 try db.init(testing.allocator, .{ .mode = dbMode() });
729
730 // Create the tables
731 inline for (AllDDL) |ddl| {
732 try db.exec(ddl, .{});
733 }
734
735 // Add data
736 var stmt = try db.prepare("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})");
737 defer stmt.deinit();
738
739 var expected_rows = std.ArrayList(TestUser).init(allocator);
740 var i: usize = 0;
741 while (i < 20) : (i += 1) {
742 const name = try std.fmt.allocPrint(allocator, "Vincent {}", .{i});
743 const user = TestUser{ .id = i, .name = name, .age = i + 200 };
744
745 try expected_rows.append(user);
746
747 stmt.reset();
748 try stmt.exec(user);
749
750 const rows_inserted = db.rowsAffected();
751 testing.expectEqual(@as(usize, 1), rows_inserted);
752 }
753
754 // Get the data with an iterator
755 var stmt2 = try db.prepare("SELECT name, age FROM user");
756 defer stmt2.deinit();
757
758 const Type = struct {
759 name: Text,
760 age: usize,
761 };
762
763 var iter = try stmt2.iterator(Type, .{});
764
765 var rows = std.ArrayList(Type).init(allocator);
766 while (true) {
767 const row = (try iter.next(.{ .allocator = allocator })) orelse break;
768 try rows.append(row);
769 }
770
771 // Check the data
772 testing.expectEqual(expected_rows.span().len, rows.span().len);
773
774 for (rows.span()) |row, j| {
775 const exp_row = expected_rows.span()[j];
776 testing.expectEqualStrings(exp_row.name, row.name.data);
777 testing.expectEqual(exp_row.age, row.age);
778 }
779}
780
718fn dbMode() Db.Mode { 781fn dbMode() Db.Mode {
719 return if (build_options.is_ci) blk: { 782 return if (build_options.is_ci) blk: {
720 break :blk .{ .Memory = {} }; 783 break :blk .{ .Memory = {} };