summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2020-12-27 18:49:17 +0100
committerGravatar Vincent Rischmann2020-12-30 22:46:23 +0100
commit72a6b23d42b9ed6f885bb801515062951e61fcf5 (patch)
tree78984de1fe170f8f4fb60534a86510965ad1d263 /sqlite.zig
parentdocument OpenFlags (diff)
downloadzig-sqlite-72a6b23d42b9ed6f885bb801515062951e61fcf5.tar.gz
zig-sqlite-72a6b23d42b9ed6f885bb801515062951e61fcf5.tar.xz
zig-sqlite-72a6b23d42b9ed6f885bb801515062951e61fcf5.zip
introduce *Alloc methods
Stmt.oneAlloc, Db.oneAlloc and Iterator.nextAlloc do the same thing as Stmt.one, Db.one, Iterator.next respectively but they can allocate memory. This is useful when reading TEXT or BLOB columns because if you can't allocate memory the only way to read these types is with an array which means you must have an idea of the maximum size of the column.
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