aboutsummaryrefslogtreecommitdiff
path: root/src/proto/row_description.zig
blob: 50e4cb052c9175f28b304fc9ad898929ab9790a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
const std = @import("std");
const log = std.log.scoped(.pgz);
const ByteArrayList = std.ArrayList(u8);
const ProtocolError = @import("../main.zig").ProtocolError;
const ClientError = @import("../main.zig").ClientError;
const enum_from_int = @import("../main.zig").enum_from_int;
const FormatCode = @import("../main.zig").FormatCode;

pub const Tag: u8 = 'T';

const RowDescription = @This();

buf: ?[]const u8 = null, // owned
fields: []Field, // owned

pub const Field = struct {
    name: []const u8,
    table_oid: u32,
    attr_no: u16,
    data_type_oid: u32,
    data_type_size: i16,
    data_type_modifier: u32,
    format_code: FormatCode,
};

pub fn read(a: std.mem.Allocator, buf: []const u8) !RowDescription {
    errdefer a.free(buf);
    var fbs = std.io.fixedBufferStream(buf);
    var reader = fbs.reader();
    const n_fields = try reader.readIntBig(u16);
    var fields = try a.alloc(Field, n_fields);
    errdefer a.free(fields);
    for (0..n_fields) |i| {
        const name_start = fbs.pos;
        try reader.skipUntilDelimiterOrEof(0);
        const name_end = fbs.pos - 1;
        const name = buf[name_start..name_end];
        const field = Field{
            .name = name,
            .table_oid = try reader.readIntBig(u32),
            .attr_no = try reader.readIntBig(u16),
            .data_type_oid = try reader.readIntBig(u32),
            .data_type_size = try reader.readIntBig(i16),
            .data_type_modifier = try reader.readIntBig(u32),
            .format_code = enum_from_int(FormatCode, try reader.readIntBig(u16)) orelse return ProtocolError.InvalidFormatCode,
        };
        fields[i] = field;
    }
    return .{
        .buf = buf,
        .fields = fields,
    };
}

pub fn write(self: RowDescription, a: std.mem.Allocator, stream_writer: anytype) !void {
    try stream_writer.writeByte(Tag);
    var al = ByteArrayList.init(a);
    defer al.deinit();
    var cw = std.io.countingWriter(al.writer());
    var writer = cw.writer();
    try writer.writeIntBig(u32, 0); // length placeholder
    try writer.writeIntBig(u16, @as(u16, @intCast(self.fields.len)));
    for (self.fields) |field| {
        try writer.writeAll(field.name);
        try writer.writeByte(0);
        try writer.writeIntBig(u32, field.table_oid);
        try writer.writeIntBig(u16, field.attr_no);
        try writer.writeIntBig(u32, field.data_type_oid);
        try writer.writeIntBig(i16, field.data_type_size);
        try writer.writeIntBig(u32, field.data_type_modifier);
        try writer.writeIntBig(u16, @intFromEnum(field.format_code));
    }
    std.mem.writeIntBig(u32, al.items[0..4], @as(u32, @intCast(cw.bytes_written)));
    try stream_writer.writeAll(al.items);
}

// Caller owns the result.
pub fn clone(self: RowDescription, a: std.mem.Allocator) !RowDescription {
    var ba = ByteArrayList.init(a);
    errdefer ba.deinit();
    try self.write(a, ba.writer());
    return try RowDescription.read(a, ba.items);
}

pub fn deinit(self: *RowDescription, a: std.mem.Allocator) void {
    a.free(self.fields);
    if (self.buf != null) a.free(self.buf.?);
}

test "round trip" {
    const allocator = std.testing.allocator;
    var fields = try allocator.alloc(Field, 3);
    fields[0] = .{
        .name = "foo",
        .table_oid = 1,
        .attr_no = 2,
        .data_type_oid = 3,
        .data_type_size = 4,
        .data_type_modifier = 5,
        .format_code = .Binary,
    };
    fields[1] = .{
        .name = "bar",
        .table_oid = 1,
        .attr_no = 2,
        .data_type_oid = 3,
        .data_type_size = 4,
        .data_type_modifier = 5,
        .format_code = .Binary,
    };
    fields[2] = .{
        .name = "BAZZZZZ",
        .table_oid = 99,
        .attr_no = 98,
        .data_type_oid = 97,
        .data_type_size = 96,
        .data_type_modifier = 95,
        .format_code = .Text,
    };
    var f0 = fields[0];
    var f1 = fields[1];
    var f2 = fields[2];
    var sm = RowDescription{
        .fields = fields,
    };
    defer sm.deinit(allocator);

    var bal = ByteArrayList.init(allocator);
    defer bal.deinit();
    try sm.write(allocator, bal.writer());

    var fbs = std.io.fixedBufferStream(bal.items);
    var reader = fbs.reader();
    const tag = try reader.readByte();
    try std.testing.expectEqual(Tag, tag);
    const len = try reader.readIntBig(u32);
    const buf = try allocator.alloc(u8, len - 4);
    try reader.readNoEof(buf);
    var sm2 = try RowDescription.read(allocator, buf);
    defer sm2.deinit(allocator);

    try std.testing.expectEqualDeep(f0, sm2.fields[0]);
    try std.testing.expectEqualDeep(f1, sm2.fields[1]);
    try std.testing.expectEqualDeep(f2, sm2.fields[2]);
}