diff options
Diffstat (limited to 'sqlite.zig')
| -rw-r--r-- | sqlite.zig | 318 |
1 files changed, 228 insertions, 90 deletions
| @@ -159,6 +159,30 @@ pub const Db = struct { | |||
| 159 | return getLastDetailedErrorFromDb(self.db); | 159 | return getLastDetailedErrorFromDb(self.db); |
| 160 | } | 160 | } |
| 161 | 161 | ||
| 162 | fn getPragmaQuery(comptime buf: []u8, comptime name: []const u8, comptime arg: anytype) []const u8 { | ||
| 163 | return if (arg.len == 1) blk: { | ||
| 164 | break :blk try std.fmt.bufPrint(buf, "PRAGMA {} = {}", .{ name, arg[0] }); | ||
| 165 | } else blk: { | ||
| 166 | break :blk try std.fmt.bufPrint(buf, "PRAGMA {}", .{name}); | ||
| 167 | }; | ||
| 168 | } | ||
| 169 | |||
| 170 | /// pragmaAlloc is like `pragma` but can allocate memory. | ||
| 171 | /// | ||
| 172 | /// Useful when the pragma command returns text, for example: | ||
| 173 | /// | ||
| 174 | /// const journal_mode = try db.pragma([]const u8, allocator, .{}, "journal_mode", .{}); | ||
| 175 | /// | ||
| 176 | pub fn pragmaAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: anytype, comptime name: []const u8, comptime arg: anytype) !?Type { | ||
| 177 | comptime var buf: [1024]u8 = undefined; | ||
| 178 | comptime var query = getPragmaQuery(&buf, name, arg); | ||
| 179 | |||
| 180 | var stmt = try self.prepare(query); | ||
| 181 | defer stmt.deinit(); | ||
| 182 | |||
| 183 | return try stmt.oneAlloc(Type, allocator, options, .{}); | ||
| 184 | } | ||
| 185 | |||
| 162 | /// pragma is a convenience function to use the PRAGMA statement. | 186 | /// pragma is a convenience function to use the PRAGMA statement. |
| 163 | /// | 187 | /// |
| 164 | /// Here is how to set a pragma value: | 188 | /// Here is how to set a pragma value: |
| @@ -167,21 +191,14 @@ pub const Db = struct { | |||
| 167 | /// | 191 | /// |
| 168 | /// Here is how to query a pragama value: | 192 | /// Here is how to query a pragama value: |
| 169 | /// | 193 | /// |
| 170 | /// const journal_mode = try db.pragma( | 194 | /// const journal_mode = try db.pragma([128:0]const u8, .{}, "journal_mode", .{}); |
| 171 | /// []const u8, | ||
| 172 | /// "journal_mode", | ||
| 173 | /// .{ .allocator = allocator }, | ||
| 174 | /// .{}, | ||
| 175 | /// ); | ||
| 176 | /// | 195 | /// |
| 177 | /// The pragma name must be known at comptime. | 196 | /// The pragma name must be known at comptime. |
| 178 | pub fn pragma(self: *Self, comptime Type: type, comptime name: []const u8, options: anytype, arg: anytype) !?Type { | 197 | /// |
| 198 | /// This cannot allocate memory. If your pragma command returns text you must use an array or call `pragmaAlloc`. | ||
| 199 | pub fn pragma(self: *Self, comptime Type: type, options: anytype, comptime name: []const u8, arg: anytype) !?Type { | ||
| 179 | comptime var buf: [1024]u8 = undefined; | 200 | comptime var buf: [1024]u8 = undefined; |
| 180 | comptime var query = if (arg.len == 1) blk: { | 201 | comptime var query = getPragmaQuery(&buf, name, arg); |
| 181 | break :blk try std.fmt.bufPrint(&buf, "PRAGMA {} = {}", .{ name, arg[0] }); | ||
| 182 | } else blk: { | ||
| 183 | break :blk try std.fmt.bufPrint(&buf, "PRAGMA {}", .{name}); | ||
| 184 | }; | ||
| 185 | 202 | ||
| 186 | var stmt = try self.prepare(query); | 203 | var stmt = try self.prepare(query); |
| 187 | defer stmt.deinit(); | 204 | defer stmt.deinit(); |
| @@ -203,6 +220,13 @@ pub const Db = struct { | |||
| 203 | return try stmt.one(Type, options, values); | 220 | return try stmt.one(Type, options, values); |
| 204 | } | 221 | } |
| 205 | 222 | ||
| 223 | /// oneAlloc is like `one` but can allocate memory. | ||
| 224 | pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, comptime query: []const u8, options: anytype, values: anytype) !?Type { | ||
| 225 | var stmt = try self.prepare(query); | ||
| 226 | defer stmt.deinit(); | ||
| 227 | return try stmt.oneAlloc(Type, allocator, options, values); | ||
| 228 | } | ||
| 229 | |||
| 206 | /// prepare prepares a statement for the `query` provided. | 230 | /// prepare prepares a statement for the `query` provided. |
| 207 | /// | 231 | /// |
| 208 | /// The query is analysed at comptime to search for bind markers. | 232 | /// The query is analysed at comptime to search for bind markers. |
| @@ -259,16 +283,55 @@ pub fn Iterator(comptime Type: type) type { | |||
| 259 | stmt: *c.sqlite3_stmt, | 283 | stmt: *c.sqlite3_stmt, |
| 260 | 284 | ||
| 261 | // next scans the next row using the prepared statement. | 285 | // next scans the next row using the prepared statement. |
| 262 | // | ||
| 263 | // If it returns null iterating is done. | 286 | // If it returns null iterating is done. |
| 287 | // | ||
| 288 | // 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 { | 289 | pub fn next(self: *Self, options: anytype) !?Type { |
| 265 | var result = c.sqlite3_step(self.stmt); | 290 | var result = c.sqlite3_step(self.stmt); |
| 266 | if (result == c.SQLITE_DONE) { | 291 | if (result == c.SQLITE_DONE) { |
| 267 | return null; | 292 | return null; |
| 268 | } | 293 | } |
| 294 | if (result != c.SQLITE_ROW) { | ||
| 295 | return error.SQLiteStepError; | ||
| 296 | } | ||
| 297 | |||
| 298 | const columns = c.sqlite3_column_count(self.stmt); | ||
| 269 | 299 | ||
| 300 | switch (TypeInfo) { | ||
| 301 | .Int => { | ||
| 302 | debug.assert(columns == 1); | ||
| 303 | return try self.readInt(Type, 0); | ||
| 304 | }, | ||
| 305 | .Float => { | ||
| 306 | debug.assert(columns == 1); | ||
| 307 | return try self.readFloat(Type, 0); | ||
| 308 | }, | ||
| 309 | .Bool => { | ||
| 310 | debug.assert(columns == 1); | ||
| 311 | return try self.readBool(0); | ||
| 312 | }, | ||
| 313 | .Void => { | ||
| 314 | debug.assert(columns == 1); | ||
| 315 | }, | ||
| 316 | .Array => { | ||
| 317 | debug.assert(columns == 1); | ||
| 318 | return try self.readArray(Type, 0); | ||
| 319 | }, | ||
| 320 | .Struct => { | ||
| 321 | std.debug.assert(columns == TypeInfo.Struct.fields.len); | ||
| 322 | return try self.readStruct(.{}); | ||
| 323 | }, | ||
| 324 | else => @compileError("cannot read into type " ++ @typeName(Type) ++ " ; if dynamic memory allocation is required use nextAlloc"), | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | // nextAlloc is like `next` but can allocate memory. | ||
| 329 | pub fn nextAlloc(self: *Self, allocator: *mem.Allocator, options: anytype) !?Type { | ||
| 330 | var result = c.sqlite3_step(self.stmt); | ||
| 331 | if (result == c.SQLITE_DONE) { | ||
| 332 | return null; | ||
| 333 | } | ||
| 270 | if (result != c.SQLITE_ROW) { | 334 | if (result != c.SQLITE_ROW) { |
| 271 | logger.err("unable to iterate, result: {}", .{result}); | ||
| 272 | return error.SQLiteStepError; | 335 | return error.SQLiteStepError; |
| 273 | } | 336 | } |
| 274 | 337 | ||
| @@ -277,15 +340,15 @@ pub fn Iterator(comptime Type: type) type { | |||
| 277 | switch (Type) { | 340 | switch (Type) { |
| 278 | []const u8, []u8 => { | 341 | []const u8, []u8 => { |
| 279 | debug.assert(columns == 1); | 342 | debug.assert(columns == 1); |
| 280 | return try self.readBytes(Type, options.allocator, 0, .Text); | 343 | return try self.readBytes(Type, allocator, 0, .Text); |
| 281 | }, | 344 | }, |
| 282 | Blob => { | 345 | Blob => { |
| 283 | debug.assert(columns == 1); | 346 | debug.assert(columns == 1); |
| 284 | return try self.readBytes(Blob, options.allocator, 0, .Blob); | 347 | return try self.readBytes(Blob, allocator, 0, .Blob); |
| 285 | }, | 348 | }, |
| 286 | Text => { | 349 | Text => { |
| 287 | debug.assert(columns == 1); | 350 | debug.assert(columns == 1); |
| 288 | return try self.readBytes(Text, options.allocator, 0, .Text); | 351 | return try self.readBytes(Text, allocator, 0, .Text); |
| 289 | }, | 352 | }, |
| 290 | else => {}, | 353 | else => {}, |
| 291 | } | 354 | } |
| @@ -312,11 +375,13 @@ pub fn Iterator(comptime Type: type) type { | |||
| 312 | }, | 375 | }, |
| 313 | .Pointer => { | 376 | .Pointer => { |
| 314 | debug.assert(columns == 1); | 377 | debug.assert(columns == 1); |
| 315 | return try self.readPointer(Type, 0, options); | 378 | return try self.readPointer(Type, allocator, 0); |
| 316 | }, | 379 | }, |
| 317 | .Struct => { | 380 | .Struct => { |
| 318 | std.debug.assert(columns == TypeInfo.Struct.fields.len); | 381 | std.debug.assert(columns == TypeInfo.Struct.fields.len); |
| 319 | return try self.readStruct(options); | 382 | return try self.readStruct(.{ |
| 383 | .allocator = allocator, | ||
| 384 | }); | ||
| 320 | }, | 385 | }, |
| 321 | else => @compileError("cannot read into type " ++ @typeName(Type)), | 386 | else => @compileError("cannot read into type " ++ @typeName(Type)), |
| 322 | } | 387 | } |
| @@ -458,7 +523,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 458 | } | 523 | } |
| 459 | } | 524 | } |
| 460 | 525 | ||
| 461 | fn readPointer(self: *Self, comptime PointerType: type, i: usize, options: anytype) !PointerType { | 526 | fn readPointer(self: *Self, comptime PointerType: type, allocator: *mem.Allocator, i: usize) !PointerType { |
| 462 | const type_info = @typeInfo(PointerType); | 527 | const type_info = @typeInfo(PointerType); |
| 463 | 528 | ||
| 464 | var ret: PointerType = undefined; | 529 | var ret: PointerType = undefined; |
| @@ -467,7 +532,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 467 | switch (ptr.size) { | 532 | switch (ptr.size) { |
| 468 | .One => unreachable, | 533 | .One => unreachable, |
| 469 | .Slice => switch (ptr.child) { | 534 | .Slice => switch (ptr.child) { |
| 470 | u8 => ret = try self.readBytes(PointerType, options.allocator, i, .Text), | 535 | u8 => ret = try self.readBytes(PointerType, allocator, i, .Text), |
| 471 | else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)), | 536 | else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)), |
| 472 | }, | 537 | }, |
| 473 | else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)), | 538 | else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)), |
| @@ -516,7 +581,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 516 | .Bool => try self.readBool(i), | 581 | .Bool => try self.readBool(i), |
| 517 | .Void => {}, | 582 | .Void => {}, |
| 518 | .Array => try self.readArray(field.field_type, i), | 583 | .Array => try self.readArray(field.field_type, i), |
| 519 | .Pointer => try self.readPointer(field.field_type, i, options), | 584 | .Pointer => try self.readPointer(field.field_type, options.allocator, i), |
| 520 | else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)), | 585 | else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)), |
| 521 | }, | 586 | }, |
| 522 | }; | 587 | }; |
| @@ -734,10 +799,10 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 734 | /// const row = try stmt.one( | 799 | /// const row = try stmt.one( |
| 735 | /// struct { | 800 | /// struct { |
| 736 | /// id: usize, | 801 | /// id: usize, |
| 737 | /// name: []const u8, | 802 | /// name: [400]u8, |
| 738 | /// age: usize, | 803 | /// age: usize, |
| 739 | /// }, | 804 | /// }, |
| 740 | /// .{ .allocator = allocator }, | 805 | /// .{}, |
| 741 | /// .{ .foo = "bar", .age = 500 }, | 806 | /// .{ .foo = "bar", .age = 500 }, |
| 742 | /// ); | 807 | /// ); |
| 743 | /// | 808 | /// |
| @@ -747,6 +812,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 | 812 | /// 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. | 813 | /// in the input query string. |
| 749 | /// | 814 | /// |
| 815 | /// 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 { | 816 | pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type { |
| 751 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | 817 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { |
| 752 | @compileError("options passed to iterator must be a struct"); | 818 | @compileError("options passed to iterator must be a struct"); |
| @@ -758,6 +824,18 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 758 | return row; | 824 | return row; |
| 759 | } | 825 | } |
| 760 | 826 | ||
| 827 | /// oneAlloc is like `one` but can allocate memory. | ||
| 828 | pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: anytype, values: anytype) !?Type { | ||
| 829 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | ||
| 830 | @compileError("options passed to iterator must be a struct"); | ||
| 831 | } | ||
| 832 | |||
| 833 | var iter = try self.iterator(Type, values); | ||
| 834 | |||
| 835 | const row = (try iter.nextAlloc(allocator, options)) orelse return null; | ||
| 836 | return row; | ||
| 837 | } | ||
| 838 | |||
| 761 | /// all reads all rows from the result set of this statement. | 839 | /// all reads all rows from the result set of this statement. |
| 762 | /// | 840 | /// |
| 763 | /// The data in each row is used to populate a value of the type `Type`. | 841 | /// The data in each row is used to populate a value of the type `Type`. |
| @@ -773,7 +851,8 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 773 | /// name: []const u8, | 851 | /// name: []const u8, |
| 774 | /// age: usize, | 852 | /// age: usize, |
| 775 | /// }, | 853 | /// }, |
| 776 | /// .{ .allocator = allocator }, | 854 | /// allocator, |
| 855 | /// .{}, | ||
| 777 | /// .{ .foo = "bar", .age = 500 }, | 856 | /// .{ .foo = "bar", .age = 500 }, |
| 778 | /// ); | 857 | /// ); |
| 779 | /// | 858 | /// |
| @@ -783,18 +862,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 | 862 | /// 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. | 863 | /// in the input query string. |
| 785 | /// | 864 | /// |
| 786 | /// Note that this allocates all rows into a single slice: if you read a lot of data this can | 865 | /// 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. | 866 | 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))) { | 867 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { |
| 791 | @compileError("options passed to iterator must be a struct"); | 868 | @compileError("options passed to iterator must be a struct"); |
| 792 | } | 869 | } |
| 793 | var iter = try self.iterator(Type, values); | 870 | var iter = try self.iterator(Type, values); |
| 794 | 871 | ||
| 795 | var rows = std.ArrayList(Type).init(options.allocator); | 872 | var rows = std.ArrayList(Type).init(allocator); |
| 796 | while (true) { | 873 | while (true) { |
| 797 | const row = (try iter.next(options)) orelse break; | 874 | const row = (try iter.nextAlloc(allocator, options)) orelse break; |
| 798 | try rows.append(row); | 875 | try rows.append(row); |
| 799 | } | 876 | } |
| 800 | 877 | ||
| @@ -860,28 +937,36 @@ test "sqlite: db pragma" { | |||
| 860 | var db: Db = undefined; | 937 | var db: Db = undefined; |
| 861 | try db.init(initOptions()); | 938 | try db.init(initOptions()); |
| 862 | 939 | ||
| 863 | const foreign_keys = try db.pragma(usize, "foreign_keys", .{}, .{}); | 940 | const foreign_keys = try db.pragma(usize, .{}, "foreign_keys", .{}); |
| 864 | testing.expect(foreign_keys != null); | 941 | testing.expect(foreign_keys != null); |
| 865 | testing.expectEqual(@as(usize, 0), foreign_keys.?); | 942 | testing.expectEqual(@as(usize, 0), foreign_keys.?); |
| 866 | 943 | ||
| 944 | const arg = .{"wal"}; | ||
| 945 | |||
| 867 | if (build_options.in_memory) { | 946 | if (build_options.in_memory) { |
| 868 | const journal_mode = try db.pragma( | 947 | { |
| 869 | []const u8, | 948 | const journal_mode = try db.pragma([128:0]u8, .{}, "journal_mode", arg); |
| 870 | "journal_mode", | 949 | testing.expect(journal_mode != null); |
| 871 | .{ .allocator = &arena.allocator }, | 950 | testing.expectEqualStrings("memory", mem.spanZ(&journal_mode.?)); |
| 872 | .{"wal"}, | 951 | } |
| 873 | ); | 952 | |
| 874 | testing.expect(journal_mode != null); | 953 | { |
| 875 | testing.expectEqualStrings("memory", journal_mode.?); | 954 | const journal_mode = try db.pragmaAlloc([]const u8, &arena.allocator, .{}, "journal_mode", arg); |
| 955 | testing.expect(journal_mode != null); | ||
| 956 | testing.expectEqualStrings("memory", journal_mode.?); | ||
| 957 | } | ||
| 876 | } else { | 958 | } else { |
| 877 | const journal_mode = try db.pragma( | 959 | { |
| 878 | []const u8, | 960 | const journal_mode = try db.pragma([128:0]u8, .{}, "journal_mode", arg); |
| 879 | "journal_mode", | 961 | testing.expect(journal_mode != null); |
| 880 | .{ .allocator = &arena.allocator }, | 962 | testing.expectEqualStrings("wal", mem.spanZ(&journal_mode.?)); |
| 881 | .{"wal"}, | 963 | } |
| 882 | ); | 964 | |
| 883 | testing.expect(journal_mode != null); | 965 | { |
| 884 | testing.expectEqualStrings("wal", journal_mode.?); | 966 | const journal_mode = try db.pragmaAlloc([]const u8, &arena.allocator, .{}, "journal_mode", arg); |
| 967 | testing.expect(journal_mode != null); | ||
| 968 | testing.expectEqualStrings("wal", journal_mode.?); | ||
| 969 | } | ||
| 885 | } | 970 | } |
| 886 | } | 971 | } |
| 887 | 972 | ||
| @@ -920,11 +1005,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}"); | 1005 | var stmt = try db.prepare("SELECT id, name, age, weight FROM user WHERE id = ?{usize}"); |
| 921 | defer stmt.deinit(); | 1006 | defer stmt.deinit(); |
| 922 | 1007 | ||
| 923 | var rows = try stmt.all( | 1008 | var rows = try stmt.all(TestUser, &arena.allocator, .{}, .{ |
| 924 | TestUser, | 1009 | .id = @as(usize, 20), |
| 925 | .{ .allocator = &arena.allocator }, | 1010 | }); |
| 926 | .{ .id = @as(usize, 20) }, | ||
| 927 | ); | ||
| 928 | for (rows) |row| { | 1011 | for (rows) |row| { |
| 929 | testing.expectEqual(test_users[0].id, row.id); | 1012 | testing.expectEqual(test_users[0].id, row.id); |
| 930 | testing.expectEqualStrings(test_users[0].name, row.name); | 1013 | testing.expectEqualStrings(test_users[0].name, row.name); |
| @@ -936,11 +1019,32 @@ test "sqlite: read a single user into a struct" { | |||
| 936 | var row = try db.one( | 1019 | var row = try db.one( |
| 937 | struct { | 1020 | struct { |
| 938 | id: usize, | 1021 | id: usize, |
| 1022 | name: [128:0]u8, | ||
| 1023 | age: usize, | ||
| 1024 | }, | ||
| 1025 | "SELECT id, name, age FROM user WHERE id = ?{usize}", | ||
| 1026 | .{}, | ||
| 1027 | .{@as(usize, 20)}, | ||
| 1028 | ); | ||
| 1029 | testing.expect(row != null); | ||
| 1030 | |||
| 1031 | const exp = test_users[0]; | ||
| 1032 | testing.expectEqual(exp.id, row.?.id); | ||
| 1033 | testing.expectEqualStrings(exp.name, mem.spanZ(&row.?.name)); | ||
| 1034 | testing.expectEqual(exp.age, row.?.age); | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | // Read a row with db.oneAlloc() | ||
| 1038 | { | ||
| 1039 | var row = try db.oneAlloc( | ||
| 1040 | struct { | ||
| 1041 | id: usize, | ||
| 939 | name: Text, | 1042 | name: Text, |
| 940 | age: usize, | 1043 | age: usize, |
| 941 | }, | 1044 | }, |
| 1045 | &arena.allocator, | ||
| 942 | "SELECT id, name, age FROM user WHERE id = ?{usize}", | 1046 | "SELECT id, name, age FROM user WHERE id = ?{usize}", |
| 943 | .{ .allocator = &arena.allocator }, | 1047 | .{}, |
| 944 | .{@as(usize, 20)}, | 1048 | .{@as(usize, 20)}, |
| 945 | ); | 1049 | ); |
| 946 | testing.expect(row != null); | 1050 | testing.expect(row != null); |
| @@ -963,11 +1067,7 @@ test "sqlite: read all users into a struct" { | |||
| 963 | var stmt = try db.prepare("SELECT id, name, age, weight FROM user"); | 1067 | var stmt = try db.prepare("SELECT id, name, age, weight FROM user"); |
| 964 | defer stmt.deinit(); | 1068 | defer stmt.deinit(); |
| 965 | 1069 | ||
| 966 | var rows = try stmt.all( | 1070 | var rows = try stmt.all(TestUser, &arena.allocator, .{}, .{}); |
| 967 | TestUser, | ||
| 968 | .{ .allocator = &arena.allocator }, | ||
| 969 | .{}, | ||
| 970 | ); | ||
| 971 | testing.expectEqual(@as(usize, 3), rows.len); | 1071 | testing.expectEqual(@as(usize, 3), rows.len); |
| 972 | for (rows) |row, i| { | 1072 | for (rows) |row, i| { |
| 973 | const exp = test_users[i]; | 1073 | const exp = test_users[i]; |
| @@ -988,7 +1088,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}"); | 1088 | var stmt = try db.prepare("SELECT id, name, name, age, id, weight FROM user WHERE id = ?{usize}"); |
| 989 | defer stmt.deinit(); | 1089 | defer stmt.deinit(); |
| 990 | 1090 | ||
| 991 | var row = try stmt.one( | 1091 | var row = try stmt.oneAlloc( |
| 992 | struct { | 1092 | struct { |
| 993 | id: usize, | 1093 | id: usize, |
| 994 | name: []const u8, | 1094 | name: []const u8, |
| @@ -997,7 +1097,8 @@ test "sqlite: read in an anonymous struct" { | |||
| 997 | is_id: bool, | 1097 | is_id: bool, |
| 998 | weight: f64, | 1098 | weight: f64, |
| 999 | }, | 1099 | }, |
| 1000 | .{ .allocator = &arena.allocator }, | 1100 | &arena.allocator, |
| 1101 | .{}, | ||
| 1001 | .{ .id = @as(usize, 20) }, | 1102 | .{ .id = @as(usize, 20) }, |
| 1002 | ); | 1103 | ); |
| 1003 | testing.expect(row != null); | 1104 | testing.expect(row != null); |
| @@ -1022,13 +1123,14 @@ test "sqlite: read in a Text struct" { | |||
| 1022 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); | 1123 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); |
| 1023 | defer stmt.deinit(); | 1124 | defer stmt.deinit(); |
| 1024 | 1125 | ||
| 1025 | var row = try stmt.one( | 1126 | var row = try stmt.oneAlloc( |
| 1026 | struct { | 1127 | struct { |
| 1027 | id: usize, | 1128 | id: usize, |
| 1028 | name: Text, | 1129 | name: Text, |
| 1029 | age: usize, | 1130 | age: usize, |
| 1030 | }, | 1131 | }, |
| 1031 | .{ .allocator = &arena.allocator }, | 1132 | &arena.allocator, |
| 1133 | .{}, | ||
| 1032 | .{@as(usize, 20)}, | 1134 | .{@as(usize, 20)}, |
| 1033 | ); | 1135 | ); |
| 1034 | testing.expect(row != null); | 1136 | testing.expect(row != null); |
| @@ -1069,11 +1171,9 @@ test "sqlite: read a single text value" { | |||
| 1069 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 1171 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 1070 | defer stmt.deinit(); | 1172 | defer stmt.deinit(); |
| 1071 | 1173 | ||
| 1072 | const name = try stmt.one( | 1174 | const name = try stmt.oneAlloc(typ, &arena.allocator, .{}, .{ |
| 1073 | typ, | 1175 | .id = @as(usize, 20), |
| 1074 | .{ .allocator = &arena.allocator }, | 1176 | }); |
| 1075 | .{ .id = @as(usize, 20) }, | ||
| 1076 | ); | ||
| 1077 | testing.expect(name != null); | 1177 | testing.expect(name != null); |
| 1078 | switch (typ) { | 1178 | switch (typ) { |
| 1079 | Text, Blob => { | 1179 | Text, Blob => { |
| @@ -1120,7 +1220,9 @@ test "sqlite: read a single integer value" { | |||
| 1120 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 1220 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 1121 | defer stmt.deinit(); | 1221 | defer stmt.deinit(); |
| 1122 | 1222 | ||
| 1123 | var age = try stmt.one(typ, .{}, .{ .id = @as(usize, 20) }); | 1223 | var age = try stmt.one(typ, .{}, .{ |
| 1224 | .id = @as(usize, 20), | ||
| 1225 | }); | ||
| 1124 | testing.expect(age != null); | 1226 | testing.expect(age != null); |
| 1125 | 1227 | ||
| 1126 | testing.expectEqual(@as(typ, 33), age.?); | 1228 | testing.expectEqual(@as(typ, 33), age.?); |
| @@ -1137,7 +1239,9 @@ test "sqlite: read a single value into void" { | |||
| 1137 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 1239 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 1138 | defer stmt.deinit(); | 1240 | defer stmt.deinit(); |
| 1139 | 1241 | ||
| 1140 | _ = try stmt.one(void, .{}, .{ .id = @as(usize, 20) }); | 1242 | _ = try stmt.one(void, .{}, .{ |
| 1243 | .id = @as(usize, 20), | ||
| 1244 | }); | ||
| 1141 | } | 1245 | } |
| 1142 | 1246 | ||
| 1143 | test "sqlite: read a single value into bool" { | 1247 | test "sqlite: read a single value into bool" { |
| @@ -1150,7 +1254,9 @@ test "sqlite: read a single value into bool" { | |||
| 1150 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 1254 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 1151 | defer stmt.deinit(); | 1255 | defer stmt.deinit(); |
| 1152 | 1256 | ||
| 1153 | const b = try stmt.one(bool, .{}, .{ .id = @as(usize, 20) }); | 1257 | const b = try stmt.one(bool, .{}, .{ |
| 1258 | .id = @as(usize, 20), | ||
| 1259 | }); | ||
| 1154 | testing.expect(b != null); | 1260 | testing.expect(b != null); |
| 1155 | testing.expect(b.?); | 1261 | testing.expect(b.?); |
| 1156 | } | 1262 | } |
| @@ -1171,7 +1277,9 @@ test "sqlite: insert bool and bind bool" { | |||
| 1171 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); | 1277 | var stmt: Statement(.{}, ParsedQuery.from(query)) = try db.prepare(query); |
| 1172 | defer stmt.deinit(); | 1278 | defer stmt.deinit(); |
| 1173 | 1279 | ||
| 1174 | const b = try stmt.one(bool, .{}, .{ .is_published = true }); | 1280 | const b = try stmt.one(bool, .{}, .{ |
| 1281 | .is_published = true, | ||
| 1282 | }); | ||
| 1175 | testing.expect(b != null); | 1283 | testing.expect(b != null); |
| 1176 | testing.expect(b.?); | 1284 | testing.expect(b.?); |
| 1177 | } | 1285 | } |
| @@ -1232,30 +1340,60 @@ test "sqlite: statement iterator" { | |||
| 1232 | testing.expectEqual(@as(usize, 1), rows_inserted); | 1340 | testing.expectEqual(@as(usize, 1), rows_inserted); |
| 1233 | } | 1341 | } |
| 1234 | 1342 | ||
| 1235 | // Get the data with an iterator | 1343 | // Get data with a non-allocating iterator. |
| 1236 | var stmt2 = try db.prepare("SELECT name, age FROM user"); | 1344 | { |
| 1237 | defer stmt2.deinit(); | 1345 | var stmt2 = try db.prepare("SELECT name, age FROM user"); |
| 1346 | defer stmt2.deinit(); | ||
| 1238 | 1347 | ||
| 1239 | const Type = struct { | 1348 | const RowType = struct { |
| 1240 | name: Text, | 1349 | name: [128:0]u8, |
| 1241 | age: usize, | 1350 | age: usize, |
| 1242 | }; | 1351 | }; |
| 1352 | |||
| 1353 | var iter = try stmt2.iterator(RowType, .{}); | ||
| 1354 | |||
| 1355 | var rows = std.ArrayList(RowType).init(allocator); | ||
| 1356 | while (true) { | ||
| 1357 | const row = (try iter.next(.{})) orelse break; | ||
| 1358 | try rows.append(row); | ||
| 1359 | } | ||
| 1243 | 1360 | ||
| 1244 | var iter = try stmt2.iterator(Type, .{}); | 1361 | // Check the data |
| 1362 | testing.expectEqual(expected_rows.items.len, rows.items.len); | ||
| 1245 | 1363 | ||
| 1246 | var rows = std.ArrayList(Type).init(allocator); | 1364 | for (rows.items) |row, j| { |
| 1247 | while (true) { | 1365 | const exp_row = expected_rows.items[j]; |
| 1248 | const row = (try iter.next(.{ .allocator = allocator })) orelse break; | 1366 | testing.expectEqualStrings(exp_row.name, mem.spanZ(&row.name)); |
| 1249 | try rows.append(row); | 1367 | testing.expectEqual(exp_row.age, row.age); |
| 1368 | } | ||
| 1250 | } | 1369 | } |
| 1251 | 1370 | ||
| 1252 | // Check the data | 1371 | // Get data with an iterator |
| 1253 | testing.expectEqual(expected_rows.items.len, rows.items.len); | 1372 | { |
| 1373 | var stmt2 = try db.prepare("SELECT name, age FROM user"); | ||
| 1374 | defer stmt2.deinit(); | ||
| 1254 | 1375 | ||
| 1255 | for (rows.items) |row, j| { | 1376 | const RowType = struct { |
| 1256 | const exp_row = expected_rows.items[j]; | 1377 | name: Text, |
| 1257 | testing.expectEqualStrings(exp_row.name, row.name.data); | 1378 | age: usize, |
| 1258 | testing.expectEqual(exp_row.age, row.age); | 1379 | }; |
| 1380 | |||
| 1381 | var iter = try stmt2.iterator(RowType, .{}); | ||
| 1382 | |||
| 1383 | var rows = std.ArrayList(RowType).init(allocator); | ||
| 1384 | while (true) { | ||
| 1385 | const row = (try iter.nextAlloc(allocator, .{})) orelse break; | ||
| 1386 | try rows.append(row); | ||
| 1387 | } | ||
| 1388 | |||
| 1389 | // Check the data | ||
| 1390 | testing.expectEqual(expected_rows.items.len, rows.items.len); | ||
| 1391 | |||
| 1392 | for (rows.items) |row, j| { | ||
| 1393 | const exp_row = expected_rows.items[j]; | ||
| 1394 | testing.expectEqualStrings(exp_row.name, row.name.data); | ||
| 1395 | testing.expectEqual(exp_row.age, row.age); | ||
| 1396 | } | ||
| 1259 | } | 1397 | } |
| 1260 | } | 1398 | } |
| 1261 | 1399 | ||