diff options
| author | 2020-12-27 18:49:17 +0100 | |
|---|---|---|
| committer | 2020-12-30 22:46:23 +0100 | |
| commit | 72a6b23d42b9ed6f885bb801515062951e61fcf5 (patch) | |
| tree | 78984de1fe170f8f4fb60534a86510965ad1d263 /sqlite.zig | |
| parent | document OpenFlags (diff) | |
| download | zig-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.zig | 157 |
1 files changed, 109 insertions, 48 deletions
| @@ -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 | ||