summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
Diffstat (limited to 'sqlite.zig')
-rw-r--r--sqlite.zig157
1 files changed, 109 insertions, 48 deletions
diff --git a/sqlite.zig b/sqlite.zig
index e72c2d5..4cd91fd 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -203,6 +203,13 @@ pub const Db = struct {
203 return try stmt.one(Type, options, values); 203 return try stmt.one(Type, options, values);
204 } 204 }
205 205
206 /// oneAlloc is like `one` but can allocate memory.
207 pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, comptime query: []const u8, options: anytype, values: anytype) !?Type {
208 var stmt = try self.prepare(query);
209 defer stmt.deinit();
210 return try stmt.oneAlloc(Type, allocator, options, values);
211 }
212
206 /// prepare prepares a statement for the `query` provided. 213 /// prepare prepares a statement for the `query` provided.
207 /// 214 ///
208 /// The query is analysed at comptime to search for bind markers. 215 /// The query is analysed at comptime to search for bind markers.
@@ -259,16 +266,55 @@ pub fn Iterator(comptime Type: type) type {
259 stmt: *c.sqlite3_stmt, 266 stmt: *c.sqlite3_stmt,
260 267
261 // next scans the next row using the prepared statement. 268 // next scans the next row using the prepared statement.
262 //
263 // If it returns null iterating is done. 269 // If it returns null iterating is done.
270 //
271 // This cannot allocate memory. If you need to read TEXT or BLOB columns you need to use arrays or alternatively call nextAlloc.
264 pub fn next(self: *Self, options: anytype) !?Type { 272 pub fn next(self: *Self, options: anytype) !?Type {
265 var result = c.sqlite3_step(self.stmt); 273 var result = c.sqlite3_step(self.stmt);
266 if (result == c.SQLITE_DONE) { 274 if (result == c.SQLITE_DONE) {
267 return null; 275 return null;
268 } 276 }
277 if (result != c.SQLITE_ROW) {
278 return error.SQLiteStepError;
279 }
269 280
281 const columns = c.sqlite3_column_count(self.stmt);
282
283 switch (TypeInfo) {
284 .Int => {
285 debug.assert(columns == 1);
286 return try self.readInt(Type, 0);
287 },
288 .Float => {
289 debug.assert(columns == 1);
290 return try self.readFloat(Type, 0);
291 },
292 .Bool => {
293 debug.assert(columns == 1);
294 return try self.readBool(0);
295 },
296 .Void => {
297 debug.assert(columns == 1);
298 },
299 .Array => {
300 debug.assert(columns == 1);
301 return try self.readArray(Type, 0);
302 },
303 .Struct => {
304 std.debug.assert(columns == TypeInfo.Struct.fields.len);
305 return try self.readStruct(.{});
306 },
307 else => @compileError("cannot read into type " ++ @typeName(Type) ++ " ; if dynamic memory allocation is required use nextAlloc"),
308 }
309 }
310
311 // nextAlloc is like `next` but can allocate memory.
312 pub fn nextAlloc(self: *Self, allocator: *mem.Allocator, options: anytype) !?Type {
313 var result = c.sqlite3_step(self.stmt);
314 if (result == c.SQLITE_DONE) {
315 return null;
316 }
270 if (result != c.SQLITE_ROW) { 317 if (result != c.SQLITE_ROW) {
271 logger.err("unable to iterate, result: {}", .{result});
272 return error.SQLiteStepError; 318 return error.SQLiteStepError;
273 } 319 }
274 320
@@ -277,15 +323,15 @@ pub fn Iterator(comptime Type: type) type {
277 switch (Type) { 323 switch (Type) {
278 []const u8, []u8 => { 324 []const u8, []u8 => {
279 debug.assert(columns == 1); 325 debug.assert(columns == 1);
280 return try self.readBytes(Type, options.allocator, 0, .Text); 326 return try self.readBytes(Type, allocator, 0, .Text);
281 }, 327 },
282 Blob => { 328 Blob => {
283 debug.assert(columns == 1); 329 debug.assert(columns == 1);
284 return try self.readBytes(Blob, options.allocator, 0, .Blob); 330 return try self.readBytes(Blob, allocator, 0, .Blob);
285 }, 331 },
286 Text => { 332 Text => {
287 debug.assert(columns == 1); 333 debug.assert(columns == 1);
288 return try self.readBytes(Text, options.allocator, 0, .Text); 334 return try self.readBytes(Text, allocator, 0, .Text);
289 }, 335 },
290 else => {}, 336 else => {},
291 } 337 }
@@ -312,11 +358,13 @@ pub fn Iterator(comptime Type: type) type {
312 }, 358 },
313 .Pointer => { 359 .Pointer => {
314 debug.assert(columns == 1); 360 debug.assert(columns == 1);
315 return try self.readPointer(Type, 0, options); 361 return try self.readPointer(Type, allocator, 0);
316 }, 362 },
317 .Struct => { 363 .Struct => {
318 std.debug.assert(columns == TypeInfo.Struct.fields.len); 364 std.debug.assert(columns == TypeInfo.Struct.fields.len);
319 return try self.readStruct(options); 365 return try self.readStruct(.{
366 .allocator = allocator,
367 });
320 }, 368 },
321 else => @compileError("cannot read into type " ++ @typeName(Type)), 369 else => @compileError("cannot read into type " ++ @typeName(Type)),
322 } 370 }
@@ -458,7 +506,7 @@ pub fn Iterator(comptime Type: type) type {
458 } 506 }
459 } 507 }
460 508
461 fn readPointer(self: *Self, comptime PointerType: type, i: usize, options: anytype) !PointerType { 509 fn readPointer(self: *Self, comptime PointerType: type, allocator: *mem.Allocator, i: usize) !PointerType {
462 const type_info = @typeInfo(PointerType); 510 const type_info = @typeInfo(PointerType);
463 511
464 var ret: PointerType = undefined; 512 var ret: PointerType = undefined;
@@ -467,7 +515,7 @@ pub fn Iterator(comptime Type: type) type {
467 switch (ptr.size) { 515 switch (ptr.size) {
468 .One => unreachable, 516 .One => unreachable,
469 .Slice => switch (ptr.child) { 517 .Slice => switch (ptr.child) {
470 u8 => ret = try self.readBytes(PointerType, options.allocator, i, .Text), 518 u8 => ret = try self.readBytes(PointerType, allocator, i, .Text),
471 else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)), 519 else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)),
472 }, 520 },
473 else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)), 521 else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)),
@@ -516,7 +564,7 @@ pub fn Iterator(comptime Type: type) type {
516 .Bool => try self.readBool(i), 564 .Bool => try self.readBool(i),
517 .Void => {}, 565 .Void => {},
518 .Array => try self.readArray(field.field_type, i), 566 .Array => try self.readArray(field.field_type, i),
519 .Pointer => try self.readPointer(field.field_type, i, options), 567 .Pointer => try self.readPointer(field.field_type, options.allocator, i),
520 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)), 568 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)),
521 }, 569 },
522 }; 570 };
@@ -734,10 +782,10 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
734 /// const row = try stmt.one( 782 /// const row = try stmt.one(
735 /// struct { 783 /// struct {
736 /// id: usize, 784 /// id: usize,
737 /// name: []const u8, 785 /// name: [400]u8,
738 /// age: usize, 786 /// age: usize,
739 /// }, 787 /// },
740 /// .{ .allocator = allocator }, 788 /// .{},
741 /// .{ .foo = "bar", .age = 500 }, 789 /// .{ .foo = "bar", .age = 500 },
742 /// ); 790 /// );
743 /// 791 ///
@@ -747,6 +795,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
747 /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers 795 /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers
748 /// in the input query string. 796 /// in the input query string.
749 /// 797 ///
798 /// This cannot allocate memory. If you need to read TEXT or BLOB columns you need to use arrays or alternatively call `oneAlloc`.
750 pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type { 799 pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type {
751 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { 800 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) {
752 @compileError("options passed to iterator must be a struct"); 801 @compileError("options passed to iterator must be a struct");
@@ -758,6 +807,18 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
758 return row; 807 return row;
759 } 808 }
760 809
810 /// oneAlloc is like `one` but can allocate memory.
811 pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: anytype, values: anytype) !?Type {
812 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) {
813 @compileError("options passed to iterator must be a struct");
814 }
815
816 var iter = try self.iterator(Type, values);
817
818 const row = (try iter.nextAlloc(allocator, options)) orelse return null;
819 return row;
820 }
821
761 /// all reads all rows from the result set of this statement. 822 /// all reads all rows from the result set of this statement.
762 /// 823 ///
763 /// The data in each row is used to populate a value of the type `Type`. 824 /// The data in each row is used to populate a value of the type `Type`.
@@ -773,7 +834,8 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
773 /// name: []const u8, 834 /// name: []const u8,
774 /// age: usize, 835 /// age: usize,
775 /// }, 836 /// },
776 /// .{ .allocator = allocator }, 837 /// allocator,
838 /// .{},
777 /// .{ .foo = "bar", .age = 500 }, 839 /// .{ .foo = "bar", .age = 500 },
778 /// ); 840 /// );
779 /// 841 ///
@@ -783,18 +845,16 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
783 /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers 845 /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers
784 /// in the input query string. 846 /// in the input query string.
785 /// 847 ///
786 /// Note that this allocates all rows into a single slice: if you read a lot of data this can 848 /// Note that this allocates all rows into a single slice: if you read a lot of data this can use a lot of memory.
787 /// use a lot of memory. 849 pub fn all(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: anytype, values: anytype) ![]Type {
788 ///
789 pub fn all(self: *Self, comptime Type: type, options: anytype, values: anytype) ![]Type {
790 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { 850 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) {
791 @compileError("options passed to iterator must be a struct"); 851 @compileError("options passed to iterator must be a struct");
792 } 852 }
793 var iter = try self.iterator(Type, values); 853 var iter = try self.iterator(Type, values);
794 854
795 var rows = std.ArrayList(Type).init(options.allocator); 855 var rows = std.ArrayList(Type).init(allocator);
796 while (true) { 856 while (true) {
797 const row = (try iter.next(options)) orelse break; 857 const row = (try iter.nextAlloc(allocator, options)) orelse break;
798 try rows.append(row); 858 try rows.append(row);
799 } 859 }
800 860
@@ -866,22 +926,25 @@ test "sqlite: db pragma" {
866 926
867 if (build_options.in_memory) { 927 if (build_options.in_memory) {
868 const journal_mode = try db.pragma( 928 const journal_mode = try db.pragma(
869 []const u8, 929 [128:0]u8,
870 "journal_mode", 930 "journal_mode",
871 .{ .allocator = &arena.allocator }, 931 .{},
872 .{"wal"}, 932 .{"wal"},
873 ); 933 );
874 testing.expect(journal_mode != null); 934 testing.expect(journal_mode != null);
875 testing.expectEqualStrings("memory", journal_mode.?); 935 testing.expectEqualStrings("memory", journal_mode.?);
876 } else { 936 } else {
877 const journal_mode = try db.pragma( 937 {
878 []const u8, 938 const journal_mode = try db.pragma([128:0]u8, .{}, "journal_mode", arg);
879 "journal_mode", 939 testing.expect(journal_mode != null);
880 .{ .allocator = &arena.allocator }, 940 testing.expectEqualStrings("wal", mem.spanZ(&journal_mode.?));
881 .{"wal"}, 941 }
882 ); 942
883 testing.expect(journal_mode != null); 943 {
884 testing.expectEqualStrings("wal", journal_mode.?); 944 const journal_mode = try db.pragmaAlloc([]const u8, &arena.allocator, .{}, "journal_mode", arg);
945 testing.expect(journal_mode != null);
946 testing.expectEqualStrings("wal", journal_mode.?);
947 }
885 } 948 }
886} 949}
887 950
@@ -920,11 +983,9 @@ test "sqlite: read a single user into a struct" {
920 var stmt = try db.prepare("SELECT id, name, age, weight FROM user WHERE id = ?{usize}"); 983 var stmt = try db.prepare("SELECT id, name, age, weight FROM user WHERE id = ?{usize}");
921 defer stmt.deinit(); 984 defer stmt.deinit();
922 985
923 var rows = try stmt.all( 986 var rows = try stmt.all(TestUser, &arena.allocator, .{}, .{
924 TestUser, 987 .id = @as(usize, 20),
925 .{ .allocator = &arena.allocator }, 988 });
926 .{ .id = @as(usize, 20) },
927 );
928 for (rows) |row| { 989 for (rows) |row| {
929 testing.expectEqual(test_users[0].id, row.id); 990 testing.expectEqual(test_users[0].id, row.id);
930 testing.expectEqualStrings(test_users[0].name, row.name); 991 testing.expectEqualStrings(test_users[0].name, row.name);
@@ -933,14 +994,15 @@ test "sqlite: read a single user into a struct" {
933 994
934 // Read a row with db.one() 995 // Read a row with db.one()
935 { 996 {
936 var row = try db.one( 997 var row = try db.oneAlloc(
937 struct { 998 struct {
938 id: usize, 999 id: usize,
939 name: Text, 1000 name: Text,
940 age: usize, 1001 age: usize,
941 }, 1002 },
1003 &arena.allocator,
942 "SELECT id, name, age FROM user WHERE id = ?{usize}", 1004 "SELECT id, name, age FROM user WHERE id = ?{usize}",
943 .{ .allocator = &arena.allocator }, 1005 .{},
944 .{@as(usize, 20)}, 1006 .{@as(usize, 20)},
945 ); 1007 );
946 testing.expect(row != null); 1008 testing.expect(row != null);
@@ -963,11 +1025,7 @@ test "sqlite: read all users into a struct" {
963 var stmt = try db.prepare("SELECT id, name, age, weight FROM user"); 1025 var stmt = try db.prepare("SELECT id, name, age, weight FROM user");
964 defer stmt.deinit(); 1026 defer stmt.deinit();
965 1027
966 var rows = try stmt.all( 1028 var rows = try stmt.all(TestUser, &arena.allocator, .{}, .{});
967 TestUser,
968 .{ .allocator = &arena.allocator },
969 .{},
970 );
971 testing.expectEqual(@as(usize, 3), rows.len); 1029 testing.expectEqual(@as(usize, 3), rows.len);
972 for (rows) |row, i| { 1030 for (rows) |row, i| {
973 const exp = test_users[i]; 1031 const exp = test_users[i];
@@ -988,7 +1046,7 @@ test "sqlite: read in an anonymous struct" {
988 var stmt = try db.prepare("SELECT id, name, name, age, id, weight FROM user WHERE id = ?{usize}"); 1046 var stmt = try db.prepare("SELECT id, name, name, age, id, weight FROM user WHERE id = ?{usize}");
989 defer stmt.deinit(); 1047 defer stmt.deinit();
990 1048
991 var row = try stmt.one( 1049 var row = try stmt.oneAlloc(
992 struct { 1050 struct {
993 id: usize, 1051 id: usize,
994 name: []const u8, 1052 name: []const u8,
@@ -997,7 +1055,8 @@ test "sqlite: read in an anonymous struct" {
997 is_id: bool, 1055 is_id: bool,
998 weight: f64, 1056 weight: f64,
999 }, 1057 },
1000 .{ .allocator = &arena.allocator }, 1058 &arena.allocator,
1059 .{},
1001 .{ .id = @as(usize, 20) }, 1060 .{ .id = @as(usize, 20) },
1002 ); 1061 );
1003 testing.expect(row != null); 1062 testing.expect(row != null);
@@ -1022,13 +1081,14 @@ test "sqlite: read in a Text struct" {
1022 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); 1081 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}");
1023 defer stmt.deinit(); 1082 defer stmt.deinit();
1024 1083
1025 var row = try stmt.one( 1084 var row = try stmt.oneAlloc(
1026 struct { 1085 struct {
1027 id: usize, 1086 id: usize,
1028 name: Text, 1087 name: Text,
1029 age: usize, 1088 age: usize,
1030 }, 1089 },
1031 .{ .allocator = &arena.allocator }, 1090 &arena.allocator,
1091 .{},
1032 .{@as(usize, 20)}, 1092 .{@as(usize, 20)},
1033 ); 1093 );
1034 testing.expect(row != null); 1094 testing.expect(row != null);
@@ -1069,9 +1129,10 @@ test "sqlite: read a single text value" {
1069 var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); 1129 var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query);
1070 defer stmt.deinit(); 1130 defer stmt.deinit();
1071 1131
1072 const name = try stmt.one( 1132 const name = try stmt.oneAlloc(
1073 typ, 1133 typ,
1074 .{ .allocator = &arena.allocator }, 1134 &arena.allocator,
1135 .{},
1075 .{ .id = @as(usize, 20) }, 1136 .{ .id = @as(usize, 20) },
1076 ); 1137 );
1077 testing.expect(name != null); 1138 testing.expect(name != null);
@@ -1245,7 +1306,7 @@ test "sqlite: statement iterator" {
1245 1306
1246 var rows = std.ArrayList(Type).init(allocator); 1307 var rows = std.ArrayList(Type).init(allocator);
1247 while (true) { 1308 while (true) {
1248 const row = (try iter.next(.{ .allocator = allocator })) orelse break; 1309 const row = (try iter.nextAlloc(allocator, .{})) orelse break;
1249 try rows.append(row); 1310 try rows.append(row);
1250 } 1311 }
1251 1312