diff options
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 | ||