quack_rs/vector/struct_reader.rs
1// SPDX-License-Identifier: MIT
2// Copyright 2026 Tom F. <https://github.com/tomtom215/>
3// My way of giving something small back to the open source community
4// and encouraging more Rust development!
5
6//! Batched, typed reader for STRUCT input vectors.
7//!
8//! [`StructReader`] pre-creates [`VectorReader`]s for every field at construction,
9//! then exposes typed `read_*` methods that take `(row, field_idx)`.
10//! This is the read-side counterpart to [`StructWriter`][super::StructWriter].
11//!
12//! # Example
13//!
14//! ```rust,no_run
15//! use quack_rs::vector::StructReader;
16//! use libduckdb_sys::duckdb_vector;
17//!
18//! // Inside a scan callback, given a STRUCT input vector with 3 fields:
19//! // let sr = unsafe { StructReader::new(struct_vec, 3, row_count) };
20//! // for row in 0..row_count {
21//! // let name = unsafe { sr.read_str(row, 0) };
22//! // let age = unsafe { sr.read_i32(row, 1) };
23//! // let active = unsafe { sr.read_bool(row, 2) };
24//! // }
25//! ```
26
27use libduckdb_sys::duckdb_vector;
28
29use crate::interval::DuckInterval;
30use crate::vector::complex::StructVector;
31use crate::vector::VectorReader;
32
33/// A batched reader for STRUCT input vectors.
34///
35/// Pre-creates a [`VectorReader`] for every field at construction, allowing
36/// direct typed reads without repeated `duckdb_struct_vector_get_child` calls.
37pub struct StructReader {
38 fields: Vec<VectorReader>,
39}
40
41impl StructReader {
42 /// Creates a new `StructReader` for a STRUCT vector with `field_count` fields.
43 ///
44 /// # Safety
45 ///
46 /// - `vector` must be a valid `DuckDB` STRUCT vector.
47 /// - `field_count` must match the number of fields in the STRUCT type.
48 /// - `row_count` must match the number of rows in the parent chunk.
49 /// - The vector must remain valid for the lifetime of this reader.
50 pub unsafe fn new(vector: duckdb_vector, field_count: usize, row_count: usize) -> Self {
51 let mut fields = Vec::with_capacity(field_count);
52 for idx in 0..field_count {
53 // SAFETY: caller guarantees vector is valid STRUCT with field_count fields.
54 fields.push(unsafe { StructVector::field_reader(vector, idx, row_count) });
55 }
56 Self { fields }
57 }
58
59 /// Returns the number of fields in this struct reader.
60 #[mutants::skip]
61 #[must_use]
62 #[inline]
63 pub fn field_count(&self) -> usize {
64 self.fields.len()
65 }
66
67 /// Returns a reference to the [`VectorReader`] for the given field.
68 ///
69 /// # Panics
70 ///
71 /// Panics if `field_idx >= field_count`.
72 #[must_use]
73 #[inline]
74 pub fn field(&self, field_idx: usize) -> &VectorReader {
75 &self.fields[field_idx]
76 }
77
78 /// Returns `true` if the value at `row` in field `field_idx` is not NULL.
79 ///
80 /// # Safety
81 ///
82 /// `row` must be less than the row count.
83 ///
84 /// # Panics
85 ///
86 /// Panics if `field_idx >= field_count`.
87 #[inline]
88 pub unsafe fn is_valid(&self, row: usize, field_idx: usize) -> bool {
89 unsafe { self.fields[field_idx].is_valid(row) }
90 }
91
92 /// Reads a `bool` (BOOLEAN) value from field `field_idx` at row `row`.
93 ///
94 /// # Safety
95 ///
96 /// - `row` must be less than the row count.
97 /// - The field at `field_idx` must have `BOOLEAN` type.
98 ///
99 /// # Panics
100 ///
101 /// Panics if `field_idx >= field_count`.
102 #[inline]
103 pub unsafe fn read_bool(&self, row: usize, field_idx: usize) -> bool {
104 unsafe { self.fields[field_idx].read_bool(row) }
105 }
106
107 /// Reads a VARCHAR value from field `field_idx` at row `row`.
108 ///
109 /// # Safety
110 ///
111 /// - `row` must be less than the row count.
112 /// - The field at `field_idx` must have `VARCHAR` type.
113 ///
114 /// # Panics
115 ///
116 /// Panics if `field_idx >= field_count`.
117 #[inline]
118 pub unsafe fn read_str(&self, row: usize, field_idx: usize) -> &str {
119 unsafe { self.fields[field_idx].read_str(row) }
120 }
121
122 /// Reads an `i8` (TINYINT) value from field `field_idx` at row `row`.
123 ///
124 /// # Safety
125 ///
126 /// See [`read_bool`][Self::read_bool].
127 #[inline]
128 pub unsafe fn read_i8(&self, row: usize, field_idx: usize) -> i8 {
129 unsafe { self.fields[field_idx].read_i8(row) }
130 }
131
132 /// Reads an `i16` (SMALLINT) value.
133 ///
134 /// # Safety
135 ///
136 /// See [`read_bool`][Self::read_bool].
137 #[inline]
138 pub unsafe fn read_i16(&self, row: usize, field_idx: usize) -> i16 {
139 unsafe { self.fields[field_idx].read_i16(row) }
140 }
141
142 /// Reads an `i32` (INTEGER) value.
143 ///
144 /// # Safety
145 ///
146 /// See [`read_bool`][Self::read_bool].
147 #[inline]
148 pub unsafe fn read_i32(&self, row: usize, field_idx: usize) -> i32 {
149 unsafe { self.fields[field_idx].read_i32(row) }
150 }
151
152 /// Reads an `i64` (BIGINT) value.
153 ///
154 /// # Safety
155 ///
156 /// See [`read_bool`][Self::read_bool].
157 #[inline]
158 pub unsafe fn read_i64(&self, row: usize, field_idx: usize) -> i64 {
159 unsafe { self.fields[field_idx].read_i64(row) }
160 }
161
162 /// Reads an `i128` (HUGEINT) value.
163 ///
164 /// # Safety
165 ///
166 /// See [`read_bool`][Self::read_bool].
167 #[inline]
168 pub unsafe fn read_i128(&self, row: usize, field_idx: usize) -> i128 {
169 unsafe { self.fields[field_idx].read_i128(row) }
170 }
171
172 /// Reads a `u8` (UTINYINT) value.
173 ///
174 /// # Safety
175 ///
176 /// See [`read_bool`][Self::read_bool].
177 #[inline]
178 pub unsafe fn read_u8(&self, row: usize, field_idx: usize) -> u8 {
179 unsafe { self.fields[field_idx].read_u8(row) }
180 }
181
182 /// Reads a `u16` (USMALLINT) value.
183 ///
184 /// # Safety
185 ///
186 /// See [`read_bool`][Self::read_bool].
187 #[inline]
188 pub unsafe fn read_u16(&self, row: usize, field_idx: usize) -> u16 {
189 unsafe { self.fields[field_idx].read_u16(row) }
190 }
191
192 /// Reads a `u32` (UINTEGER) value.
193 ///
194 /// # Safety
195 ///
196 /// See [`read_bool`][Self::read_bool].
197 #[inline]
198 pub unsafe fn read_u32(&self, row: usize, field_idx: usize) -> u32 {
199 unsafe { self.fields[field_idx].read_u32(row) }
200 }
201
202 /// Reads a `u64` (UBIGINT) value.
203 ///
204 /// # Safety
205 ///
206 /// See [`read_bool`][Self::read_bool].
207 #[inline]
208 pub unsafe fn read_u64(&self, row: usize, field_idx: usize) -> u64 {
209 unsafe { self.fields[field_idx].read_u64(row) }
210 }
211
212 /// Reads an `f32` (FLOAT) value.
213 ///
214 /// # Safety
215 ///
216 /// See [`read_bool`][Self::read_bool].
217 #[inline]
218 pub unsafe fn read_f32(&self, row: usize, field_idx: usize) -> f32 {
219 unsafe { self.fields[field_idx].read_f32(row) }
220 }
221
222 /// Reads an `f64` (DOUBLE) value.
223 ///
224 /// # Safety
225 ///
226 /// See [`read_bool`][Self::read_bool].
227 #[inline]
228 pub unsafe fn read_f64(&self, row: usize, field_idx: usize) -> f64 {
229 unsafe { self.fields[field_idx].read_f64(row) }
230 }
231
232 /// Reads an INTERVAL value.
233 ///
234 /// # Safety
235 ///
236 /// See [`read_bool`][Self::read_bool].
237 #[inline]
238 pub unsafe fn read_interval(&self, row: usize, field_idx: usize) -> DuckInterval {
239 unsafe { self.fields[field_idx].read_interval(row) }
240 }
241
242 /// Reads a DATE value (days since epoch).
243 ///
244 /// # Safety
245 ///
246 /// See [`read_bool`][Self::read_bool].
247 #[inline]
248 pub unsafe fn read_date(&self, row: usize, field_idx: usize) -> i32 {
249 unsafe { self.fields[field_idx].read_date(row) }
250 }
251
252 /// Reads a TIMESTAMP value (microseconds since epoch).
253 ///
254 /// # Safety
255 ///
256 /// See [`read_bool`][Self::read_bool].
257 #[inline]
258 pub unsafe fn read_timestamp(&self, row: usize, field_idx: usize) -> i64 {
259 unsafe { self.fields[field_idx].read_timestamp(row) }
260 }
261
262 /// Reads a TIME value (microseconds since midnight).
263 ///
264 /// # Safety
265 ///
266 /// See [`read_bool`][Self::read_bool].
267 #[inline]
268 pub unsafe fn read_time(&self, row: usize, field_idx: usize) -> i64 {
269 unsafe { self.fields[field_idx].read_time(row) }
270 }
271
272 /// Reads a `BLOB` (binary) value from field `field_idx` at row `row`.
273 ///
274 /// # Safety
275 ///
276 /// See [`read_bool`][Self::read_bool].
277 #[inline]
278 pub unsafe fn read_blob(&self, row: usize, field_idx: usize) -> &[u8] {
279 unsafe { self.fields[field_idx].read_blob(row) }
280 }
281
282 /// Reads a `UUID` value (as i128) from field `field_idx` at row `row`.
283 ///
284 /// # Safety
285 ///
286 /// See [`read_bool`][Self::read_bool].
287 #[inline]
288 pub unsafe fn read_uuid(&self, row: usize, field_idx: usize) -> i128 {
289 unsafe { self.fields[field_idx].read_uuid(row) }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn struct_reader_field_count() {
299 let sr = StructReader { fields: Vec::new() };
300 assert_eq!(sr.field_count(), 0);
301 }
302
303 #[test]
304 fn size_of_struct_reader() {
305 assert_eq!(
306 std::mem::size_of::<StructReader>(),
307 3 * std::mem::size_of::<usize>() // Vec = ptr + len + cap
308 );
309 }
310}