summaryrefslogtreecommitdiff
path: root/src/main.zig
blob: cccdf44370ad99ce9f90d0c78f8908c061dfaf9d (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
146
147
148
const std = @import("std");
var stdout_buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
const stdout = &stdout_writer.interface;

fn parseVarint(data: []const u8) struct { value: u64, len: usize } {
    var result: u64 = 0;
    var i: usize = 0;

    while (i < data.len and i < 9) : (i += 1) {
        const byte = data[i];
        result |= @as(u64, byte & 0x7f) << @as(u6, @intCast(i * 7));

        if ((byte & 0x80) == 0) {
            return .{ .value = result, .len = i + 1 };
        }
    }

    return .{ .value = result, .len = i };
}

fn readStringFromRecord(data: []const u8, serial_type: u64) struct { value: []const u8, len: usize } {
    if (serial_type >= 13 and (serial_type % 2) == 1) {
        const size = (serial_type - 13) / 2;
        return .{ .value = data[0..size], .len = size };
    }
    return .{ .value = "", .len = 0 };
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    if (args.len < 3) {
        std.debug.print("Usage: {s} <database_file_path> <command>\n", .{args[0]});
        std.process.exit(1);
    }

    const database_file_path: []const u8 = args[1];
    const command: []const u8 = args[2];

    if (std.mem.eql(u8, command, ".dbinfo")) {
        var file = try std.fs.cwd().openFile(database_file_path, .{});
        defer file.close();

        var buf: [2]u8 = undefined;
        _ = try file.seekTo(16);
        _ = try file.read(&buf);
        const page_size = std.mem.readInt(u16, &buf, .big);
        try stdout.print("database page size: {}\n", .{page_size});

        _ = try file.seekTo(103);
        _ = try file.read(&buf);
        const num_tables = std.mem.readInt(u16, &buf, .big);
        try stdout.print("number of tables: {}\n", .{num_tables});
        try stdout.flush();
    } else if (std.mem.eql(u8, command, ".tables")) {
        var file = try std.fs.cwd().openFile(database_file_path, .{});
        defer file.close();

        var buf: [2]u8 = undefined;
        _ = try file.seekTo(16);
        _ = try file.read(&buf);
        const page_size = std.mem.readInt(u16, &buf, .big);

        _ = try file.seekTo(103);
        _ = try file.read(&buf);
        const num_cells = std.mem.readInt(u16, &buf, .big);

        var cell_pointers = try allocator.alloc(u16, num_cells);
        defer allocator.free(cell_pointers);

        for (0..num_cells) |i| {
            _ = try file.seekTo(108 + i * 2);
            _ = try file.read(&buf);
            cell_pointers[i] = std.mem.readInt(u16, &buf, .big);
        }

        var page_data = try allocator.alloc(u8, page_size);
        defer allocator.free(page_data);

        _ = try file.seekTo(0);
        _ = try file.read(page_data);

        var first_table = true;

        for (0..num_cells) |i| {
            const cell_offset = cell_pointers[i];
            const cell_data = page_data[cell_offset..];

            var parsed = parseVarint(cell_data);
            var pos = parsed.len;

            parsed = parseVarint(cell_data[pos..]);
            pos += parsed.len;

            const record_start = pos;
            const record_data = cell_data[record_start..];

            parsed = parseVarint(record_data);
            const header_size = parsed.value;
            var header_pos = parsed.len;

            var serial_types: [5]u64 = undefined;
            for (0..5) |col| {
                parsed = parseVarint(record_data[header_pos..]);
                serial_types[col] = parsed.value;
                header_pos += parsed.len;
            }

            var body_pos: usize = header_size;

            for (0..2) |col| {
                const serial_type = serial_types[col];
                if (serial_type >= 13 and (serial_type % 2) == 1) {
                    const size = (serial_type - 13) / 2;
                    body_pos += size;
                }
            }

            const tbl_name_type = serial_types[2];
            const tbl_name = readStringFromRecord(record_data[body_pos..], tbl_name_type);

            const body_pos_type: usize = header_size;
            const type_serial = serial_types[0];
            const type_str = readStringFromRecord(record_data[body_pos_type..], type_serial);

            if (std.mem.eql(u8, type_str.value, "table")) {
                if (!std.mem.eql(u8, tbl_name.value, "sqlite_sequence")) {
                    if (!first_table) {
                        try stdout.print(" ", .{});
                    }
                    try stdout.print("{s}", .{tbl_name.value});
                    first_table = false;
                }
            }
        }

        if (!first_table) {
            try stdout.print("\n", .{});
        }
        try stdout.flush();
    }
}