Native PostgreSQL driver / client for Zig
0

Configure Feed

Select the types of activity you want to include in your feed.

Add comptime (via generic) FailMode

A Row/QueryRow/Iterator/Record can now either be "safe" or "unsafe". When safe,
fetching operations (e.g. row.get() will return an error on type failure, e.g.
requesting a bool when the underlying column is an int).

By default, operations return "safe" variants.

In unsafe-mode, these methods log + unreachable. Unsafe types can be fetched
via "Unsafe" variatns, e.g pool.row() -> pool.rowUnsafe().

This is a huge breaking change. Before this, everything was unsafe by default.
Migration means either
1 - switching to unsafe methods, row -> rowUnsafe
2 - handling the fail-case on any get row.get(bool, 0) -> try row.get(bool, 0)

Karl Seguin (Feb 15, 2026, 2:53 PM +0800) 787998d8 a7f26bce

+529 -322
+56 -35
src/conn.zig
··· 12 12 const Stream = lib.Stream; 13 13 const Timeout = lib.Timeout; 14 14 const QueryRow = lib.QueryRow; 15 + const QueryRowUnsafe = lib.QueryRowUnsafe; 15 16 const has_openssl = lib.has_openssl; 16 17 17 18 const os = std.os; ··· 309 310 } 310 311 311 312 pub fn row(self: *Conn, sql: []const u8, values: anytype) !?QueryRow { 312 - return self.rowOpts(sql, values, .{}); 313 + return self._row(.safe, sql, values, .{}); 314 + } 315 + 316 + pub fn rowUnsafe(self: *Conn, sql: []const u8, values: anytype) !?QueryRowUnsafe { 317 + return self._row(.unsafe, sql, values, .{}); 313 318 } 314 319 315 320 pub fn rowOpts(self: *Conn, sql: []const u8, values: anytype, opts: QueryOpts) !?QueryRow { 321 + return self._row(.safe, sql, values, opts); 322 + } 323 + 324 + pub fn rowUnsafeOpts(self: *Conn, sql: []const u8, values: anytype, opts: QueryOpts) !?QueryRowUnsafe { 325 + return self._row(.unsafe, sql, values, opts); 326 + } 327 + 328 + fn _row(self: *Conn, comptime fail_mode: lib.FailMode, sql: []const u8, values: anytype, opts: QueryOpts) !(if (fail_mode == .safe) ?QueryRow else ?QueryRowUnsafe) { 316 329 var result = try self.queryOpts(sql, values, opts); 317 330 errdefer result.deinit(); 318 331 319 - const r = try result.next() orelse { 320 - result.deinit(); 321 - return null; 322 - }; 332 + if (comptime fail_mode == .safe) { 333 + return .{ 334 + .result = result, 335 + .row = try result.next() orelse { 336 + result.deinit(); 337 + return null; 338 + }, 339 + }; 340 + } 323 341 324 342 return .{ 325 - .row = r, 326 343 .result = result, 344 + .row = try result.nextUnsafe() orelse { 345 + result.deinit(); 346 + return null; 347 + }, 327 348 }; 328 349 } 329 350 ··· 647 668 defer rows.deinit(); 648 669 649 670 try t.expectError(error.ConnectionBusy, c.row("select 2", .{})); 650 - try t.expectEqual(1, (try rows.next()).?.get(i32, 0)); 671 + try t.expectEqual(1, (try rows.nextUnsafe()).?.get(i32, 0)); 651 672 } 652 673 653 674 test "PG: type support" { ··· 791 812 // used for our arrays 792 813 const aa = t.arena.allocator(); 793 814 794 - const row = (try result.next()) orelse unreachable; 815 + const row = (try result.nextUnsafe()) orelse unreachable; 795 816 try t.expectEqual(1, row.get(i32, 0)); 796 817 797 818 { ··· 1054 1075 // used for our arrays 1055 1076 const aa = t.arena.allocator(); 1056 1077 1057 - const row = (try result.next()) orelse unreachable; 1078 + const row = (try result.nextUnsafe()) orelse unreachable; 1058 1079 1059 1080 { 1060 1081 //uuid, uuid[] ··· 1211 1232 , .{3}); 1212 1233 defer result.deinit(); 1213 1234 1214 - const row = (try result.next()) orelse unreachable; 1235 + const row = (try result.nextUnsafe()) orelse unreachable; 1215 1236 try t.expectEqual(null, row.get(?i16, 1)); 1216 1237 try t.expectEqual(true, row.iterator(i16, 2).is_null); 1217 1238 ··· 1303 1324 var result = try c.query("select col_json, col_jsonb from all_types where id = $1", .{4}); 1304 1325 defer result.deinit(); 1305 1326 1306 - const row = (try result.next()) orelse unreachable; 1327 + const row = (try result.nextUnsafe()) orelse unreachable; 1307 1328 try t.expectString("{\"id\":1,\"name\":\"Leto\"}", row.get([]u8, 0)); 1308 1329 try t.expectString("{\"id\": 2, \"name\": \"Ghanima\"}", row.get(?[]const u8, 1).?); 1309 1330 } ··· 1332 1353 const r1 = try c.row("select 1 where $1", .{false}); 1333 1354 try t.expectEqual(null, r1); 1334 1355 1335 - var r2 = (try c.row("select 2 where $1", .{true})) orelse unreachable; 1356 + var r2 = (try c.rowUnsafe("select 2 where $1", .{true})) orelse unreachable; 1336 1357 try t.expectEqual(2, r2.get(i32, 0)); 1337 1358 try r2.deinit(); 1338 1359 1339 1360 // make sure the conn is still valid after a successful row 1340 - var r3 = (try c.row("select $1::int where $2", .{ 3, true })) orelse unreachable; 1361 + var r3 = (try c.rowUnsafe("select $1::int where $2", .{ 3, true })) orelse unreachable; 1341 1362 try t.expectEqual(3, r3.get(i32, 0)); 1342 1363 try r3.deinit(); 1343 1364 1344 1365 // make sure the conn is still valid after MoreThanOneRow error 1345 - var r4 = (try c.row("select $1::text where $2", .{ "hi", true })) orelse unreachable; 1366 + var r4 = (try c.rowUnsafe("select $1::text where $2", .{ "hi", true })) orelse unreachable; 1346 1367 try t.expectString("hi", r4.get([]u8, 0)); 1347 1368 try r4.deinit(); 1348 1369 } ··· 1356 1377 _ = try c.exec("insert into simple_table values ($1)", .{"begin_commit"}); 1357 1378 try c.commit(); 1358 1379 1359 - var row = (try c.row("select value from simple_table", .{})).?; 1380 + var row = (try c.rowUnsafe("select value from simple_table", .{})).?; 1360 1381 defer row.deinit() catch {}; 1361 1382 1362 1383 try t.expectString("begin_commit", row.get([]u8, 0)); ··· 1384 1405 \\ values (5, $1, $2, $3, $4) 1385 1406 , .{ DummyEnum.val1, &[_]DummyEnum{ DummyEnum.val1, DummyEnum.val2 }, DummyEnum.val2, [_]DummyEnum{ DummyEnum.val2, DummyEnum.val1 } }); 1386 1407 1387 - var row = (try c.row( 1408 + var row = (try c.rowUnsafe( 1388 1409 \\ select col_enum, col_text, col_enum_arr, col_text_arr 1389 1410 \\ from all_types 1390 1411 \\ where id = 5 ··· 1421 1442 1422 1443 { 1423 1444 // read 1424 - var row = (try c.row( 1445 + var row = (try c.rowUnsafe( 1425 1446 \\ select 'nan'::numeric, '+Inf'::numeric, '-Inf'::numeric, 1426 1447 \\ 0::numeric, 0.0::numeric, -0.00009::numeric, -999999.888880::numeric, 1427 1448 \\ 0.000008, 999999.888807::numeric, 123456.78901234::numeric(14, 8) ··· 1442 1463 1443 1464 { 1444 1465 // write + write 1445 - var row = (try c.row( 1466 + var row = (try c.rowUnsafe( 1446 1467 \\ select 1447 1468 \\ $1::numeric, $2::numeric, $3::numeric, 1448 1469 \\ $4::numeric, $5::numeric, $6::numeric, ··· 1511 1532 defer c.deinit(); 1512 1533 1513 1534 // read 1514 - var row = (try c.row( 1535 + var row = (try c.rowUnsafe( 1515 1536 \\ select $1::char[], $2::char[], $3::char[], $4::char[] 1516 1537 , .{ &[_]u8{','}, &[_]u8{ ',', '"' }, &[_]u8{ '\\', 'a', ' ' }, &[_]u8{ 'z', '@' } })).?; 1517 1538 defer row.deinit() catch {}; ··· 1544 1565 var result = try c.query("select id, col_text from all_types where id = $1", .{6}); 1545 1566 defer result.deinit(); 1546 1567 1547 - const row = (try result.next()) orelse unreachable; 1568 + const row = (try result.nextUnsafe()) orelse unreachable; 1548 1569 try t.expectEqual(6, row.get(i32, 0)); 1549 1570 try t.expectString("hello", row.get([]u8, 1)); 1550 1571 } ··· 1568 1589 var result = try c.query("select id, col_int8_arr from all_types where id = $1", .{7}); 1569 1590 defer result.deinit(); 1570 1591 1571 - const row = (try result.next()) orelse unreachable; 1592 + const row = (try result.nextUnsafe()) orelse unreachable; 1572 1593 try t.expectEqual(7, row.get(i32, 0)); 1573 1594 { 1574 1595 const arr = try row.iterator(?i64, 1).alloc(t.arena.allocator()); ··· 1598 1619 var result = try c.query("select id, col_float8_arr from all_types where id = $1", .{8}); 1599 1620 defer result.deinit(); 1600 1621 1601 - const row = (try result.next()) orelse unreachable; 1622 + const row = (try result.nextUnsafe()) orelse unreachable; 1602 1623 try t.expectEqual(8, row.get(i32, 0)); 1603 1624 { 1604 1625 const arr = try row.iterator(?f64, 1).alloc(t.arena.allocator()); ··· 1629 1650 var result = try c.query("select id, col_bool_arr from all_types where id = $1", .{9}); 1630 1651 defer result.deinit(); 1631 1652 1632 - const row = (try result.next()) orelse unreachable; 1653 + const row = (try result.nextUnsafe()) orelse unreachable; 1633 1654 try t.expectEqual(9, row.get(i32, 0)); 1634 1655 { 1635 1656 const arr = try row.iterator(?bool, 1).alloc(t.arena.allocator()); ··· 1660 1681 var result = try c.query("select id, col_text_arr from all_types where id = $1", .{10}); 1661 1682 defer result.deinit(); 1662 1683 1663 - const row = (try result.next()) orelse unreachable; 1684 + const row = (try result.nextUnsafe()) orelse unreachable; 1664 1685 try t.expectEqual(10, row.get(i32, 0)); 1665 1686 { 1666 1687 const arr = try row.iterator(?[]const u8, 1).alloc(t.arena.allocator()); ··· 1688 1709 const data = lib.Binary{ 1689 1710 .data = &.{ 1, 1, 0, 0, 32, 230, 16, 0, 0, 43, 107, 238, 243, 22, 122, 82, 192, 60, 20, 204, 226, 238, 89, 68, 64 }, 1690 1711 }; 1691 - var row = (try c.row("select $1::geography", .{data})).?; 1712 + var row = (try c.rowUnsafe("select $1::geography", .{data})).?; 1692 1713 defer row.deinit() catch {}; 1693 1714 try t.expectString(data.data, row.get([]const u8, 0)); 1694 1715 } ··· 1726 1747 var rows = try c.query("select $1::text", .{"!" ** 1000}); 1727 1748 defer rows.deinit(); 1728 1749 1729 - const row = (try rows.next()).?; 1750 + const row = (try rows.nextUnsafe()).?; 1730 1751 try t.expectString("!" ** 1000, row.get([]u8, 0)); 1731 1752 try t.expectEqual(null, try rows.next()); 1732 1753 } 1733 1754 1734 1755 { 1735 1756 // with a row 1736 - var row = (try c.row("select $1::text", .{"z" ** 1000})).?; 1757 + var row = (try c.rowUnsafe("select $1::text", .{"z" ** 1000})).?; 1737 1758 defer row.deinit() catch {}; 1738 1759 try t.expectString("z" ** 1000, row.get([]u8, 0)); 1739 1760 } ··· 1746 1767 var rows = try c.query("select $1::text", .{"!" ** 200}); 1747 1768 defer rows.deinit(); 1748 1769 1749 - const row = (try rows.next()).?; 1770 + const row = (try rows.nextUnsafe()).?; 1750 1771 try t.expectString("!" ** 200, row.get([]u8, 0)); 1751 1772 1752 1773 // we end here, simulating the app returning an error. This causes ··· 1760 1781 defer c.deinit(); 1761 1782 1762 1783 { 1763 - var row = (try c.row("select row(9001, 'hello'::text)", .{})).?; 1784 + var row = (try c.rowUnsafe("select row(9001, 'hello'::text)", .{})).?; 1764 1785 defer row.deinit() catch {}; 1765 1786 1766 1787 var record = row.record(0); ··· 1770 1791 } 1771 1792 1772 1793 { 1773 - var row = (try c.row("select row(null)", .{})).?; 1794 + var row = (try c.rowUnsafe("select row(null)", .{})).?; 1774 1795 defer row.deinit() catch {}; 1775 1796 1776 1797 var record = row.record(0); ··· 1789 1810 .application_name = "pg_zig_test", 1790 1811 }); 1791 1812 1792 - var row = (try conn.row("show application_name", .{})) orelse unreachable; 1813 + var row = (try conn.rowUnsafe("show application_name", .{})) orelse unreachable; 1793 1814 defer row.deinit() catch {}; 1794 1815 1795 1816 try t.expectString("pg_zig_test", row.get([]const u8, 0)); ··· 1921 1942 { 1922 1943 var result = try c.queryOpts("select $1::int as id, $2::text as name", .{ 1, "leto" }, .{ .cache_name = "c1" }); 1923 1944 try t.expectEqual(0, result.column_names.len); 1924 - const row = (try result.next()) orelse unreachable; 1945 + const row = (try result.nextUnsafe()) orelse unreachable; 1925 1946 try t.expectEqual(1, row.get(i32, 0)); 1926 1947 try t.expectString("leto", row.get([]u8, 1)); 1927 1948 ··· 1932 1953 { 1933 1954 var result = try c.queryOpts("slc", .{ 2, "ghanima" }, .{ .cache_name = "c1" }); 1934 1955 try t.expectEqual(0, result.column_names.len); 1935 - const row = (try result.next()) orelse unreachable; 1956 + const row = (try result.nextUnsafe()) orelse unreachable; 1936 1957 try t.expectEqual(2, row.get(i32, 0)); 1937 1958 try t.expectString("ghanima", row.get([]u8, 1)); 1938 1959 ··· 1958 1979 try t.expectString("id", result.column_names[0]); 1959 1980 try t.expectString("name", result.column_names[1]); 1960 1981 1961 - const row = (try result.next()) orelse unreachable; 1982 + const row = (try result.nextUnsafe()) orelse unreachable; 1962 1983 try t.expectEqual(1, row.get(i32, 0)); 1963 1984 try t.expectString("leto", row.get([]u8, 1)); 1964 1985 ··· 1972 1993 try t.expectString("id", result.column_names[0]); 1973 1994 try t.expectString("name", result.column_names[1]); 1974 1995 1975 - const row = (try result.next()) orelse unreachable; 1996 + const row = (try result.nextUnsafe()) orelse unreachable; 1976 1997 try t.expectEqual(2, row.get(i32, 0)); 1977 1998 try t.expectString("ghanima", row.get([]u8, 1)); 1978 1999
+48 -7
src/lib.zig
··· 24 24 25 25 const result = @import("result.zig"); 26 26 pub const Row = result.Row; 27 + pub const RowUnsafe = result.RowUnsafe; 27 28 pub const Result = result.Result; 28 29 pub const Iterator = result.Iterator; 30 + pub const IteratorUnsafe = result.IteratorUnsafe; 29 31 pub const QueryRow = result.QueryRow; 32 + pub const QueryRowUnsafe = result.QueryRowUnsafe; 30 33 pub const Mapper = result.Mapper; 31 34 32 35 const reader = @import("reader.zig"); ··· 59 62 } 60 63 } 61 64 62 - pub fn assertDecodeType(comptime T: type, comptime expected_oids: []const i32, actual: i32) void { 65 + pub fn verifyDecodeType(comptime fail_mode: FailMode, comptime T: type, comptime expected_oids: []const i32, actual: i32) !void { 66 + if (comptime fail_mode == .safe) { 67 + if (isExpectedId(expected_oids, actual)) { 68 + return; 69 + } 70 + return error.InvalidType; 71 + } 72 + 63 73 if (comptime _assert == false) { 64 74 return; 65 75 } 66 76 67 - inline for (expected_oids) |expected_oid| { 68 - if (expected_oid == actual) { 69 - return; 70 - } 77 + if (isExpectedId(expected_oids, actual)) { 78 + return; 71 79 } 72 80 73 81 log.warn("PostgreSQL value of type {s} cannot be read into a " ++ @typeName(T) ++ ". " ++ ··· 75 83 unreachable; 76 84 } 77 85 78 - pub fn assertNotNull(comptime T: type, is_null: bool) void { 86 + fn isExpectedId(comptime expected_oids: []const i32, actual: i32) bool { 87 + inline for (expected_oids) |expected_oid| { 88 + if (expected_oid == actual) { 89 + return true; 90 + } 91 + } 92 + return false; 93 + } 94 + 95 + pub fn verifyNotNull(comptime fail_mode: FailMode, comptime T: type, is_null: bool) !void { 96 + if (comptime fail_mode == .safe) { 97 + if (is_null == false) { 98 + return; 99 + } 100 + return error.UnexpectedNull; 101 + } 102 + 79 103 if (comptime _assert == false) { 80 104 return; 81 105 } ··· 89 113 unreachable; 90 114 } 91 115 92 - pub fn assertColumnName(name: []const u8, valid: bool) void { 116 + pub fn verifyColumnName(comptime fail_mode: FailMode, name: []const u8, valid: bool) !void { 117 + if (comptime fail_mode == .safe) { 118 + if (valid) { 119 + return; 120 + } 121 + return error.UnknownColumnName; 122 + } 123 + 93 124 if (comptime _assert == false) { 94 125 return; 95 126 } ··· 253 284 const TestCase = struct { 254 285 uri: []const u8, 255 286 expected_opts: Pool.Opts, 287 + }; 288 + 289 + pub const FailMode = enum { 290 + safe, 291 + unsafe, 292 + }; 293 + 294 + pub const TypeError = error{ 295 + InvalidType, 296 + UnexpectedNull, 256 297 }; 257 298 258 299 const valid_tcs: [2]TestCase = .{
+18 -6
src/pool.zig
··· 6 6 const Result = lib.Result; 7 7 const SSLCtx = lib.SSLCtx; 8 8 const QueryRow = lib.QueryRow; 9 + const QueryRowUnsafe = lib.QueryRowUnsafe; 9 10 const Listener = @import("listener.zig").Listener; 10 11 11 12 const Thread = std.Thread; ··· 240 241 return self.rowOpts(sql, values, .{}); 241 242 } 242 243 244 + pub fn rowUnsafe(self: *Pool, sql: []const u8, values: anytype) !?QueryRowUnsafe { 245 + return self.rowUnsafeOpts(sql, values, .{}); 246 + } 247 + 243 248 pub fn rowOpts(self: *Pool, sql: []const u8, values: anytype, opts_: Conn.QueryOpts) !?QueryRow { 244 249 var opts = opts_; 245 250 opts.release_conn = true; 246 251 var conn = try self.acquire(); 247 252 return conn.rowOpts(sql, values, opts); 253 + } 254 + 255 + pub fn rowUnsafeOpts(self: *Pool, sql: []const u8, values: anytype, opts_: Conn.QueryOpts) !?QueryRowUnsafe { 256 + var opts = opts_; 257 + opts.release_conn = true; 258 + var conn = try self.acquire(); 259 + return conn.rowUnsafeOpts(sql, values, opts); 248 260 } 249 261 }; 250 262 ··· 492 504 var result = try pool.query("select col_int8, col_text from all_types where id = any($1)", .{[2]i32{ 100, 101 }}); 493 505 defer result.deinit(); 494 506 495 - const row1 = (try result.next()) orelse unreachable; 507 + const row1 = (try result.nextUnsafe()) orelse unreachable; 496 508 try t.expectEqual(1, row1.get(i64, 0)); 497 509 try t.expectString("val-1", row1.get([]u8, 1)); 498 510 499 - const row2 = (try result.next()) orelse unreachable; 511 + const row2 = (try result.nextUnsafe()) orelse unreachable; 500 512 try t.expectEqual(2, row2.get(i64, 0)); 501 513 try t.expectString("val-2", row2.get([]u8, 1)); 502 514 503 - try t.expectEqual(null, result.next()); 515 + try t.expectEqual(null, result.nextUnsafe()); 504 516 } 505 517 506 518 for (0..3) |_| { 507 - var row = try pool.row("select col_int8, col_text from all_types where id = $1", .{101}) orelse unreachable; 519 + var row = try pool.rowUnsafe("select col_int8, col_text from all_types where id = $1", .{101}) orelse unreachable; 508 520 defer row.deinit() catch {}; 509 521 510 522 try t.expectEqual(2, row.get(i64, 0)); ··· 516 528 var pool = try Pool.init(t.allocator, .{ .size = 1, .auth = t.authOpts(.{}) }); 517 529 defer pool.deinit(); 518 530 519 - _ = try pool.row("insert into all_types (id) values ($1)", .{200}); 531 + _ = try pool.rowUnsafe("insert into all_types (id) values ($1)", .{200}); 520 532 521 533 // This would segfault: 522 534 // https://github.com/karlseguin/pg.zig/issues/34 523 - try t.expectError(error.PG, pool.row("insert into all_types (id) values ($1)", .{200})); 535 + try t.expectError(error.PG, pool.rowUnsafe("insert into all_types (id) values ($1)", .{200})); 524 536 525 537 try t.expectEqual(1, pool._available); 526 538 }
+337 -247
src/result.zig
··· 76 76 } 77 77 78 78 pub fn next(self: *Result) !?Row { 79 + return self._next(.safe); 80 + } 81 + pub fn nextUnsafe(self: *Result) !?RowUnsafe { 82 + return self._next(.unsafe); 83 + } 84 + 85 + fn _next(self: *Result, comptime fail_mode: lib.FailMode) !(if (fail_mode == .safe) ?Row else ?RowUnsafe) { 79 86 if (self._conn._state != .query) { 80 87 // Possibly weird state. Most likely cause is calling next() multiple times 81 88 // despite null being returned. ··· 242 249 }; 243 250 }; 244 251 245 - pub const Row = struct { 246 - _result: *Result, 247 - oids: []i32, 248 - values: []Result.State.Value, 252 + pub const Row = RowT(.safe); 253 + pub const RowUnsafe = RowT(.unsafe); 249 254 250 - pub fn get(self: *const Row, comptime T: type, col: usize) T { 251 - const value = self.values[col]; 252 - const TT = switch (@typeInfo(T)) { 253 - .optional => |opt| { 254 - if (value.is_null) { 255 - return null; 256 - } else { 257 - return self.get(opt.child, col); 258 - } 259 - }, 260 - .@"struct" => blk: { 261 - if (@hasDecl(T, "fromPgzRow") == true) { 262 - return T.fromPgzRow(value.data, self.oids[col]) catch { 263 - std.debug.panic("PostgreSQL value of type {s} could not be read into a " ++ @typeName(T) ++ ".", .{types.oidToString(self.oids[col])}); 264 - }; 265 - } 266 - break :blk T; 267 - }, 268 - else => blk: { 269 - lib.assertNotNull(T, value.is_null); 270 - break :blk T; 271 - }, 272 - }; 255 + pub fn RowT(comptime fail_mode: lib.FailMode) type { 256 + return struct { 257 + _result: *Result, 258 + oids: []i32, 259 + values: []Result.State.Value, 273 260 274 - return getScalar(TT, value.data, self.oids[col]); 275 - } 261 + const Self = @This(); 276 262 277 - pub fn getCol(self: *const Row, comptime T: type, name: []const u8) T { 278 - const col = self._result.columnIndex(name); 279 - lib.assertColumnName(name, col != null); 280 - return self.get(T, col.?); 281 - } 263 + pub fn get(self: *const Self, comptime T: type, col: usize) if (fail_mode == .safe) lib.TypeError!T else T { 264 + const value = self.values[col]; 265 + const TT = switch (@typeInfo(T)) { 266 + .optional => |opt| { 267 + if (value.is_null) { 268 + return null; 269 + } 270 + const val = self.get(opt.child, col); 271 + if (comptime fail_mode == .safe) { 272 + return try val; 273 + } 274 + return val; 275 + }, 276 + .@"struct" => blk: { 277 + if (@hasDecl(T, "fromPgzRow") == true) { 278 + return T.fromPgzRow(value.data, self.oids[col]) catch |err| { 279 + if (comptime fail_mode == .safe) { 280 + return err; 281 + } 282 + std.debug.panic("PostgreSQL value of type {s} could not be read into a " ++ @typeName(T) ++ ".", .{types.oidToString(self.oids[col])}); 283 + }; 284 + } 285 + break :blk T; 286 + }, 287 + else => blk: { 288 + lib.verifyNotNull(fail_mode, T, value.is_null) catch |err| { 289 + if (comptime fail_mode == .unsafe) unreachable; 290 + return err; 291 + }; 292 + break :blk T; 293 + }, 294 + }; 282 295 283 - pub fn iterator(self: *const Row, comptime T: type, col: usize) Iterator(T) { 284 - const value = self.values[col]; 285 - if (value.is_null) { 286 - return Iterator(T).asNull(); 296 + return getScalar(fail_mode, TT, value.data, self.oids[col]); 287 297 } 288 - return Iterator(T).fromPgzRow(value.data, self.oids[col]) catch @panic("Could not get iterator of type " ++ @typeName(T) ++ " for row."); 289 - } 290 298 291 - pub fn iteratorCol(self: *const Row, comptime T: type, name: []const u8) Iterator(T) { 292 - const col = self._result.columnIndex(name); 293 - lib.assertColumnName(name, col != null); 294 - return self.iterator(T, col.?); 295 - } 299 + pub fn getCol(self: *const Self, comptime T: type, name: []const u8) if (fail_mode == .safe) lib.TypeError!T else T { 300 + const col = self._result.columnIndex(name); 301 + try lib.verifyColumnName(fail_mode, name, col != null); 302 + return self.get(T, col.?); 303 + } 296 304 297 - pub fn record(self: *const Row, col: usize) Record { 298 - const data = self.values[col].data; 299 - const number_of_columns = std.mem.readInt(i32, data[0..4], .big); 300 - return .{ 301 - .data = data[4..], 302 - .number_of_columns = @intCast(number_of_columns), 303 - }; 304 - } 305 + pub fn iterator(self: *const Self, comptime T: type, col: usize) if (fail_mode == .safe) lib.TypeError!Iterator(T) else IteratorUnsafe(T) { 306 + const value = self.values[col]; 307 + if (value.is_null) { 308 + return IteratorT(fail_mode, T).asNull(); 309 + } 310 + return IteratorT(fail_mode, T).fromPgzRow(value.data, self.oids[col]) catch |err| { 311 + if (comptime fail_mode == .safe) { 312 + return err; 313 + } 314 + @panic("Could not get iterator of type " ++ @typeName(T) ++ " for row."); 315 + }; 316 + } 305 317 306 - pub fn recordCol(self: *const Row, name: []const u8) Record { 307 - const col = self._result.columnIndex(name); 308 - lib.assertColumnName(name, col != null); 309 - return self.record(col); 310 - } 311 - 312 - const ToOpts = struct { 313 - dupe: bool = false, 314 - map: Mapping = .ordinal, 315 - allocator: ?Allocator = null, 318 + pub fn iteratorCol(self: *const Self, comptime T: type, name: []const u8) if (fail_mode == .safe) lib.TypeError!Iterator(T) else IteratorUnsafe(T) { 319 + const col = self._result.columnIndex(name); 320 + try lib.verifyColumnName(fail_mode, name, col != null); 321 + return self.iterator(T, col.?); 322 + } 316 323 317 - const Mapping = enum { 318 - name, 319 - ordinal, 320 - }; 321 - }; 324 + pub fn record(self: *const Self, col: usize) RecordT(fail_mode) { 325 + const data = self.values[col].data; 326 + const number_of_columns = std.mem.readInt(i32, data[0..4], .big); 327 + return .{ 328 + .data = data[4..], 329 + .number_of_columns = @intCast(number_of_columns), 330 + }; 331 + } 322 332 323 - pub fn to(self: *const Row, T: type, opts: ToOpts) !T { 324 - // if we're given an allocator, use that. 325 - // if we're not given an allocator, but asked to dupe use our arena and thus 326 - // tie the lifetime of the returned T to the lifetime of the DB result object. 327 - var allocator: ?Allocator = null; 328 - if (opts.allocator) |a| { 329 - allocator = a; 330 - } else if (opts.dupe) { 331 - allocator = self._result._arena.allocator(); 333 + pub fn recordCol(self: *const Self, name: []const u8) if (fail_mode == .safe) lib.TypeError!Record else RecordUnsafe { 334 + const col = self._result.columnIndex(name); 335 + try lib.verifyColumnName(fail_mode, name, col != null); 336 + return self.record(col); 332 337 } 333 338 334 - return switch (opts.map) { 335 - .ordinal => self.toUsingOrdinal(T, allocator), 336 - .name => return self.toUsingName(T, allocator), 339 + const ToOpts = struct { 340 + dupe: bool = false, 341 + map: Mapping = .ordinal, 342 + allocator: ?Allocator = null, 343 + 344 + const Mapping = enum { 345 + name, 346 + ordinal, 347 + }; 337 348 }; 338 - } 349 + 350 + pub fn to(self: *const Self, T: type, opts: ToOpts) !T { 351 + // if we're given an allocator, use that. 352 + // if we're not given an allocator, but asked to dupe use our arena and thus 353 + // tie the lifetime of the returned T to the lifetime of the DB result object. 354 + var allocator: ?Allocator = null; 355 + if (opts.allocator) |a| { 356 + allocator = a; 357 + } else if (opts.dupe) { 358 + allocator = self._result._arena.allocator(); 359 + } 339 360 340 - fn toUsingOrdinal(self: *const Row, T: type, allocator: ?Allocator) !T { 341 - var value: T = undefined; 342 - inline for (std.meta.fields(T), 0..) |field, column_index| { 343 - @field(value, field.name) = try self.mapColumn(&field, column_index, allocator); 361 + return switch (opts.map) { 362 + .ordinal => self.toUsingOrdinal(T, allocator), 363 + .name => return self.toUsingName(T, allocator), 364 + }; 344 365 } 345 - return value; 346 - } 347 366 348 - fn toUsingName(self: *const Row, T: type, allocator: ?Allocator) !T { 349 - var value: T = undefined; 350 - const result = self._result; 351 - inline for (std.meta.fields(T)) |field| { 352 - const name = field.name; 353 - @field(value, name) = try self.mapColumn(&field, result.columnIndex(name), allocator); 367 + fn toUsingOrdinal(self: *const Self, T: type, allocator: ?Allocator) !T { 368 + var value: T = undefined; 369 + inline for (std.meta.fields(T), 0..) |field, column_index| { 370 + @field(value, field.name) = try self.mapColumn(&field, column_index, allocator); 371 + } 372 + return value; 354 373 } 355 - return value; 356 - } 357 374 358 - fn mapColumn(self: *const Row, field: *const std.builtin.Type.StructField, optional_column_index: ?usize, allocator: ?Allocator) !field.type { 359 - const T = field.type; 360 - const column_index = optional_column_index orelse { 361 - if (field.default_value_ptr) |dflt| { 362 - return @as(*align(1) const field.type, @ptrCast(dflt)).*; 375 + fn toUsingName(self: *const Self, T: type, allocator: ?Allocator) !T { 376 + var value: T = undefined; 377 + const result = self._result; 378 + inline for (std.meta.fields(T)) |field| { 379 + const name = field.name; 380 + @field(value, name) = try self.mapColumn(&field, result.columnIndex(name), allocator); 363 381 } 364 - return error.FieldColumnMismatch; 365 - }; 382 + return value; 383 + } 366 384 367 - if (comptime isSlice(T)) |S| { 368 - const slice = blk: { 369 - if (@typeInfo(T) == .optional) { 370 - break :blk self.get(?Iterator(S), column_index) orelse return null; 371 - } else { 372 - break :blk self.get(Iterator(S), column_index); 385 + fn mapColumn(self: *const Self, field: *const std.builtin.Type.StructField, optional_column_index: ?usize, allocator: ?Allocator) !field.type { 386 + const T = field.type; 387 + const column_index = optional_column_index orelse { 388 + if (field.default_value_ptr) |dflt| { 389 + return @as(*align(1) const field.type, @ptrCast(dflt)).*; 373 390 } 391 + return error.FieldColumnMismatch; 374 392 }; 375 - return try slice.alloc(allocator orelse return error.AllocatorRequiredForSliceMapping); 393 + 394 + if (comptime isSlice(T)) |S| { 395 + const slice = blk: { 396 + if (@typeInfo(T) == .optional) { 397 + break :blk self.get(?Iterator(S), column_index) orelse return null; 398 + } else { 399 + break :blk self.get(Iterator(S), column_index); 400 + } 401 + }; 402 + return try slice.alloc(allocator orelse return error.AllocatorRequiredForSliceMapping); 403 + } 404 + 405 + const value = self.get(field.type, column_index); 406 + const a = allocator orelse return value; 407 + return mapValue(T, if (comptime fail_mode == .safe) try value else value, a); 376 408 } 377 - 378 - const value = self.get(field.type, column_index); 379 - const a = allocator orelse return value; 380 - return mapValue(T, value, a); 381 - } 382 - }; 409 + }; 410 + } 383 411 384 412 fn isSlice(comptime T: type) ?type { 385 413 switch (@typeInfo(T)) { ··· 438 466 }; 439 467 } 440 468 441 - pub const QueryRow = struct { 442 - row: Row, 443 - result: *Result, 469 + pub const QueryRow = QueryRowT(.safe); 470 + pub const QueryRowUnsafe = QueryRowT(.unsafe); 444 471 445 - pub fn get(self: *const QueryRow, comptime T: type, col: usize) T { 446 - return self.row.get(T, col); 447 - } 472 + pub fn QueryRowT(comptime fail_mode: lib.FailMode) type { 473 + return struct { 474 + row: RowT(fail_mode), 475 + result: *Result, 448 476 449 - pub fn getCol(self: *const QueryRow, comptime T: type, name: []const u8) T { 450 - return self.row.getCol(T, name); 451 - } 477 + const Self = @This(); 452 478 453 - pub fn iterator(self: *const QueryRow, comptime T: type, col: usize) Iterator(T) { 454 - return self.row.iterator(T, col); 455 - } 479 + pub fn get(self: *const Self, comptime T: type, col: usize) if (fail_mode == .safe) lib.TypeError!T else T { 480 + return self.row.get(T, col); 481 + } 456 482 457 - pub fn iteratorCol(self: *const QueryRow, comptime T: type, name: []const u8) Iterator(T) { 458 - return self.row.iteratorCol(T, name); 459 - } 483 + pub fn getCol(self: *const Self, comptime T: type, name: []const u8) if (fail_mode == .safe) lib.TypeError!T else T { 484 + return self.row.getCol(T, name); 485 + } 460 486 461 - pub fn record(self: *const QueryRow, col: usize) Record { 462 - return self.row.record(col); 463 - } 487 + pub fn iterator(self: *const Self, comptime T: type, col: usize) if (fail_mode == .safe) lib.TypeError!Iterator(T) else IteratorUnsafe(T) { 488 + return self.row.iterator(T, col); 489 + } 464 490 465 - pub fn recordCol(self: *const QueryRow, name: []const u8) Record { 466 - return self.row.recordCol(name); 467 - } 468 - pub fn to(self: *const QueryRow, T: type, opts: Row.ToOpts) !T { 469 - return self.row.to(T, opts); 470 - } 491 + pub fn iteratorCol(self: *const Self, comptime T: type, name: []const u8) if (fail_mode == .safe) lib.TypeError!Iterator(T) else IteratorUnsafe(T) { 492 + return self.row.iteratorCol(T, name); 493 + } 494 + 495 + pub fn record(self: *const Self, col: usize) RecordT(fail_mode) { 496 + return self.row.record(col); 497 + } 498 + 499 + pub fn recordCol(self: *const Self, name: []const u8) if (fail_mode == .safe) lib.TypeError!Record else RecordUnsafe { 500 + return self.row.recordCol(name); 501 + } 502 + pub fn to(self: *const Self, T: type, opts: Row.ToOpts) !T { 503 + return self.row.to(T, opts); 504 + } 471 505 472 - pub fn deinit(self: *QueryRow) !void { 473 - // this is unfortunate 474 - try self.result.drain(); 475 - self.result.deinit(); 476 - } 477 - }; 506 + pub fn deinit(self: *Self) !void { 507 + // this is unfortunate 508 + try self.result.drain(); 509 + self.result.deinit(); 510 + } 511 + }; 512 + } 478 513 479 514 pub fn Iterator(comptime T: type) type { 515 + return IteratorT(.safe, T); 516 + } 517 + pub fn IteratorUnsafe(comptime T: type) type { 518 + return IteratorT(.unsafe, T); 519 + } 520 + pub fn IteratorT(comptime fail_mode: lib.FailMode, comptime T: type) type { 480 521 return struct { 481 522 is_null: bool, 482 523 _len: usize, ··· 520 561 521 562 const decoder = switch (TT) { 522 563 u8 => blk: { 523 - lib.assertDecodeType([]u8, &.{types.CharArray.oid.decimal}, oid); 564 + lib.verifyDecodeType(fail_mode, []u8, &.{types.CharArray.oid.decimal}, oid) catch |err| { 565 + if (comptime fail_mode == .unsafe) unreachable; 566 + return err; 567 + }; 524 568 break :blk &types.Char.decodeKnown; 525 569 }, 526 570 i16 => blk: { 527 - lib.assertDecodeType([]i16, &.{types.Int16Array.oid.decimal}, oid); 571 + lib.verifyDecodeType(fail_mode, []i16, &.{types.Int16Array.oid.decimal}, oid) catch |err| { 572 + if (comptime fail_mode == .unsafe) unreachable; 573 + return err; 574 + }; 528 575 break :blk &types.Int16.decodeKnown; 529 576 }, 530 577 i32 => blk: { 531 - lib.assertDecodeType([]i32, &.{types.Int32Array.oid.decimal}, oid); 578 + lib.verifyDecodeType(fail_mode, []i32, &.{types.Int32Array.oid.decimal}, oid) catch |err| { 579 + if (comptime fail_mode == .unsafe) unreachable; 580 + return err; 581 + }; 532 582 break :blk &types.Int32.decodeKnown; 533 583 }, 534 584 i64 => switch (oid) { ··· 538 588 else => std.debug.panic("{d} oid cannot target i64 iterator", .{oid}), 539 589 }, 540 590 f32 => blk: { 541 - lib.assertDecodeType([]f32, &.{types.Float32Array.oid.decimal}, oid); 591 + lib.verifyDecodeType(fail_mode, []f32, &.{types.Float32Array.oid.decimal}, oid) catch |err| { 592 + if (comptime fail_mode == .unsafe) unreachable; 593 + return err; 594 + }; 542 595 break :blk &types.Float32.decodeKnown; 543 596 }, 544 597 f64 => switch (oid) { ··· 547 600 else => std.debug.panic("{d} oid cannot target f64 iterator", .{oid}), 548 601 }, 549 602 bool => blk: { 550 - lib.assertDecodeType([]bool, &.{types.BoolArray.oid.decimal}, oid); 603 + lib.verifyDecodeType(fail_mode, []bool, &.{types.BoolArray.oid.decimal}, oid) catch |err| { 604 + if (comptime fail_mode == .unsafe) unreachable; 605 + return err; 606 + }; 551 607 break :blk &types.Bool.decodeKnown; 552 608 }, 553 609 []const u8 => switch (oid) { ··· 559 615 else => &types.Bytea.decodeKnownMutable, 560 616 }, 561 617 types.Numeric => blk: { 562 - lib.assertDecodeType([]f64, &.{types.NumericArray.oid.decimal}, oid); 618 + lib.verifyDecodeType(fail_mode, []f64, &.{types.NumericArray.oid.decimal}, oid) catch |err| { 619 + if (comptime fail_mode == .unsafe) unreachable; 620 + return err; 621 + }; 563 622 break :blk &types.Numeric.decodeKnown; 564 623 }, 565 624 types.Cidr => blk: { 566 - lib.assertDecodeType([]types.Cidr, &.{ types.CidrArray.oid.decimal, types.CidrArray.inet_oid.decimal }, oid); 625 + lib.verifyDecodeType(fail_mode, []types.Cidr, &.{ types.CidrArray.oid.decimal, types.CidrArray.inet_oid.decimal }, oid) catch |err| { 626 + if (comptime fail_mode == .unsafe) unreachable; 627 + return err; 628 + }; 567 629 break :blk &types.Cidr.decodeKnown; 568 630 }, 569 631 else => switch (@typeInfo(TT)) { 570 632 .@"enum" => blk: { 571 - lib.assertDecodeType([]const u8, &.{types.StringArray.oid.decimal}, oid); 633 + lib.verifyDecodeType(fail_mode, []const u8, &.{types.StringArray.oid.decimal}, oid) catch |err| { 634 + if (comptime fail_mode == .unsafe) unreachable; 635 + return err; 636 + }; 572 637 break :blk &EnumDecoder(TT).decodeKnown; 573 638 }, 574 639 else => compileHaltGetError(T), ··· 690 755 @compileError("cannot get value of type " ++ @typeName(T)); 691 756 } 692 757 693 - const Record = struct { 694 - data: []const u8, 695 - number_of_columns: usize, 758 + pub const Record = RecordT(.safe); 759 + pub const RecordUnsafe = RecordT(.unsafe); 696 760 697 - pub fn next(self: *Record, comptime T: type) T { 698 - var data = self.data; 761 + pub fn RecordT(comptime fail_mode: lib.FailMode) type { 762 + return struct { 763 + data: []const u8, 764 + number_of_columns: usize, 699 765 700 - // at least 4 bytes for the type and 4 bytes for the lenght 701 - lib.assert(data.len >= 8); 766 + const Self = @This(); 702 767 703 - const oid = std.mem.readInt(i32, data[0..4], .big); 768 + pub fn next(self: *Self, comptime T: type) if (fail_mode == .safe) lib.TypeError!T else T { 769 + var data = self.data; 704 770 705 - data = data[4..]; 706 - const len = std.mem.readInt(i32, data[0..4], .big); 771 + // at least 4 bytes for the type and 4 bytes for the lenght 772 + lib.assert(data.len >= 8); 773 + 774 + const oid = std.mem.readInt(i32, data[0..4], .big); 775 + 776 + data = data[4..]; 777 + const len = std.mem.readInt(i32, data[0..4], .big); 707 778 708 - const TT = switch (@typeInfo(T)) { 709 - .optional => |opt| blk: { 710 - if (len == -1) return null; 711 - break :blk opt.child; 712 - }, 713 - else => T, 714 - }; 779 + const TT = switch (@typeInfo(T)) { 780 + .optional => |opt| blk: { 781 + if (len == -1) return null; 782 + break :blk opt.child; 783 + }, 784 + else => T, 785 + }; 715 786 716 - // end of the data for this "column" 717 - const end = @as(usize, @intCast(len)) + 4; 787 + // end of the data for this "column" 788 + const end = @as(usize, @intCast(len)) + 4; 718 789 719 - // the rest of the data 720 - self.data = data[end..]; 790 + // the rest of the data 791 + self.data = data[end..]; 721 792 722 - // start at 4 to skip the length which we already read 723 - return getScalar(TT, data[4..end], oid); 724 - } 725 - }; 793 + // start at 4 to skip the length which we already read 794 + return getScalar(fail_mode, TT, data[4..end], oid); 795 + } 796 + }; 797 + } 726 798 727 - fn getScalar(T: type, data: []const u8, oid: i32) T { 799 + fn getScalar(comptime fail_mode: lib.FailMode, comptime T: type, data: []const u8, oid: i32) if (fail_mode == .safe) lib.TypeError!T else T { 728 800 switch (T) { 729 - u8 => return types.Char.decode(data, oid), 730 - i16 => return types.Int16.decode(data, oid), 731 - i32 => return types.Int32.decode(data, oid), 732 - i64 => return types.Int64.decode(data, oid), 733 - f32 => return types.Float32.decode(data, oid), 734 - f64 => return types.Float64.decode(data, oid), 735 - bool => return types.Bool.decode(data, oid), 801 + u8 => return types.Char.decode(fail_mode, data, oid), 802 + i16 => return types.Int16.decode(fail_mode, data, oid), 803 + i32 => return types.Int32.decode(fail_mode, data, oid), 804 + i64 => return types.Int64.decode(fail_mode, data, oid), 805 + f32 => return types.Float32.decode(fail_mode, data, oid), 806 + f64 => return types.Float64.decode(fail_mode, data, oid), 807 + bool => return types.Bool.decode(fail_mode, data, oid), 736 808 []const u8 => return types.Bytea.decode(data, oid), 737 809 []u8 => return @constCast(types.Bytea.decode(data, oid)), 738 - types.Numeric => return types.Numeric.decode(data, oid), 739 - types.Cidr => return types.Cidr.decode(data, oid), 810 + types.Numeric => return types.Numeric.decode(fail_mode, data, oid), 811 + types.Cidr => return types.Cidr.decode(fail_mode, data, oid), 740 812 else => switch (@typeInfo(T)) { 741 813 .@"enum" => { 742 814 const str = types.Bytea.decode(data, oid); ··· 758 830 // int max 759 831 var result = try c.query(sql, .{ @as(i16, 32767), @as(i32, 2147483647), @as(i64, 9223372036854775807) }); 760 832 defer result.deinit(); 761 - const row = (try result.next()).?; 833 + const row = (try result.nextUnsafe()).?; 762 834 try t.expectEqual(32767, row.get(i16, 0)); 763 835 try t.expectEqual(2147483647, row.get(i32, 1)); 764 836 try t.expectEqual(9223372036854775807, row.get(i64, 2)); ··· 774 846 // int min 775 847 var result = try c.query(sql, .{ @as(i16, -32768), @as(i32, -2147483648), @as(i64, -9223372036854775808) }); 776 848 defer result.deinit(); 777 - const row = (try result.next()).?; 849 + const row = (try result.nextUnsafe()).?; 778 850 try t.expectEqual(-32768, row.get(i16, 0)); 779 851 try t.expectEqual(-2147483648, row.get(i32, 1)); 780 852 try t.expectEqual(-9223372036854775808, row.get(i64, 2)); ··· 786 858 var result = try c.query(sql, .{ null, null, null }); 787 859 defer result.deinit(); 788 860 defer result.drain() catch unreachable; 789 - const row = (try result.next()).?; 861 + const row = (try result.nextUnsafe()).?; 790 862 try t.expectEqual(null, row.get(?i16, 0)); 791 863 try t.expectEqual(null, row.get(?i32, 1)); 792 864 try t.expectEqual(null, row.get(?i64, 2)); ··· 796 868 // uint within limit 797 869 var result = try c.query(sql, .{ @as(u16, 32767), @as(u32, 2147483647), @as(u64, 9223372036854775807) }); 798 870 defer result.deinit(); 799 - const row = (try result.next()).?; 871 + const row = (try result.nextUnsafe()).?; 800 872 try t.expectEqual(32767, row.get(i16, 0)); 801 873 try t.expectEqual(2147483647, row.get(i32, 1)); 802 874 try t.expectEqual(9223372036854775807, row.get(i64, 2)); ··· 826 898 // positive float 827 899 var result = try c.query(sql, .{ @as(f32, 1.23456), @as(f64, 1093.229183) }); 828 900 defer result.deinit(); 829 - const row = (try result.next()).?; 901 + const row = (try result.nextUnsafe()).?; 830 902 try t.expectEqual(1.23456, row.get(f32, 0)); 831 903 try t.expectEqual(1093.229183, row.get(f64, 1)); 832 904 ··· 840 912 // negative float 841 913 var result = try c.query(sql, .{ @as(f32, -392.31), @as(f64, -99991.99992) }); 842 914 defer result.deinit(); 843 - const row = (try result.next()).?; 915 + const row = (try result.nextUnsafe()).?; 844 916 try t.expectEqual(-392.31, row.get(f32, 0)); 845 917 try t.expectEqual(-99991.99992, row.get(f64, 1)); 846 918 try t.expectEqual(null, result.next()); ··· 850 922 // null float 851 923 var result = try c.query(sql, .{ null, null }); 852 924 defer result.deinit(); 853 - const row = (try result.next()).?; 925 + const row = (try result.nextUnsafe()).?; 854 926 try t.expectEqual(null, row.get(?f32, 0)); 855 927 try t.expectEqual(null, row.get(?f64, 1)); 856 928 try t.expectEqual(null, result.next()); ··· 867 939 var result = try c.query(sql, .{true}); 868 940 defer result.deinit(); 869 941 defer result.drain() catch unreachable; 870 - const row = (try result.next()).?; 942 + const row = (try result.nextUnsafe()).?; 871 943 try t.expectEqual(true, row.get(bool, 0)); 872 944 try t.expectEqual(true, row.get(?bool, 0)); 873 945 try t.expectEqual(null, result.next()); ··· 878 950 var result = try c.query(sql, .{false}); 879 951 defer result.deinit(); 880 952 defer result.drain() catch unreachable; 881 - const row = (try result.next()).?; 953 + const row = (try result.nextUnsafe()).?; 882 954 try t.expectEqual(false, row.get(bool, 0)); 883 955 try t.expectEqual(false, row.get(?bool, 0)); 884 956 try t.expectEqual(null, result.next()); ··· 889 961 var result = try c.query(sql, .{null}); 890 962 defer result.deinit(); 891 963 defer result.drain() catch unreachable; 892 - const row = (try result.next()).?; 964 + const row = (try result.nextUnsafe()).?; 893 965 try t.expectEqual(null, row.get(?bool, 0)); 894 966 try t.expectEqual(null, result.next()); 895 967 } ··· 904 976 // empty 905 977 var result = try c.query(sql, .{ "", "" }); 906 978 defer result.deinit(); 907 - const row = (try result.next()).?; 979 + const row = (try result.nextUnsafe()).?; 908 980 try t.expectString("", row.get([]u8, 0)); 909 981 try t.expectString("", row.get(?[]u8, 0).?); 910 982 try t.expectString("", row.get([]u8, 1)); ··· 916 988 // not empty 917 989 var result = try c.query(sql, .{ "it's over 9000!!!", "i will Not fear" }); 918 990 defer result.deinit(); 919 - const row = (try result.next()).?; 991 + const row = (try result.nextUnsafe()).?; 920 992 try t.expectString("it's over 9000!!!", row.get([]u8, 0)); 921 993 try t.expectString("it's over 9000!!!", row.get(?[]const u8, 0).?); 922 994 try t.expectString("i will Not fear", row.get([]const u8, 1)); ··· 928 1000 // as an array 929 1001 var result = try c.query(sql, .{ [_]u8{ 'a', 'c', 'b' }, [_]u8{ 'z', 'z', '3' } }); 930 1002 defer result.deinit(); 931 - const row = (try result.next()).?; 1003 + const row = (try result.nextUnsafe()).?; 932 1004 try t.expectString("acb", row.get([]const u8, 0)); 933 1005 try t.expectString("acb", row.get(?[]u8, 0).?); 934 1006 try t.expectString("zz3", row.get([]const u8, 1)); ··· 944 1016 945 1017 var result = try c.query(sql, .{ s1, constString() }); 946 1018 defer result.deinit(); 947 - const row = (try result.next()).?; 1019 + const row = (try result.nextUnsafe()).?; 948 1020 try t.expectString("Leto", row.get([]u8, 0)); 949 1021 try t.expectString("Leto", row.get(?[]u8, 0).?); 950 1022 try t.expectString("Ghanima", row.get([]u8, 1)); ··· 956 1028 // null 957 1029 var result = try c.query(sql, .{ null, null }); 958 1030 defer result.deinit(); 959 - const row = (try result.next()).?; 1031 + const row = (try result.nextUnsafe()).?; 960 1032 try t.expectEqual(null, row.get(?[]u8, 0)); 961 1033 try t.expectEqual(null, row.get(?[]u8, 1)); 962 1034 try result.drain(); ··· 976 1048 // int max 977 1049 var result = try c.query(sql, .{ @as(?i32, 321), @as(?i32, null) }); 978 1050 defer result.deinit(); 979 - const row = (try result.next()).?; 1051 + const row = (try result.nextUnsafe()).?; 980 1052 try t.expectEqual(321, row.get(i32, 0)); 981 1053 982 1054 try t.expectEqual(321, row.get(?i32, 0)); ··· 993 1065 // empty row.iterator() 994 1066 var result = try c.query("select $1::int[]", .{[_]i32{}}); 995 1067 defer result.deinit(); 996 - var row = (try result.next()).?; 1068 + var row = (try result.nextUnsafe()).?; 997 1069 998 1070 var iterator = row.iterator(i32, 0); 999 1071 try t.expectEqual(0, iterator.len()); ··· 1010 1082 // empty row.get() 1011 1083 var result = try c.query("select $1::int[]", .{[_]i32{}}); 1012 1084 defer result.deinit(); 1013 - var row = (try result.next()).?; 1085 + var row = (try result.nextUnsafe()).?; 1014 1086 1015 1087 var iterator = row.get(Iterator(i32), 0); 1016 1088 try t.expectEqual(0, iterator.len()); ··· 1027 1099 // one: row.iterator 1028 1100 var result = try c.query("select $1::int[]", .{[_]i32{9}}); 1029 1101 defer result.deinit(); 1030 - var row = (try result.next()).?; 1102 + var row = (try result.nextUnsafe()).?; 1031 1103 1032 1104 var iterator = row.iterator(i32, 0); 1033 1105 try t.expectEqual(1, iterator.len()); ··· 1046 1118 // one: row.get 1047 1119 var result = try c.query("select $1::int[]", .{[_]i32{9}}); 1048 1120 defer result.deinit(); 1049 - var row = (try result.next()).?; 1121 + var row = (try result.nextUnsafe()).?; 1050 1122 1051 1123 var iterator = row.get(Iterator(i32), 0); 1052 1124 try t.expectEqual(1, iterator.len()); ··· 1065 1137 // fill 1066 1138 var result = try c.query("select $1::int[]", .{[_]i32{ 0, -19 }}); 1067 1139 defer result.deinit(); 1068 - var row = (try result.next()).?; 1140 + var row = (try result.nextUnsafe()).?; 1069 1141 1070 1142 var iterator = row.iterator(i32, 0); 1071 1143 try t.expectEqual(2, iterator.len()); ··· 1096 1168 var result = try c.query("select $1::int[]", .{null}); 1097 1169 defer result.deinit(); 1098 1170 1099 - var row = (try result.next()).?; 1171 + var row = (try result.nextUnsafe()).?; 1100 1172 1101 1173 var iterator = row.iterator(i32, 0); 1102 1174 try t.expectEqual(true, iterator.is_null); ··· 1109 1181 var result = try c.query("select $1::text[]", .{null}); 1110 1182 defer result.deinit(); 1111 1183 1112 - var row = (try result.next()).?; 1184 + var row = (try result.nextUnsafe()).?; 1113 1185 1114 1186 var iterator = row.iterator([]u8, 0); 1115 1187 try t.expectEqual(true, iterator.is_null); ··· 1126 1198 var result = try c.query(sql, .{ [_]i16{ -303, 9449, 2 }, [_]i32{ -3003, 49493229, 0 }, [_]i64{ 944949338498392, -2 } }); 1127 1199 defer result.deinit(); 1128 1200 1129 - var row = (try result.next()).?; 1201 + var row = (try result.nextUnsafe()).?; 1130 1202 1131 1203 const v1 = try row.iterator(i16, 0).alloc(t.allocator); 1132 1204 defer t.allocator.free(v1); ··· 1149 1221 var result = try c.query(sql, .{ [_]f32{ 1.1, 0, -384.2 }, [_]f64{ -888585.123322, 0.001 } }); 1150 1222 defer result.deinit(); 1151 1223 1152 - var row = (try result.next()).?; 1224 + var row = (try result.nextUnsafe()).?; 1153 1225 1154 1226 const v1 = try row.iterator(f32, 0).alloc(t.allocator); 1155 1227 defer t.allocator.free(v1); ··· 1168 1240 var result = try c.query(sql, .{[_]bool{ true, false, false }}); 1169 1241 defer result.deinit(); 1170 1242 1171 - var row = (try result.next()).?; 1243 + var row = (try result.nextUnsafe()).?; 1172 1244 1173 1245 const v1 = try row.iterator(bool, 0).alloc(t.allocator); 1174 1246 defer t.allocator.free(v1); ··· 1185 1257 var result = try c.query(sql, .{ [_][]const u8{ "over", "9000" }, [_][]u8{ &arr1, &arr2 } }); 1186 1258 defer result.deinit(); 1187 1259 1188 - var row = (try result.next()).?; 1260 + var row = (try result.nextUnsafe()).?; 1189 1261 1190 1262 const v1 = try row.iterator([]u8, 0).alloc(t.allocator); 1191 1263 defer { ··· 1227 1299 } 1228 1300 1229 1301 { 1230 - var row = (try c.row("select array['Leto', 'Test']::text[]", .{})) orelse unreachable; 1302 + var row = (try c.rowUnsafe("select array['Leto', 'Test']::text[]", .{})) orelse unreachable; 1231 1303 defer row.deinit() catch {}; 1232 1304 arr1 = try row.iterator([]const u8, 0).alloc(t.allocator); 1233 1305 } 1234 1306 1235 1307 { 1236 - var row = (try c.row("select array['Ghanima', 'Goku']::text[]", .{})) orelse unreachable; 1308 + var row = (try c.rowUnsafe("select array['Ghanima', 'Goku']::text[]", .{})) orelse unreachable; 1237 1309 defer row.deinit() catch {}; 1238 1310 arr2 = try row.iterator([]const u8, 0).alloc(t.allocator); 1239 1311 } ··· 1249 1321 var result = try c.query(sql, .{ "fcbebf0f-b996-43b9-9818-672bc689cda8", &[_]u8{ 174, 47, 71, 95, 128, 112, 65, 183, 186, 51, 134, 187, 168, 137, 123, 222 } }); 1250 1322 defer result.deinit(); 1251 1323 1252 - const row = (try result.next()).?; 1324 + const row = (try result.nextUnsafe()).?; 1253 1325 try t.expectSlice(u8, &.{ 252, 190, 191, 15, 185, 150, 67, 185, 152, 24, 103, 43, 198, 137, 205, 168 }, row.get([]u8, 0)); 1254 1326 try t.expectSlice(u8, &.{ 174, 47, 71, 95, 128, 112, 65, 183, 186, 51, 134, 187, 168, 137, 123, 222 }, row.get([]u8, 1)); 1255 1327 } ··· 1261 1333 var result = try c.query(sql, .{32788447688}); 1262 1334 defer result.deinit(); 1263 1335 1264 - const row = (try result.next()).?; 1336 + const row = (try result.nextUnsafe()).?; 1265 1337 try t.expectEqual(32788447689, row.get(i64, 0)); 1266 1338 } 1267 1339 ··· 1269 1341 var c = t.connect(.{}); 1270 1342 defer c.deinit(); 1271 1343 const sql = "select 923 as id, 'Leto' as name"; 1272 - var row = (try c.rowOpts(sql, .{}, .{ .column_names = true })).?; 1344 + var row = (try c.rowUnsafeOpts(sql, .{}, .{ .column_names = true })).?; 1273 1345 defer row.deinit() catch {}; 1274 1346 1275 1347 try t.expectEqual(923, row.getCol(i32, "id")); ··· 1280 1352 var c = t.connect(.{}); 1281 1353 defer c.deinit(); 1282 1354 const sql = "select 'Leto'"; 1283 - var row = (try c.row(sql, .{})).?; 1355 + var row = (try c.rowUnsafe(sql, .{})).?; 1284 1356 defer row.deinit() catch {}; 1285 1357 1286 1358 var name = row.get([]u8, 0); ··· 1292 1364 var c = t.connect(.{}); 1293 1365 defer c.deinit(); 1294 1366 const sql = "select array['Leto', 'Test']::text[]"; 1295 - var row = (try c.row(sql, .{})).?; 1367 + var row = (try c.rowUnsafe(sql, .{})).?; 1296 1368 defer row.deinit() catch {}; 1297 1369 1298 1370 var values = try row.iterator([]u8, 0).alloc(t.allocator); ··· 1326 1398 1327 1399 { 1328 1400 // null, no dupe 1329 - var row = (try c.row("select 1::integer, true, 'teg', null::text, 'blue'", .{})).?; 1401 + var row = (try c.rowUnsafe("select 1::integer, true, 'teg', null::text, 'blue'", .{})).?; 1330 1402 defer row.deinit() catch {}; 1331 1403 1332 1404 const user = try row.to(User, .{}); ··· 1339 1411 1340 1412 { 1341 1413 // not null, no dupe 1342 - var row = (try c.row("select 2::integer, false, 'ghanima', 'n1', 'red'", .{})).?; 1414 + var row = (try c.rowUnsafe("select 2::integer, false, 'ghanima', 'n1', 'red'", .{})).?; 1343 1415 defer row.deinit() catch {}; 1344 1416 1345 1417 const user = try row.to(User, .{}); ··· 1352 1424 1353 1425 { 1354 1426 // null, dupe with internal arena 1355 - var row = (try c.row("select 1::integer, true, 'teg', null::text, 'red'", .{})).?; 1427 + var row = (try c.rowUnsafe("select 1::integer, true, 'teg', null::text, 'red'", .{})).?; 1356 1428 defer row.deinit() catch {}; 1357 1429 1358 1430 const user = try row.to(User, .{ .dupe = true }); ··· 1365 1437 1366 1438 { 1367 1439 // not null, dupe with internal arena 1368 - var row = (try c.row("select 2::integer, false, 'ghanima', 'n1', 'red'", .{})).?; 1440 + var row = (try c.rowUnsafe("select 2::integer, false, 'ghanima', 'n1', 'red'", .{})).?; 1369 1441 const user = try row.to(User, .{ .dupe = true }); 1370 1442 defer row.deinit() catch {}; 1371 1443 ··· 1378 1450 1379 1451 { 1380 1452 // null, dupe with explicit allocator 1381 - var row = (try c.row("select 1::integer, true, 'teg', null::text, 'red'", .{})).?; 1453 + var row = (try c.rowUnsafe("select 1::integer, true, 'teg', null::text, 'red'", .{})).?; 1382 1454 const user = try row.to(User, .{ .allocator = t.allocator }); 1383 1455 row.deinit() catch {}; 1384 1456 ··· 1392 1464 1393 1465 { 1394 1466 // not null, dupe with explicit allocator 1395 - var row = (try c.row("select 2::integer, false, 'ghanima', 'n1', 'red'", .{})).?; 1467 + var row = (try c.rowUnsafe("select 2::integer, false, 'ghanima', 'n1', 'red'", .{})).?; 1396 1468 1397 1469 const user = try row.to(User, .{ .allocator = t.allocator }); 1398 1470 row.deinit() catch {}; ··· 1421 1493 1422 1494 { 1423 1495 // null, no dupe 1424 - var row = (try c.rowOpts("select 1 as id, true as active, 'teg' as name, null as note", .{}, .{ .column_names = true })).?; 1496 + var row = (try c.rowUnsafeOpts("select 1 as id, true as active, 'teg' as name, null as note", .{}, .{ .column_names = true })).?; 1425 1497 defer row.deinit() catch {}; 1426 1498 1427 1499 const user = try row.to(User, .{ .map = .name }); ··· 1434 1506 { 1435 1507 // default values are used if no colum 1436 1508 // and extra columns are ignored 1437 - var row = (try c.rowOpts("select 2 as id, false as active, 'ghanima' as name, 'x123' as other", .{}, .{ .column_names = true })).?; 1509 + var row = (try c.rowUnsafeOpts("select 2 as id, false as active, 'ghanima' as name, 'x123' as other", .{}, .{ .column_names = true })).?; 1438 1510 defer row.deinit() catch {}; 1439 1511 1440 1512 const user = try row.to(User, .{ .map = .name }); ··· 1447 1519 { 1448 1520 // nullable fields are nulled if no column 1449 1521 // and extra columns are ignored 1450 - var row = (try c.rowOpts("select false as active, 'ghanima' as name, 'x123' as other", .{}, .{ .column_names = true })).?; 1522 + var row = (try c.rowUnsafeOpts("select false as active, 'ghanima' as name, 'x123' as other", .{}, .{ .column_names = true })).?; 1451 1523 defer row.deinit() catch {}; 1452 1524 1453 1525 const user = try row.to(User, .{ .map = .name }); ··· 1459 1531 1460 1532 { 1461 1533 // error on missing column with non-default value 1462 - var row = (try c.rowOpts("select 1 as id", .{}, .{ .column_names = true })).?; 1534 + var row = (try c.rowUnsafeOpts("select 1 as id", .{}, .{ .column_names = true })).?; 1463 1535 defer row.deinit() catch {}; 1464 1536 1465 1537 try t.expectError(error.FieldColumnMismatch, row.to(User, .{ .map = .name })); ··· 1467 1539 1468 1540 { 1469 1541 // not null, no dupe 1470 - var row = (try c.rowOpts("select 2::integer as id, false as active, 'ghanima' as name, 'n1' as note", .{}, .{ .column_names = true })).?; 1542 + var row = (try c.rowUnsafeOpts("select 2::integer as id, false as active, 'ghanima' as name, 'n1' as note", .{}, .{ .column_names = true })).?; 1471 1543 defer row.deinit() catch {}; 1472 1544 1473 1545 const user = try row.to(User, .{ .map = .name }); ··· 1479 1551 1480 1552 { 1481 1553 // null, dupe with internal arena 1482 - var row = (try c.rowOpts("select 1::integer as id, true as active, 'teg' as name, null::text as note", .{}, .{ .column_names = true })).?; 1554 + var row = (try c.rowUnsafeOpts("select 1::integer as id, true as active, 'teg' as name, null::text as note", .{}, .{ .column_names = true })).?; 1483 1555 defer row.deinit() catch {}; 1484 1556 1485 1557 const user = try row.to(User, .{ .dupe = true, .map = .name }); ··· 1491 1563 1492 1564 { 1493 1565 // not null, dupe with internal arena 1494 - var row = (try c.rowOpts("select 2::integer as id, false as active, 'ghanima' as name, 'n1' as note", .{}, .{ .column_names = true })).?; 1566 + var row = (try c.rowUnsafeOpts("select 2::integer as id, false as active, 'ghanima' as name, 'n1' as note", .{}, .{ .column_names = true })).?; 1495 1567 defer row.deinit() catch {}; 1496 1568 1497 1569 const user = try row.to(User, .{ .dupe = true, .map = .name }); ··· 1503 1575 1504 1576 { 1505 1577 // null, dupe with explicit allocator 1506 - var row = (try c.rowOpts("select 1::integer as id, true as active, 'teg' as name, null::text as note", .{}, .{ .column_names = true })).?; 1578 + var row = (try c.rowUnsafeOpts("select 1::integer as id, true as active, 'teg' as name, null::text as note", .{}, .{ .column_names = true })).?; 1507 1579 defer row.deinit() catch {}; 1508 1580 1509 1581 const user = try row.to(User, .{ .allocator = t.allocator, .map = .name }); ··· 1516 1588 1517 1589 { 1518 1590 // not null, dupe with explicit allocator 1519 - var row = (try c.rowOpts("select 5::integer as id, false as active, 'ghanima' as name, 'n1' as note", .{}, .{ .column_names = true })).?; 1591 + var row = (try c.rowUnsafeOpts("select 5::integer as id, false as active, 'ghanima' as name, 'n1' as note", .{}, .{ .column_names = true })).?; 1520 1592 defer row.deinit() catch {}; 1521 1593 1522 1594 const user = try row.to(User, .{ .allocator = t.allocator, .map = .name }); ··· 1621 1693 defer c.deinit(); 1622 1694 1623 1695 { 1624 - var row = (try c.row("select array[1, 99]::integer[], null", .{})).?; 1696 + var row = (try c.rowUnsafe("select array[1, 99]::integer[], null", .{})).?; 1625 1697 defer row.deinit() catch {}; 1626 1698 1627 1699 const user = try row.to(User, .{}); ··· 1630 1702 } 1631 1703 1632 1704 { 1633 - var row = (try c.row("select array[0]::integer[], array['over', '9000']::text[]", .{})).?; 1705 + var row = (try c.rowUnsafe("select array[0]::integer[], array['over', '9000']::text[]", .{})).?; 1634 1706 const user = try row.to(User, .{ .allocator = t.allocator }); 1635 1707 row.deinit() catch {}; 1636 1708 ··· 1649 1721 \\ select array[1]::integer[], array['9000']::text[] 1650 1722 , .{}); 1651 1723 1652 - const user1 = try (try result.next()).?.to(User, .{ .dupe = true }); 1653 - const user2 = try (try result.next()).?.to(User, .{ .dupe = true }); 1654 - try t.expectEqual(null, try result.next()); 1724 + const user1 = try (try result.nextUnsafe()).?.to(User, .{ .dupe = true }); 1725 + const user2 = try (try result.nextUnsafe()).?.to(User, .{ .dupe = true }); 1726 + try t.expectEqual(null, try result.nextUnsafe()); 1655 1727 defer result.deinit(); 1656 1728 1657 1729 try t.expectSlice(i32, &.{0}, try user1.parents.alloc(t.arena.allocator())); ··· 1669 1741 \\ select array[1]::integer[], array['9000']::text[] 1670 1742 , .{}); 1671 1743 1672 - const user1 = try (try result.next()).?.to(User, .{ .allocator = t.allocator }); 1673 - const user2 = try (try result.next()).?.to(User, .{ .allocator = t.allocator }); 1674 - try t.expectEqual(null, try result.next()); 1744 + const user1 = try (try result.nextUnsafe()).?.to(User, .{ .allocator = t.allocator }); 1745 + const user2 = try (try result.nextUnsafe()).?.to(User, .{ .allocator = t.allocator }); 1746 + try t.expectEqual(null, try result.nextUnsafe()); 1675 1747 result.deinit(); 1676 1748 1677 1749 defer user1.tags.?.deinit(t.allocator); ··· 1705 1777 defer c.deinit(); 1706 1778 1707 1779 { 1708 - var row = (try c.row("select array[1, 99]::integer[], array['over', '9000']::text[], array['red', 'green']::text[]", .{})).?; 1780 + var row = (try c.rowUnsafe("select array[1, 99]::integer[], array['over', '9000']::text[], array['red', 'green']::text[]", .{})).?; 1709 1781 const user = try row.to(User, .{ .allocator = t.allocator }); 1710 1782 row.deinit() catch {}; 1711 1783 ··· 1722 1794 } 1723 1795 1724 1796 { 1725 - var row = (try c.row("select array[1, 99]::integer[], null::text[], null::text[]", .{})).?; 1797 + var row = (try c.rowUnsafe("select array[1, 99]::integer[], null::text[], null::text[]", .{})).?; 1726 1798 const user = try row.to(User, .{ .allocator = t.allocator }); 1727 1799 row.deinit() catch {}; 1728 1800 ··· 1732 1804 try t.expectSlice(i32, &.{ 1, 99 }, user.parents); 1733 1805 try t.expectEqual(null, user.tags); 1734 1806 try t.expectEqual(null, user.choices); 1807 + } 1808 + } 1809 + 1810 + test "Result: safe" { 1811 + var c = t.connect(.{}); 1812 + defer c.deinit(); 1813 + const sql = "select $1::int, $2::int"; 1814 + 1815 + { 1816 + var result = try c.query(sql, .{ @as(?i32, 321), @as(?i32, null) }); 1817 + defer result.deinit(); 1818 + const row = (try result.next()).?; 1819 + try t.expectEqual(321, try row.get(i32, 0)); 1820 + try t.expectEqual(error.InvalidType, row.get(bool, 0)); 1821 + 1822 + try t.expectEqual(321, try row.get(?i32, 0)); 1823 + try t.expectEqual(null, try row.get(?i32, 1)); 1824 + try t.expectEqual(null, result.next()); 1735 1825 } 1736 1826 } 1737 1827
+1 -1
src/t.zig
··· 234 234 var result = c.query(sql, .{}) catch unreachable; 235 235 defer result.deinit(); 236 236 237 - const row = (result.next() catch unreachable).?; 237 + const row = (result.nextUnsafe() catch unreachable).?; 238 238 const value = row.get(i32, 0); 239 239 result.drain() catch unreachable; 240 240 return value;
+57 -21
src/types.zig
··· 42 42 return buf.writeByte(value); 43 43 } 44 44 45 - pub fn decode(data: []const u8, data_oid: i32) u8 { 46 - lib.assertDecodeType(u8, &.{Char.oid.decimal}, data_oid); 45 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) u8 else lib.TypeError!u8 { 46 + lib.verifyDecodeType(fail_mode, u8, &.{Char.oid.decimal}, data_oid) catch |err| { 47 + if (comptime fail_mode == .unsafe) unreachable; 48 + return err; 49 + }; 47 50 return data[0]; 48 51 } 49 52 ··· 67 70 return Int16.encode(@intCast(value), buf, format_pos); 68 71 } 69 72 70 - pub fn decode(data: []const u8, data_oid: i32) i16 { 71 - lib.assertDecodeType(i16, &.{Int16.oid.decimal}, data_oid); 73 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) i16 else lib.TypeError!i16 { 74 + lib.verifyDecodeType(fail_mode, i16, &.{Int16.oid.decimal}, data_oid) catch |err| { 75 + if (comptime fail_mode == .unsafe) unreachable; 76 + return err; 77 + }; 72 78 return Int16.decodeKnown(data); 73 79 } 74 80 ··· 92 98 return Int32.encode(@intCast(value), buf, format_pos); 93 99 } 94 100 95 - pub fn decode(data: []const u8, data_oid: i32) i32 { 96 - lib.assertDecodeType(i32, &.{ Int32.oid.decimal, Xid.oid.decimal }, data_oid); 101 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) i32 else lib.TypeError!i32 { 102 + lib.verifyDecodeType(fail_mode, i32, &.{ Int32.oid.decimal, Xid.oid.decimal }, data_oid) catch |err| { 103 + if (comptime fail_mode == .unsafe) unreachable; 104 + return err; 105 + }; 97 106 return Int32.decodeKnown(data); 98 107 } 99 108 ··· 117 126 return Int64.encode(@intCast(value), buf, format_pos); 118 127 } 119 128 120 - pub fn decode(data: []const u8, data_oid: i32) i64 { 129 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) i64 else lib.TypeError!i64 { 121 130 switch (data_oid) { 122 131 Timestamp.oid.decimal, TimestampTz.oid.decimal => return Timestamp.decodeKnown(data), 123 132 else => { 124 - lib.assertDecodeType(i64, &.{ Int64.oid.decimal, PgLSN.oid.decimal, Xid8.oid.decimal }, data_oid); 133 + lib.verifyDecodeType(fail_mode, i64, &.{ Int64.oid.decimal, PgLSN.oid.decimal, Xid8.oid.decimal }, data_oid) catch |err| { 134 + if (comptime fail_mode == .unsafe) unreachable; 135 + return err; 136 + }; 125 137 return Int64.decodeKnown(data); 126 138 }, 127 139 } ··· 143 155 return buf.writeIntBig(i64, value - us_from_epoch_to_y2k); 144 156 } 145 157 146 - pub fn decode(data: []const u8, data_oid: i32) i64 { 147 - lib.assertDecodeType(i64, &.{ Timestamp.oid.decimal, TimestampTz.oid.decimal }, data_oid); 158 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) i64 else lib.TypeError!i64 { 159 + lib.verifyDecodeType(fail_mode, i64, &.{ Timestamp.oid.decimal, TimestampTz.oid.decimal }, data_oid) catch |err| { 160 + if (comptime fail_mode == .unsafe) unreachable; 161 + return err; 162 + }; 148 163 return std.mem.readInt(i64, data[0..8], .big) + us_from_epoch_to_y2k; 149 164 } 150 165 ··· 169 184 return buf.writeIntBig(i32, tmp.*); 170 185 } 171 186 172 - pub fn decode(data: []const u8, data_oid: i32) f32 { 173 - lib.assertDecodeType(f32, &.{Float32.oid.decimal}, data_oid); 187 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) f32 else lib.TypeError!f32 { 188 + lib.verifyDecodeType(fail_mode, f32, &.{Float32.oid.decimal}, data_oid) catch |err| { 189 + if (comptime fail_mode == .unsafe) unreachable; 190 + return err; 191 + }; 174 192 return Float32.decodeKnown(data); 175 193 } 176 194 ··· 194 212 return buf.writeIntBig(i64, tmp.*); 195 213 } 196 214 197 - pub fn decode(data: []const u8, data_oid: i32) f64 { 215 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) f64 else lib.TypeError!f64 { 198 216 switch (data_oid) { 199 - Numeric.oid.decimal => return Numeric.decode(data, data_oid).toFloat(), 217 + Numeric.oid.decimal => { 218 + const numeric = Numeric.decode(fail_mode, data, data_oid); 219 + if (comptime fail_mode == .unsafe) { 220 + return numeric.toFloat(); 221 + } 222 + return (try numeric).toFloat(); 223 + }, 200 224 else => { 201 - lib.assertDecodeType(f64, &.{Float64.oid.decimal}, data_oid); 225 + lib.verifyDecodeType(fail_mode, f64, &.{Float64.oid.decimal}, data_oid) catch |err| { 226 + if (comptime fail_mode == .unsafe) unreachable; 227 + return err; 228 + }; 202 229 return Float64.decodeKnown(data); 203 230 }, 204 231 } ··· 221 248 return buf.writeByte(if (value) 1 else 0); 222 249 } 223 250 224 - pub fn decode(data: []const u8, data_oid: i32) bool { 225 - lib.assertDecodeType(bool, &.{Bool.oid.decimal}, data_oid); 251 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) bool else lib.TypeError!bool { 252 + lib.verifyDecodeType(fail_mode, bool, &.{Bool.oid.decimal}, data_oid) catch |err| { 253 + if (comptime fail_mode == .unsafe) unreachable; 254 + return err; 255 + }; 226 256 return decodeKnown(data); 227 257 } 228 258 ··· 290 320 } 291 321 } 292 322 293 - pub fn decode(data: []const u8, data_oid: i32) []const u8 { 294 - lib.assertDecodeType([]const u8, &.{UUID.oid.decimal}, data_oid); 323 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) []const u8 else lib.TypeError![]const u8 { 324 + lib.verifyDecodeType(fail_mode, []const u8, &.{UUID.oid.decimal}, data_oid) catch |err| { 325 + if (comptime fail_mode == .unsafe) unreachable; 326 + return err; 327 + }; 295 328 return data; 296 329 } 297 330 ··· 455 488 Encode.variableLengthFill(buf, state); 456 489 } 457 490 458 - fn decode(data: []const u8, data_oid: i32) []const u8 { 459 - lib.assertDecodeType([]const u8, &.{JSONB.oid.decimal}, data_oid); 491 + fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) []const u8 else lib.TypeError![]const u8 { 492 + lib.verifyDecodeType(fail_mode, []const u8, &.{JSONB.oid.decimal}, data_oid) catch |err| { 493 + if (comptime fail_mode == .unsafe) unreachable; 494 + return err; 495 + }; 460 496 return JSONB.decodeKnown(data); 461 497 } 462 498
+6 -3
src/types/cidr.zig
··· 17 17 v6, 18 18 }; 19 19 20 - pub fn decode(data: []const u8, data_oid: i32) Cidr { 21 - lib.assertDecodeType(Cidr, &.{ Cidr.oid.decimal, Cidr.inet_oid.decimal }, data_oid); 22 - lib.assert(data.len == 8 or data.len == 20); 20 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) Cidr else lib.TypeError!Cidr { 21 + lib.verifyDecodeType(fail_mode, Cidr, &.{ Cidr.oid.decimal, Cidr.inet_oid.decimal }, data_oid) catch |err| { 22 + if (fail_mode == .unsafe) unreachable; 23 + return err; 24 + }; 23 25 26 + lib.assert(data.len == 8 or data.len == 20); 24 27 return decodeKnown(data); 25 28 } 26 29
+6 -2
src/types/numeric.zig
··· 97 97 return encodeValidString(stream.getWritten(), buf); 98 98 } 99 99 100 - pub fn decode(data: []const u8, data_oid: i32) Numeric { 101 - lib.assertDecodeType(Numeric, &.{Numeric.oid.decimal}, data_oid); 100 + pub fn decode(comptime fail_mode: lib.FailMode, data: []const u8, data_oid: i32) if (fail_mode == .unsafe) Numeric else lib.TypeError!Numeric { 101 + lib.verifyDecodeType(fail_mode, Numeric, &.{Numeric.oid.decimal}, data_oid) catch |err| { 102 + if (fail_mode == .unsafe) unreachable; 103 + return err; 104 + }; 105 + 102 106 lib.assert(data.len >= 8); 103 107 return decodeKnown(data); 104 108 }