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){
// }
// }
};
}
|