summaryrefslogtreecommitdiff
path: root/src/duckdb/Result.zig
blob: 75f74fd32f0e258ba8d3b29406581044fe6cc479 (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
149
150
151
152
153
154
const std   = @import("std");
const types = @import("Types.zig");
const c = @cImport({
    @cInclude("duckdb.h");
});


pub fn Result(comptime T: type) type{
    const column_count = switch (@typeInfo(T)) {
        .Struct => |v| v.fields.len,
        .Void => 0,
        else => @compileError("Expecting struct or void in query result type"),
    };

    return struct {
        _res:         c.duckdb_result,
        _chunk:       c.duckdb_data_chunk,
        _columns:     [column_count]c.duckdb_vector,
        _validities:  [column_count]?[*]u64,
        _data:        [column_count]?*anyopaque,

        _current_row: usize,

        const Self = @This();

        pub fn init(conn : c.duckdb_connection, query: [:0]const u8) !Self {
            var self : Self = .{
                ._res            = undefined,
                ._chunk          = null,
                ._columns        = undefined,
                ._validities     = undefined,
                ._data           = undefined,
                ._current_row    = 0,
            };
            const state = c.duckdb_query(conn, query, &self._res);
            if ( state == c.DuckDBError){
                return error.DuckDBError;
            }
            self.fetchDataChunk();
            if( column_count != 0 and
                column_count != self.getColumnCount() ){
                return error.QueryColumnCountCapture;
            }

            return self;
        }
        pub fn deinit(self: *Self) void {
            c.duckdb_destroy_result(&self._res);
            if (self._chunk != null){
                c.duckdb_destroy_data_chunk(&self._chunk);
                self._chunk = null;
            }
        }

        /// There's not way to know how many total elements we have, but we can
        /// know how many we have in the current chunk.
        fn getCurrentChunkSize(self: Self) usize {
            std.debug.assert(self._chunk != null);
            return c.duckdb_data_chunk_get_size(self._chunk);
        }

        pub fn getColumnCount(self: Self) usize {
            std.debug.assert(self._chunk != null);
            return c.duckdb_data_chunk_get_column_count(self._chunk);
        }

        /// This needs to be called repeatedly to obtain the next blocks of
        /// data. There's no way to know how many elements we'll obtain from
        /// it.
        fn fetchDataChunk(self: *Self) void{
            if (self._chunk != null){
                c.duckdb_destroy_data_chunk(&self._chunk);
            }
            self._chunk = c.duckdb_fetch_chunk(self._res);
            for (self._columns, 0..) |_, i| {
                const col = c.duckdb_data_chunk_get_vector(self._chunk, i);
                self._columns[i]    = col;
                self._validities[i] = c.duckdb_vector_get_validity(col);
                self._data[i]       = c.duckdb_vector_get_data(col);
            }
            self._current_row = 0;
        }

        pub fn exausted(self: Self) bool{
            // TODO: check exhaustion properly
            return self._chunk != null;
        }

        /// We need some comptime magic to create the output structures from
        /// the T.
        pub fn next(self: *Self) !T{
            var result: T = undefined;
            const fields = comptime switch (@typeInfo(T)) {
                .Void => .{},
                else  => |f| f.Struct.fields,
            };

            if (self._current_row == self.getCurrentChunkSize()){
                self.fetchDataChunk();
            }

            inline for (fields, 0..) |field, i| {
                // TODO: check compatibility between the column type and
                // the struct provided as result container
                //
                const column_type = c.duckdb_column_type(&self._res, i);
                // std.debug.print("tipo => {any}\n", .{column_type});
                // std.debug.print("{any}\n", .{self._data});

                // Check validity
                const entry_idx     :usize = self._current_row / 64;
                const idx_in_entry  :u6    = @intCast(@mod(self._current_row, 64));
                const one           :u64   = 1;
                var   is_valid      :bool  = false;
                if (self._validities[i]) |v| {
                    const num = v[entry_idx] & @shlExact(one, idx_in_entry);
                    is_valid = num != 0;
                }

                // Store the column in current row
                if (is_valid){
                    // Unwrap the Optional
                    const t = switch (@typeInfo(field.type)){
                        .Optional => |t| t.child,
                        else => field.type
                    };
                    // Unpack the C array of duckdb_type
                    const col: [*]types.unpack_type(t) = @alignCast(@ptrCast(self._data[i]));
                    std.debug.assert(types.valid_unpack(column_type, t));

                    // Convert to Zig data type
                    @field(result, field.name) = try types.cast(&col[self._current_row], t);
                } else {
                    // Got a NULL from the DB
                    if (@typeInfo(field.type) != .Optional){
                        // Cannot return it because it's not optional
                        return error.NullInNotOptional;
                    }
                    @field(result, field.name) = null;
                }
            }
            self._current_row += 1;
            return result;
        }



        // pub fn giveMeAll(){
        //     // TODO: know the chunk length and use it
        //     while (self._current_row < self._chunk_size) : (self._current_row += 1){
        //     }
        // }
    };
}