Native PostgreSQL driver / client for Zig
0

Configure Feed

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

Support for binding and reading arrays of nullables, e.g. []?i32

https://github.com/karlseguin/pg.zig/issues/95

Karl Seguin (Oct 2, 2025, 7:37 PM +0800) eb00ff62 bbfb0a44

+430 -153
+9 -8
readme.md
··· 225 225 ### Array Columns 226 226 Use `row.get(pg.Iterator(i32))` to return an [Iterator](#iteratort) over an array column. Supported array types are: 227 227 228 - * `u8` - `char[]` 229 - * `i16` - `smallint[]` 230 - * `i32` - `int[]` 231 - * `i64` - `bigint[]` or `timestamp(tz)[]` (see `get`) 232 - * `f32` - `float4` 233 - * `f64` - `float8` 234 - * `bool` - `bool[]` 235 - * `[]const u8` - More strict than `get([]u8)`). Supports: `text[]`, `char(n)[]`, `bytea[]`, `uuid[]`, `json[]` and `jsonb[]` 228 + * `u8` and `?u8` - `char[]` 229 + * `i16` and `?i16` - `smallint[]` 230 + * `i32` and `?i32` - `int[]` 231 + * `i64` and `?i64` - `bigint[]` or `timestamp(tz)[]` (see `get`) 232 + * `f32` and `?f32` - `float4` 233 + * `f64` and `?f64` - `float8` 234 + * `bool` and `?bool` - `bool[]` 235 + * `[]const u8` and `[]?const u8` - More strict than `get([]u8)`). Supports: `text[]`, `char(n)[]`, `bytea[]`, `uuid[]`, `json[]` and `jsonb[]` 236 236 * `[]u8` - Same as `[]const u8` but returns mutable value. 237 237 * `pg.Numeric` - See numeric section 238 238 * `pg.Cidr` - See CIDR/INET section ··· 274 274 275 275 ### Fields 276 276 * `len` - the number of values in the iterator 277 + * `is_null` - Whether the array itself was null 277 278 278 279 ### alloc(it: Iterator(T), allocator: std.mem.Allocator) ![]T 279 280 Allocates a slice and populates it with all values.
+142 -20
src/conn.zig
··· 1214 1214 1215 1215 const row = (try result.next()) orelse unreachable; 1216 1216 try t.expectEqual(null, row.get(?i16, 1)); 1217 - try t.expectEqual(null, row.iterator(?i16, 2)); 1217 + try t.expectEqual(true, row.iterator(i16, 2).is_null); 1218 1218 1219 1219 try t.expectEqual(null, row.get(?i32, 3)); 1220 - try t.expectEqual(null, row.iterator(?i32, 4)); 1220 + try t.expectEqual(true, row.iterator(i32, 4).is_null); 1221 1221 1222 1222 try t.expectEqual(null, row.get(?i64, 5)); 1223 - try t.expectEqual(null, row.iterator(?i64, 6)); 1223 + try t.expectEqual(true, row.iterator(i64, 6).is_null); 1224 1224 1225 1225 try t.expectEqual(null, row.get(?f32, 7)); 1226 - try t.expectEqual(null, row.iterator(?f32, 8)); 1226 + try t.expectEqual(true, row.iterator(f32, 8).is_null); 1227 1227 1228 1228 try t.expectEqual(null, row.get(?f64, 9)); 1229 - try t.expectEqual(null, row.iterator(?f64, 10)); 1229 + try t.expectEqual(true, row.iterator(f64, 10).is_null); 1230 1230 1231 1231 try t.expectEqual(null, row.get(?bool, 11)); 1232 - try t.expectEqual(null, row.iterator(?bool, 12)); 1232 + try t.expectEqual(true, row.iterator(bool, 12).is_null); 1233 1233 1234 1234 try t.expectEqual(null, row.get(?[]u8, 13)); 1235 - try t.expectEqual(null, row.iterator(?[]u8, 14)); 1235 + try t.expectEqual(true, row.iterator([]u8, 14).is_null); 1236 1236 1237 1237 try t.expectEqual(null, row.get(?[]const u8, 15)); 1238 - try t.expectEqual(null, row.iterator(?[]const u8, 16)); 1238 + try t.expectEqual(true, row.iterator([]const u8, 16).is_null); 1239 1239 1240 1240 try t.expectEqual(null, row.get(?[]const u8, 17)); 1241 - try t.expectEqual(null, row.iterator(?[]const u8, 18)); 1241 + try t.expectEqual(true, row.iterator([]const u8, 18).is_null); 1242 1242 1243 1243 try t.expectEqual(null, row.get(?[]u8, 19)); 1244 - try t.expectEqual(null, row.iterator(?[]const u8, 20)); 1244 + try t.expectEqual(true, row.iterator([]const u8, 20).is_null); 1245 1245 1246 1246 try t.expectEqual(null, row.get(?[]u8, 21)); 1247 1247 try t.expectEqual(null, row.get(?f64, 21)); 1248 1248 1249 1249 try t.expectEqual(null, row.get(?i64, 23)); 1250 - try t.expectEqual(null, row.iterator(?i64, 24)); 1250 + try t.expectEqual(true, row.iterator(i64, 24).is_null); 1251 1251 1252 1252 try t.expectEqual(null, row.get(?[]u8, 25)); 1253 - try t.expectEqual(null, row.iterator(?[]const u8, 26)); 1253 + try t.expectEqual(true, row.iterator([]const u8, 26).is_null); 1254 1254 1255 1255 try t.expectEqual(null, row.get(?[]u8, 27)); 1256 - try t.expectEqual(null, row.iterator(?[]const u8, 28)); 1256 + try t.expectEqual(true, row.iterator([]const u8, 28).is_null); 1257 1257 1258 1258 try t.expectEqual(null, row.get(?u8, 29)); 1259 - try t.expectEqual(null, row.iterator(?u8, 30)); 1259 + try t.expectEqual(true, row.iterator(u8, 30).is_null); 1260 1260 1261 1261 try t.expectEqual(null, row.get(?u8, 31)); 1262 - try t.expectEqual(null, row.iterator(?u8, 32)); 1262 + try t.expectEqual(true, row.iterator(u8, 32).is_null); 1263 1263 1264 1264 try t.expectEqual(null, try result.next()); 1265 1265 } ··· 1550 1550 try t.expectString("hello", row.get([]u8, 1)); 1551 1551 } 1552 1552 1553 + test "PG: bind []?i64" { 1554 + defer t.reset(); 1555 + 1556 + var c = t.connect(.{}); 1557 + defer c.deinit(); 1558 + const values = [_]?i64{ 1, null, 3 }; 1559 + 1560 + { 1561 + const result = c.exec("insert into all_types (id, col_int8_arr) values ($1, $2)", .{ 7, values }); 1562 + if (result) |affected| { 1563 + try t.expectEqual(1, affected); 1564 + } else |err| { 1565 + try t.fail(c, err); 1566 + } 1567 + } 1568 + 1569 + var result = try c.query("select id, col_int8_arr from all_types where id = $1", .{7}); 1570 + defer result.deinit(); 1571 + 1572 + const row = (try result.next()) orelse unreachable; 1573 + try t.expectEqual(7, row.get(i32, 0)); 1574 + { 1575 + const arr = try row.iterator(?i64, 1).alloc(t.arena.allocator()); 1576 + try t.expectEqual(3, arr.len); 1577 + try t.expectEqual(1, arr[0]); 1578 + try t.expectEqual(null, arr[1]); 1579 + try t.expectEqual(3, arr[2]); 1580 + } 1581 + } 1582 + 1583 + test "PG: bind []?f64" { 1584 + defer t.reset(); 1585 + 1586 + var c = t.connect(.{}); 1587 + defer c.deinit(); 1588 + const values = [_]?f64{ null, null, 0.2, null }; 1589 + 1590 + { 1591 + const result = c.exec("insert into all_types (id, col_float8_arr) values ($1, $2)", .{ 8, values }); 1592 + if (result) |affected| { 1593 + try t.expectEqual(1, affected); 1594 + } else |err| { 1595 + try t.fail(c, err); 1596 + } 1597 + } 1598 + 1599 + var result = try c.query("select id, col_float8_arr from all_types where id = $1", .{8}); 1600 + defer result.deinit(); 1601 + 1602 + const row = (try result.next()) orelse unreachable; 1603 + try t.expectEqual(8, row.get(i32, 0)); 1604 + { 1605 + const arr = try row.iterator(?f64, 1).alloc(t.arena.allocator()); 1606 + try t.expectEqual(4, arr.len); 1607 + try t.expectEqual(null, arr[0]); 1608 + try t.expectEqual(null, arr[1]); 1609 + try t.expectEqual(0.2, arr[2]); 1610 + try t.expectEqual(null, arr[3]); 1611 + } 1612 + } 1613 + 1614 + test "PG: bind []?bool" { 1615 + defer t.reset(); 1616 + 1617 + var c = t.connect(.{}); 1618 + defer c.deinit(); 1619 + const values = [_]?bool{ null, true, false, null }; 1620 + 1621 + { 1622 + const result = c.exec("insert into all_types (id, col_bool_arr) values ($1, $2)", .{ 9, values }); 1623 + if (result) |affected| { 1624 + try t.expectEqual(1, affected); 1625 + } else |err| { 1626 + try t.fail(c, err); 1627 + } 1628 + } 1629 + 1630 + var result = try c.query("select id, col_bool_arr from all_types where id = $1", .{9}); 1631 + defer result.deinit(); 1632 + 1633 + const row = (try result.next()) orelse unreachable; 1634 + try t.expectEqual(9, row.get(i32, 0)); 1635 + { 1636 + const arr = try row.iterator(?bool, 1).alloc(t.arena.allocator()); 1637 + try t.expectEqual(4, arr.len); 1638 + try t.expectEqual(null, arr[0]); 1639 + try t.expectEqual(true, arr[1]); 1640 + try t.expectEqual(false, arr[2]); 1641 + try t.expectEqual(null, arr[3]); 1642 + } 1643 + } 1644 + 1645 + test "PG: bind []?[]const u8" { 1646 + defer t.reset(); 1647 + 1648 + var c = t.connect(.{}); 1649 + defer c.deinit(); 1650 + const values = [_]?[]const u8{ "hello", null, null }; 1651 + 1652 + { 1653 + const result = c.exec("insert into all_types (id, col_text_arr) values ($1, $2)", .{ 10, values }); 1654 + if (result) |affected| { 1655 + try t.expectEqual(1, affected); 1656 + } else |err| { 1657 + try t.fail(c, err); 1658 + } 1659 + } 1660 + 1661 + var result = try c.query("select id, col_text_arr from all_types where id = $1", .{10}); 1662 + defer result.deinit(); 1663 + 1664 + const row = (try result.next()) orelse unreachable; 1665 + try t.expectEqual(10, row.get(i32, 0)); 1666 + { 1667 + const arr = try row.iterator(?[]const u8, 1).alloc(t.arena.allocator()); 1668 + try t.expectEqual(3, arr.len); 1669 + try t.expectString("hello", arr[0].?); 1670 + try t.expectEqual(null, arr[1]); 1671 + try t.expectEqual(null, arr[2]); 1672 + } 1673 + } 1674 + 1553 1675 test "PG: binary wrapper" { 1554 1676 defer t.reset(); 1555 1677 ··· 1565 1687 , .{}); 1566 1688 1567 1689 const data = lib.Binary{ 1568 - .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 + .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 }, 1569 1691 }; 1570 1692 var row = (try c.row("select $1::geography", .{data})).?; 1571 1693 defer row.deinit() catch {}; ··· 1579 1701 defer c.deinit(); 1580 1702 1581 1703 { 1582 - try t.expectError(error.PG, c.exec("insert into all_types (id, id) values ($1)", .{ 7, null })); 1704 + try t.expectError(error.PG, c.exec("insert into all_types (id, id) values ($1)", .{ 999, null })); 1583 1705 try t.expectEqual(false, c.err.?.isUnique()); 1584 1706 } 1585 1707 1586 1708 { 1587 - _ = try c.exec("insert into all_types (id) values ($1)", .{7}); 1588 - _ = try t.expectError(error.PG, c.exec("insert into all_types (id) values ($1)", .{7})); 1709 + _ = try c.exec("insert into all_types (id) values ($1)", .{999}); 1710 + _ = try t.expectError(error.PG, c.exec("insert into all_types (id) values ($1)", .{999})); 1589 1711 try t.expectEqual(true, c.err.?.isUnique()); 1590 1712 } 1591 1713 1592 1714 { 1593 1715 // can still use the connection after the error 1594 - _ = try t.expectError(error.PG, c.row("insert into all_types (id) values ($1) returning id", .{7})); 1716 + _ = try t.expectError(error.PG, c.row("insert into all_types (id) values ($1) returning id", .{999})); 1595 1717 try t.expectEqual(true, c.err.?.isUnique()); 1596 1718 } 1597 1719 }
+45 -43
src/result.zig
··· 280 280 return self.get(T, col.?); 281 281 } 282 282 283 - pub fn iterator(self: *const Row, comptime T: type, col: usize) IteratorReturnType(T) { 283 + pub fn iterator(self: *const Row, comptime T: type, col: usize) Iterator(T) { 284 284 const value = self.values[col]; 285 - const TT = switch (@typeInfo(T)) { 286 - .optional => |opt| if (value.is_null) return null else opt.child, 287 - else => T, 288 - }; 289 - return Iterator(TT).fromPgzRow(value.data, self.oids[col]) catch @panic("Could not get iterator of type " ++ @typeName(T) ++ " for row."); 285 + if (value.is_null) { 286 + return Iterator(T).asNull(); 287 + } 288 + return Iterator(T).fromPgzRow(value.data, self.oids[col]) catch @panic("Could not get iterator of type " ++ @typeName(T) ++ " for row."); 290 289 } 291 290 292 - pub fn iteratorCol(self: *const Row, comptime T: type, name: []const u8) IteratorReturnType(T) { 291 + pub fn iteratorCol(self: *const Row, comptime T: type, name: []const u8) Iterator(T) { 293 292 const col = self._result.columnIndex(name); 294 293 lib.assertColumnName(name, col != null); 295 294 return self.iterator(T, col.?); ··· 451 450 return self.row.getCol(T, name); 452 451 } 453 452 454 - pub fn iterator(self: *const QueryRow, comptime T: type, col: usize) IteratorReturnType(T) { 453 + pub fn iterator(self: *const QueryRow, comptime T: type, col: usize) Iterator(T) { 455 454 return self.row.iterator(T, col); 456 455 } 457 456 458 - pub fn iteratorCol(self: *const QueryRow, comptime T: type, name: []const u8) IteratorReturnType(T) { 457 + pub fn iteratorCol(self: *const QueryRow, comptime T: type, name: []const u8) Iterator(T) { 459 458 return self.row.iteratorCol(T, name); 460 459 } 461 460 ··· 477 476 } 478 477 }; 479 478 480 - fn IteratorReturnType(comptime T: type) type { 481 - return switch (@typeInfo(T)) { 482 - .optional => |opt| ?Iterator(opt.child), 483 - else => Iterator(T), 484 - }; 485 - } 486 - 487 479 pub fn Iterator(comptime T: type) type { 488 480 return struct { 481 + is_null: bool, 489 482 _len: usize, 490 483 _pos: usize, 491 484 _data: []const u8, ··· 504 497 return self._len; 505 498 } 506 499 500 + fn asNull() Self { 501 + return .{ 502 + .is_null = true, 503 + ._len = 0, 504 + ._pos = 0, 505 + ._data = &.{}, 506 + ._decoder = struct { 507 + fn noop(_: []const u8) ItemType() { 508 + unreachable; 509 + } 510 + }.noop, 511 + }; 512 + } 513 + 507 514 // used internally by row.get(Iterator(T)) 508 515 fn fromPgzRow(data: []const u8, oid: i32) !Self { 509 516 const TT = switch (@typeInfo(T)) { ··· 569 576 }; 570 577 571 578 if (data.len == 12) { 572 - // we have an empty 579 + // we have an empty array 573 580 return .{ 581 + .is_null = false, 574 582 ._len = 0, 575 583 ._pos = 0, 576 584 ._data = &[_]u8{}, ··· 584 592 lib.assert(dimensions == 1); 585 593 586 594 const has_nulls = std.mem.readInt(i32, data[4..8][0..4], .big); 587 - lib.assert(has_nulls == 0); 595 + lib.assert(has_nulls == 0 or @typeInfo(T) == .optional); 588 596 589 597 // const oid = std.mem.readInt(i32, data[8..12][0..4], .big); 590 598 const l = std.mem.readInt(i32, data[12..16][0..4], .big); 591 599 // const lower_bound = std.mem.readInt(i32, data[16..20][0..4], .big); 592 600 593 601 return .{ 602 + .is_null = false, 594 603 ._len = @intCast(l), 595 604 ._pos = 0, 596 605 ._data = data[20..], ··· 600 609 601 610 pub fn pgzMoveOwner(self: Self, allocator: Allocator) !Self { 602 611 return .{ 612 + .is_null = false, 603 613 ._len = self._len, 604 614 ._pos = self._pos, 605 615 ._data = try allocator.dupe(u8, self._data), ··· 651 661 // TODO: for fixed length types, we don't need to decode the length 652 662 const len_end = pos + 4; 653 663 const data_len = std.mem.readInt(i32, data[pos..len_end][0..4], .big); 654 - pos = len_end + @as(usize, @intCast(data_len)); 655 - if (comptime should_dupe and (T == []u8 or T == []const u8)) { 656 - into[i] = try allocator.dupe(u8, decoder(data[len_end..pos])); 664 + 665 + if ((comptime @typeInfo(T) == .optional) and data_len == -1) { 666 + pos = len_end; 667 + into[i] = null; 657 668 } else { 658 - into[i] = decoder(data[len_end..pos]); 669 + pos = len_end + @as(usize, @intCast(data_len)); 670 + if (comptime should_dupe and (T == []u8 or T == []const u8)) { 671 + into[i] = try allocator.dupe(u8, decoder(data[len_end..pos])); 672 + } else { 673 + into[i] = decoder(data[len_end..pos]); 674 + } 659 675 } 660 676 } 661 677 } ··· 1082 1098 1083 1099 var row = (try result.next()).?; 1084 1100 1085 - const iterator = row.iterator(?i32, 0); 1086 - try t.expectEqual(null, iterator); 1101 + var iterator = row.iterator(i32, 0); 1102 + try t.expectEqual(true, iterator.is_null); 1103 + try t.expectEqual(null, iterator.next()); 1087 1104 try result.drain(); 1088 1105 } 1089 1106 ··· 1094 1111 1095 1112 var row = (try result.next()).?; 1096 1113 1097 - const iterator = row.iterator(?[]u8, 0); 1098 - try t.expectEqual(null, iterator); 1114 + var iterator = row.iterator([]u8, 0); 1115 + try t.expectEqual(true, iterator.is_null); 1116 + try t.expectEqual(null, iterator.next()); 1099 1117 try result.drain(); 1100 1118 } 1101 1119 } ··· 1121 1139 const v3 = try row.iterator(i64, 2).alloc(t.allocator); 1122 1140 defer t.allocator.free(v3); 1123 1141 try t.expectSlice(i64, &.{ 944949338498392, -2 }, v3); 1124 - 1125 - // row 1, but fetch it as a nullable 1126 - const v4 = try row.iterator(?i16, 0).?.alloc(t.allocator); 1127 - defer t.allocator.free(v4); 1128 - try t.expectSlice(i16, &.{ -303, 9449, 2 }, v4); 1129 1142 } 1130 1143 1131 1144 test "Result: float[]" { ··· 1193 1206 try t.expectString(&arr1, v2[0]); 1194 1207 try t.expectString(&arr2, v2[1]); 1195 1208 try t.expectEqual(2, v2.len); 1196 - 1197 - // column 0 but fetched as nullable 1198 - const v3 = try row.iterator(?[]u8, 0).?.alloc(t.allocator); 1199 - defer { 1200 - t.allocator.free(v3[0]); 1201 - t.allocator.free(v3[1]); 1202 - t.allocator.free(v3); 1203 - } 1204 - try t.expectString("over", v3[0]); 1205 - try t.expectString("9000", v3[1]); 1206 - try t.expectEqual(2, v3.len); 1207 1209 } 1208 1210 1209 1211 test "Result: text[] alloc dupes" { ··· 1256 1258 var c = t.connect(.{}); 1257 1259 defer c.deinit(); 1258 1260 const sql = "select $1::pg_lsn + 1"; 1259 - var result = try c.query(sql, .{ 32788447688 }); 1261 + var result = try c.query(sql, .{32788447688}); 1260 1262 defer result.deinit(); 1261 1263 1262 1264 const row = (try result.next()).?;
+205 -72
src/types.zig
··· 93 93 } 94 94 95 95 pub fn decode(data: []const u8, data_oid: i32) i32 { 96 - lib.assertDecodeType(i32, &.{Int32.oid.decimal, Xid.oid.decimal}, data_oid); 96 + lib.assertDecodeType(i32, &.{ Int32.oid.decimal, Xid.oid.decimal }, data_oid); 97 97 return Int32.decodeKnown(data); 98 98 } 99 99 ··· 121 121 switch (data_oid) { 122 122 Timestamp.oid.decimal, TimestampTz.oid.decimal => return Timestamp.decodeKnown(data), 123 123 else => { 124 - lib.assertDecodeType(i64, &.{Int64.oid.decimal, PgLSN.oid.decimal, Xid8.oid.decimal}, data_oid); 124 + lib.assertDecodeType(i64, &.{ Int64.oid.decimal, PgLSN.oid.decimal, Xid8.oid.decimal }, data_oid); 125 125 return Int64.decodeKnown(data); 126 126 }, 127 127 } ··· 165 165 fn encode(value: f32, buf: *buffer.Buffer, format_pos: usize) !void { 166 166 buf.writeAt(Float32.encoding, format_pos); 167 167 try buf.write(&.{ 0, 0, 0, 4 }); // length of our data 168 - const tmp: *i32 = @constCast(@ptrCast(&value)); 168 + const tmp: *i32 = @ptrCast(@constCast(&value)); 169 169 return buf.writeIntBig(i32, tmp.*); 170 170 } 171 171 ··· 176 176 177 177 pub fn decodeKnown(data: []const u8) f32 { 178 178 const n = std.mem.readInt(i32, data[0..4], .big); 179 - const tmp: *f32 = @constCast(@ptrCast(&n)); 179 + const tmp: *f32 = @ptrCast(@constCast(&n)); 180 180 return tmp.*; 181 181 } 182 182 }; ··· 190 190 191 191 try buf.write(&.{ 0, 0, 0, 8 }); // length of our data 192 192 // not sure if this is the best option... 193 - const tmp: *i64 = @constCast(@ptrCast(&value)); 193 + const tmp: *i64 = @ptrCast(@constCast(&value)); 194 194 return buf.writeIntBig(i64, tmp.*); 195 195 } 196 196 ··· 206 206 207 207 pub fn decodeKnown(data: []const u8) f64 { 208 208 const n = std.mem.readInt(i64, data[0..8], .big); 209 - const tmp: *f64 = @constCast(@ptrCast(&n)); 209 + const tmp: *f64 = @ptrCast(@constCast(&n)); 210 210 return tmp.*; 211 211 } 212 212 }; ··· 474 474 pub const oid = OID.make(1005); 475 475 const encoding = &binary_encoding; 476 476 477 - fn encode(values: []const i16, buf: *buffer.Buffer, oid_pos: usize) !void { 477 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 478 478 buf.writeAt(&Int16.oid.encoded, oid_pos); 479 - return Encode.writeIntArray(i16, 2, values, buf); 479 + return Encode.writeIntArray(i16, values, buf); 480 480 } 481 481 482 - fn encodeUnsigned(values: []const u16, buf: *buffer.Buffer, oid_pos: usize) !void { 482 + fn encodeUnsigned(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 483 483 for (values) |v| { 484 484 if (v > 32767) return error.UnsignedIntWouldBeTruncated; 485 485 } 486 486 buf.writeAt(&Int16.oid.encoded, oid_pos); 487 - return Encode.writeIntArray(i16, 2, values, buf); 487 + return Encode.writeIntArray(i16, values, buf); 488 488 } 489 489 }; 490 490 ··· 492 492 pub const oid = OID.make(1007); 493 493 const encoding = &binary_encoding; 494 494 495 - fn encode(values: []const i32, buf: *buffer.Buffer, oid_pos: usize) !void { 495 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 496 496 buf.writeAt(&Int32.oid.encoded, oid_pos); 497 - return Encode.writeIntArray(i32, 4, values, buf); 497 + return Encode.writeIntArray(i32, values, buf); 498 498 } 499 499 500 - fn encodeUnsigned(values: []const u32, buf: *buffer.Buffer, oid_pos: usize) !void { 500 + fn encodeUnsigned(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 501 501 for (values) |v| { 502 502 if (v > 2147483647) return error.UnsignedIntWouldBeTruncated; 503 503 } 504 504 buf.writeAt(&Int32.oid.encoded, oid_pos); 505 - return Encode.writeIntArray(i32, 4, values, buf); 505 + return Encode.writeIntArray(i32, values, buf); 506 506 } 507 507 }; 508 508 ··· 510 510 pub const oid = OID.make(1016); 511 511 const encoding = &binary_encoding; 512 512 513 - fn encode(values: []const i64, buf: *buffer.Buffer, oid_pos: usize) !void { 513 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 514 514 buf.writeAt(&Int64.oid.encoded, oid_pos); 515 - return Encode.writeIntArray(i64, 8, values, buf); 515 + return Encode.writeIntArray(i64, values, buf); 516 516 } 517 517 518 - fn encodeUnsigned(values: []const u64, buf: *buffer.Buffer, oid_pos: usize) !void { 518 + fn encodeUnsigned(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 519 519 for (values) |v| { 520 520 if (v > 9223372036854775807) return error.UnsignedIntWouldBeTruncated; 521 521 } 522 522 buf.writeAt(&Int64.oid.encoded, oid_pos); 523 - return Encode.writeIntArray(i64, 8, values, buf); 523 + return Encode.writeIntArray(i64, values, buf); 524 524 } 525 525 }; 526 526 527 527 pub const TimestampArray = struct { 528 528 pub const oid = OID.make(1115); 529 529 const encoding = &binary_encoding; 530 - const us_from_epoch_to_y2k = 946_684_800_000_000; 531 530 532 - fn encode(values: []const i64, buf: *buffer.Buffer, oid_pos: usize) !void { 531 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 533 532 buf.writeAt(&Timestamp.oid.encoded, oid_pos); 534 - 535 - // every value is 12 bytes, 4 byte length + 8 byte value 536 - var view = try buf.skip(12 * values.len); 537 - for (values) |value| { 538 - view.write(&.{ 0, 0, 0, 8 }); // length of value 539 - view.writeIntBig(i64, value - us_from_epoch_to_y2k); 540 - } 533 + try writeTimestampArray(values, buf); 541 534 } 542 535 }; 543 536 ··· 545 538 pub const oid = OID.make(1185); 546 539 const encoding = &binary_encoding; 547 540 541 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 542 + buf.writeAt(&TimestampTz.oid.encoded, oid_pos); 543 + try writeTimestampArray(values, buf); 544 + } 545 + }; 546 + 547 + fn writeTimestampArray(values: anytype, buf: *buffer.Buffer) !void { 548 548 const us_from_epoch_to_y2k = 946_684_800_000_000; 549 549 550 - fn encode(values: []const i64, buf: *buffer.Buffer, oid_pos: usize) !void { 551 - buf.writeAt(&TimestampTz.oid.encoded, oid_pos); 550 + // at most, every value is 12 bytes, 4 byte length + 8 byte value 551 + var view = try buf.skip(12 * values.len); 552 + 553 + const nullables = @typeInfo(@TypeOf(values)).pointer.child == ?i64; 554 + var null_count: usize = 0; 555 + 556 + for (values) |value| { 557 + var v: i64 = undefined; 558 + if (comptime nullables) { 559 + v = value orelse { 560 + null_count += 1; 561 + view.write(&.{ 255, 255, 255, 255 }); // null, 562 + continue; 563 + }; 564 + } else v = value; 565 + view.write(&.{ 0, 0, 0, 8 }); // length of value 566 + view.writeIntBig(i64, v - us_from_epoch_to_y2k); 567 + } 552 568 553 - // every value is 12 bytes, 4 byte length + 8 byte value 554 - var view = try buf.skip(12 * values.len); 555 - for (values) |value| { 556 - view.write(&.{ 0, 0, 0, 8 }); // length of value 557 - view.writeIntBig(i64, value - us_from_epoch_to_y2k); 558 - } 569 + if (comptime nullables) { 570 + buf.truncate(null_count * 8); 559 571 } 560 - }; 572 + } 561 573 562 574 pub const Float32Array = struct { 563 575 pub const oid = OID.make(1021); 564 576 const encoding = &binary_encoding; 565 577 566 - fn encode(values: []const f32, buf: *buffer.Buffer, oid_pos: usize) !void { 578 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 567 579 buf.writeAt(&Float32.oid.encoded, oid_pos); 568 - 569 - // every value takes 8 bytes, 4 for the length, 4 for the value 570 - var view = try buf.skip(8 * values.len); 571 - for (values) |value| { 572 - view.write(&.{ 0, 0, 0, 4 }); //length 573 - const tmp: *i32 = @constCast(@ptrCast(&value)); 574 - view.writeIntBig(i32, tmp.*); 575 - } 580 + return writeFloatArray(f32, i32, values, buf); 576 581 } 577 582 }; 578 583 ··· 580 585 pub const oid = OID.make(1022); 581 586 const encoding = &binary_encoding; 582 587 583 - fn encode(values: []const f64, buf: *buffer.Buffer, oid_pos: usize) !void { 588 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 584 589 buf.writeAt(&Float64.oid.encoded, oid_pos); 590 + return writeFloatArray(f64, i64, values, buf); 591 + } 592 + }; 585 593 586 - // every value takes 12 bytes, 4 for the length, 8 for the value 587 - var view = try buf.skip(12 * values.len); 588 - for (values) |value| { 589 - view.write(&.{ 0, 0, 0, 8 }); //length 590 - const tmp: *i64 = @constCast(@ptrCast(&value)); 591 - view.writeIntBig(i64, tmp.*); 592 - } 594 + fn writeFloatArray(comptime F: type, comptime I: type, values: anytype, buf: *buffer.Buffer) !void { 595 + // The most space this can take, + 4 for the length; 596 + var view = try buf.skip((@sizeOf(I) + 4) * values.len); 597 + 598 + const nullables = @typeInfo(@typeInfo(@TypeOf(values)).pointer.child) == .optional; 599 + var null_count: usize = 0; 600 + 601 + for (values) |value| { 602 + var v: F = undefined; 603 + if (comptime nullables) { 604 + v = value orelse { 605 + null_count += 1; 606 + view.write(&.{ 255, 255, 255, 255 }); // null, 607 + continue; 608 + }; 609 + } else v = value; 610 + 611 + const tmp: *I = @ptrCast(@constCast(&v)); 612 + view.write(&.{ 0, 0, 0, @sizeOf(I) }); //length 613 + view.writeIntBig(I, tmp.*); 614 + } 615 + 616 + if (comptime nullables) { 617 + buf.truncate(null_count * @sizeOf(I)); 593 618 } 594 - }; 619 + } 595 620 596 621 pub const BoolArray = struct { 597 622 pub const oid = OID.make(1000); 598 623 const encoding = &binary_encoding; 599 624 600 - fn encode(values: []const bool, buf: *buffer.Buffer, oid_pos: usize) !void { 625 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 601 626 buf.writeAt(&Bool.oid.encoded, oid_pos); 627 + // at most every value takes 5 bytes, 4 for the length, 1 for the value 628 + var view = try buf.skip(5 * values.len); 602 629 603 - // every value takes 5 bytes, 4 for the length, 1 for the value 604 - var view = try buf.skip(5 * values.len); 630 + const nullables = @typeInfo(@typeInfo(@TypeOf(values)).pointer.child) == .optional; 631 + var null_count: usize = 0; 632 + 605 633 for (values) |value| { 634 + var v: bool = undefined; 635 + if (comptime nullables) { 636 + v = value orelse { 637 + null_count += 1; 638 + view.write(&.{ 255, 255, 255, 255 }); // null, 639 + continue; 640 + }; 641 + } else v = value; 642 + 606 643 // each value is prefixed with a 4 byte length 607 - if (value) { 644 + if (v) { 608 645 view.write(&.{ 0, 0, 0, 1, 1 }); 609 646 } else { 610 647 view.write(&.{ 0, 0, 0, 1, 0 }); 611 648 } 649 + } 650 + 651 + if (comptime nullables) { 652 + buf.truncate(null_count); 612 653 } 613 654 } 614 655 }; ··· 695 736 pub const oid = OID.make(1001); 696 737 const encoding = &binary_encoding; 697 738 698 - fn encode(values: []const []const u8, buf: *buffer.Buffer, oid_pos: usize) !void { 739 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 699 740 buf.writeAt(&Bytea.oid.encoded, oid_pos); 700 741 return Encode.writeByteArray(values, buf); 701 742 } ··· 705 746 pub const oid = OID.make(1009); 706 747 const encoding = &binary_encoding; 707 748 708 - fn encode(values: []const []const u8, buf: *buffer.Buffer, oid_pos: usize) !void { 749 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 709 750 buf.writeAt(&String.oid.encoded, oid_pos); 710 751 return Encode.writeByteArray(values, buf); 711 752 } ··· 724 765 pub const oid = OID.make(2951); 725 766 const encoding = &binary_encoding; 726 767 727 - fn encode(values: []const []const u8, buf: *buffer.Buffer, oid_pos: usize) !void { 768 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 728 769 buf.writeAt(&UUID.oid.encoded, oid_pos); 729 770 730 - // every value is 20 bytes, 4 byte length + 16 byte value 771 + const T = @typeInfo(@TypeOf(values)).pointer.child; 772 + const TT = switch (@typeInfo(T)) { 773 + .optional => |opt| opt.child, 774 + else => T, 775 + }; 776 + const nullables = @typeInfo(T) == .optional; 777 + 778 + var null_count: usize = 0; 779 + 780 + // at most every value is 20 bytes, 4 byte length + 16 byte value 731 781 var view = try buf.skip(20 * values.len); 732 782 for (values) |value| { 783 + var v: TT = undefined; 784 + if (comptime nullables) { 785 + v = value orelse { 786 + null_count += 1; 787 + view.write(&.{ 255, 255, 255, 255 }); 788 + continue; 789 + }; 790 + } else v = value; 791 + 733 792 view.write(&.{ 0, 0, 0, 16 }); // length of value 734 - switch (value.len) { 735 - 16 => view.write(value), 736 - 36 => view.write(&(try UUID.toBytes(value))), 793 + switch (v.len) { 794 + 16 => view.write(v), 795 + 36 => view.write(&(try UUID.toBytes(v))), 737 796 else => return error.InvalidUUID, 738 797 } 739 798 } 799 + 800 + if (comptime nullables) { 801 + buf.truncate(null_count * 16); 802 + } 740 803 } 741 804 }; 742 805 ··· 744 807 pub const oid = OID.make(199); 745 808 const encoding = &binary_encoding; 746 809 747 - fn encode(values: []const []const u8, buf: *buffer.Buffer, oid_pos: usize) !void { 810 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 748 811 buf.writeAt(&JSON.oid.encoded, oid_pos); 749 812 return Encode.writeByteArray(values, buf); 750 813 } ··· 754 817 pub const oid = OID.make(3807); 755 818 const encoding = &binary_encoding; 756 819 757 - fn encode(values: []const []const u8, buf: *buffer.Buffer, oid_pos: usize) !void { 820 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 758 821 buf.writeAt(&JSONB.oid.encoded, oid_pos); 822 + if (@typeInfo(@typeInfo(@TypeOf(values)).pointer.child) == .optional) { 823 + return encodeNullables(values, buf); 824 + } 759 825 760 826 // every value has a 5 byte prefix, a 4 byte length and a 1 byte version 761 827 var len = values.len * 5; ··· 771 837 view.write(value); 772 838 } 773 839 } 840 + 841 + fn encodeNullables(values: []const ?[]const u8, buf: *buffer.Buffer) !void { 842 + // every value has a 5 byte prefix, a 4 byte length and a 1 byte version 843 + var len = values.len * 5; 844 + for (values) |value| { 845 + if (value) |v| { 846 + len += v.len; 847 + } 848 + } 849 + 850 + var view = try buf.skip(len); 851 + for (values) |value| { 852 + if (value) |v| { 853 + // + 1 for the version 854 + view.writeIntBig(i32, @intCast(v.len + 1)); 855 + view.writeByte(1); // version 856 + view.write(v); 857 + } else { 858 + view.write(&.{ 255, 255, 255, 255 }); // null, 859 + } 860 + } 861 + } 774 862 }; 775 863 776 864 pub const CharArray = struct { ··· 791 879 } 792 880 793 881 // This is for a char[] bound to a [][]u8 794 - fn encode(values: []const []const u8, buf: *buffer.Buffer, oid_pos: usize) !void { 882 + fn encode(values: anytype, buf: *buffer.Buffer, oid_pos: usize) !void { 795 883 buf.writeAt(&Char.oid.encoded, oid_pos); 796 884 return Encode.writeByteArray(values, buf); 797 885 } ··· 813 901 814 902 pub const Encode = struct { 815 903 // helpers for encoding data (or part of the data) 816 - pub fn writeIntArray(comptime T: type, size: usize, values: []const T, buf: *buffer.Buffer) !void { 904 + pub fn writeIntArray(comptime T: type, values: anytype, buf: *buffer.Buffer) !void { 905 + const size = @sizeOf(T); 906 + // at most, every value is a 4 byte length + the size of the underlying it 817 907 var view = try buf.skip((size + 4) * values.len); 908 + 909 + const nullables = @typeInfo(@typeInfo(@TypeOf(values)).pointer.child) == .optional; 910 + var null_count: usize = 0; 818 911 819 912 var value_len: [4]u8 = undefined; 820 913 std.mem.writeInt(i32, &value_len, @intCast(size), .big); 914 + 821 915 for (values) |value| { 916 + var v: T = undefined; 917 + if (comptime nullables) { 918 + v = value orelse { 919 + null_count += 1; 920 + view.write(&.{ 255, 255, 255, 255 }); // null, 921 + continue; 922 + }; 923 + } else v = value; 822 924 view.write(&value_len); 823 - view.writeIntBig(T, value); 925 + view.writeIntBig(T, v); 926 + } 927 + 928 + if (comptime nullables) { 929 + buf.truncate(null_count * size); 824 930 } 825 931 } 826 932 827 - pub fn writeByteArray(values: []const []const u8, buf: *buffer.Buffer) !void { 933 + pub fn writeByteArray(values: anytype, buf: *buffer.Buffer) !void { 934 + if (@typeInfo(@typeInfo(@TypeOf(values)).pointer.child) == .optional) { 935 + return writeNullableByteArray(values, buf); 936 + } 828 937 // each value has a 4 byte length prefix 829 938 var len = values.len * 4; 830 939 for (values) |value| { ··· 835 944 for (values) |value| { 836 945 view.writeIntBig(i32, @intCast(value.len)); 837 946 view.write(value); 947 + } 948 + } 949 + 950 + pub fn writeNullableByteArray(values: []const ?[]const u8, buf: *buffer.Buffer) !void { 951 + // each value has a 4 byte length prefix 952 + var len = values.len * 4; 953 + for (values) |value| { 954 + if (value) |v| { 955 + len += v.len; 956 + } 957 + } 958 + 959 + var view = try buf.skip(len); 960 + for (values) |value| { 961 + if (value) |v| { 962 + view.writeIntBig(i32, @intCast(v.len)); 963 + view.write(v); 964 + } else { 965 + view.write(&.{ 255, 255, 255, 255 }); 966 + } 838 967 } 839 968 } 840 969 ··· 1297 1426 try buf.write(&.{ 0, 0, 0, 1 }); // lower bound of this demension 1298 1427 1299 1428 const ElemT = @typeInfo(T).pointer.child; 1300 - switch (@typeInfo(ElemT)) { 1429 + const ElemTT = switch (@typeInfo(ElemT)) { 1430 + .optional => |opt| opt.child, 1431 + else => ElemT, 1432 + }; 1433 + switch (@typeInfo(ElemTT)) { 1301 1434 .int => |int| { 1302 1435 if (int.signedness == .signed) { 1303 1436 switch (int.bits) {
+29 -10
src/types/numeric.zig
··· 58 58 } 59 59 60 60 pub fn encodeBuf(value: anytype, buf: *buffer.Buffer) !void { 61 - if (@TypeOf(value) != comptime_float) { 62 - if (math.isNan(value)) { 63 - return encodeNaN(buf); 64 - } 65 - if (math.isNegativeInf(value)) { 66 - return encodeNegativeInf(buf); 67 - } 68 - if (math.isInf(value)) { 69 - return encodeInf(buf); 70 - } 61 + const T = @TypeOf(value); 62 + if (T == comptime_float) { 63 + return encodeValue(value, buf); 64 + } 65 + 66 + const TT = switch (@typeInfo(T)) { 67 + .optional => |opt| opt.child, 68 + else => T, 69 + }; 70 + 71 + var v: TT = undefined; 72 + if (comptime @typeInfo(T) == .optional) { 73 + v = value orelse return encodeValidString("null", buf); 74 + } else { 75 + v = value; 76 + } 77 + 78 + if (math.isNan(v)) { 79 + return encodeNaN(buf); 80 + } 81 + if (math.isNegativeInf(v)) { 82 + return encodeNegativeInf(buf); 83 + } 84 + if (math.isInf(v)) { 85 + return encodeInf(buf); 71 86 } 72 87 88 + return encodeValue(v, buf); 89 + } 90 + 91 + fn encodeValue(value: anytype, buf: *buffer.Buffer) !void { 73 92 // turn our float into a string 74 93 var str_buf: [512]u8 = undefined; 75 94 var stream = std.io.fixedBufferStream(&str_buf);