Skip to main content

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    vector: duckdb_vector,
39    fields: Vec<VectorReader>,
40}
41
42impl StructReader {
43    /// Creates a new `StructReader` for a STRUCT vector with `field_count` fields.
44    ///
45    /// # Safety
46    ///
47    /// - `vector` must be a valid `DuckDB` STRUCT vector.
48    /// - `field_count` must match the number of fields in the STRUCT type.
49    /// - `row_count` must match the number of rows in the parent chunk.
50    /// - The vector must remain valid for the lifetime of this reader.
51    pub unsafe fn new(vector: duckdb_vector, field_count: usize, row_count: usize) -> Self {
52        let mut fields = Vec::with_capacity(field_count);
53        for idx in 0..field_count {
54            // SAFETY: caller guarantees vector is valid STRUCT with field_count fields.
55            fields.push(unsafe { StructVector::field_reader(vector, idx, row_count) });
56        }
57        Self { vector, fields }
58    }
59
60    /// Returns the number of fields in this struct reader.
61    #[mutants::skip]
62    #[must_use]
63    #[inline]
64    pub fn field_count(&self) -> usize {
65        self.fields.len()
66    }
67
68    /// Returns a reference to the [`VectorReader`] for the given field.
69    ///
70    /// # Panics
71    ///
72    /// Panics if `field_idx >= field_count`.
73    #[must_use]
74    #[inline]
75    pub fn field(&self, field_idx: usize) -> &VectorReader {
76        &self.fields[field_idx]
77    }
78
79    /// Returns the raw `duckdb_vector` handle for the given field.
80    ///
81    /// Use this when a struct field has a complex type (LIST, MAP, ARRAY) that
82    /// requires operations beyond simple scalar reads — for example, calling
83    /// [`ListVector::get_entry`][crate::vector::complex::ListVector::get_entry] or
84    /// [`ListVector::child_reader`][crate::vector::complex::ListVector::child_reader].
85    ///
86    /// # Safety
87    ///
88    /// - `field_idx` must be a valid field index (0 ≤ `field_idx` < `field_count`).
89    /// - The returned vector is borrowed from the parent STRUCT vector and must
90    ///   not outlive it.
91    #[must_use]
92    #[inline]
93    pub unsafe fn child_vector(&self, field_idx: usize) -> duckdb_vector {
94        unsafe { StructVector::get_child(self.vector, field_idx) }
95    }
96
97    /// Returns `true` if the value at `row` in field `field_idx` is not NULL.
98    ///
99    /// # Safety
100    ///
101    /// `row` must be less than the row count.
102    ///
103    /// # Panics
104    ///
105    /// Panics if `field_idx >= field_count`.
106    #[inline]
107    pub unsafe fn is_valid(&self, row: usize, field_idx: usize) -> bool {
108        unsafe { self.fields[field_idx].is_valid(row) }
109    }
110
111    /// Reads a `bool` (BOOLEAN) value from field `field_idx` at row `row`.
112    ///
113    /// # Safety
114    ///
115    /// - `row` must be less than the row count.
116    /// - The field at `field_idx` must have `BOOLEAN` type.
117    ///
118    /// # Panics
119    ///
120    /// Panics if `field_idx >= field_count`.
121    #[inline]
122    pub unsafe fn read_bool(&self, row: usize, field_idx: usize) -> bool {
123        unsafe { self.fields[field_idx].read_bool(row) }
124    }
125
126    /// Reads a VARCHAR value from field `field_idx` at row `row`.
127    ///
128    /// # Safety
129    ///
130    /// - `row` must be less than the row count.
131    /// - The field at `field_idx` must have `VARCHAR` type.
132    ///
133    /// # Panics
134    ///
135    /// Panics if `field_idx >= field_count`.
136    #[inline]
137    pub unsafe fn read_str(&self, row: usize, field_idx: usize) -> &str {
138        unsafe { self.fields[field_idx].read_str(row) }
139    }
140
141    /// Reads an `i8` (TINYINT) value from field `field_idx` at row `row`.
142    ///
143    /// # Safety
144    ///
145    /// See [`read_bool`][Self::read_bool].
146    #[inline]
147    pub unsafe fn read_i8(&self, row: usize, field_idx: usize) -> i8 {
148        unsafe { self.fields[field_idx].read_i8(row) }
149    }
150
151    /// Reads an `i16` (SMALLINT) value.
152    ///
153    /// # Safety
154    ///
155    /// See [`read_bool`][Self::read_bool].
156    #[inline]
157    pub unsafe fn read_i16(&self, row: usize, field_idx: usize) -> i16 {
158        unsafe { self.fields[field_idx].read_i16(row) }
159    }
160
161    /// Reads an `i32` (INTEGER) value.
162    ///
163    /// # Safety
164    ///
165    /// See [`read_bool`][Self::read_bool].
166    #[inline]
167    pub unsafe fn read_i32(&self, row: usize, field_idx: usize) -> i32 {
168        unsafe { self.fields[field_idx].read_i32(row) }
169    }
170
171    /// Reads an `i64` (BIGINT) value.
172    ///
173    /// # Safety
174    ///
175    /// See [`read_bool`][Self::read_bool].
176    #[inline]
177    pub unsafe fn read_i64(&self, row: usize, field_idx: usize) -> i64 {
178        unsafe { self.fields[field_idx].read_i64(row) }
179    }
180
181    /// Reads an `i128` (HUGEINT) value.
182    ///
183    /// # Safety
184    ///
185    /// See [`read_bool`][Self::read_bool].
186    #[inline]
187    pub unsafe fn read_i128(&self, row: usize, field_idx: usize) -> i128 {
188        unsafe { self.fields[field_idx].read_i128(row) }
189    }
190
191    /// Reads a `u8` (UTINYINT) value.
192    ///
193    /// # Safety
194    ///
195    /// See [`read_bool`][Self::read_bool].
196    #[inline]
197    pub unsafe fn read_u8(&self, row: usize, field_idx: usize) -> u8 {
198        unsafe { self.fields[field_idx].read_u8(row) }
199    }
200
201    /// Reads a `u16` (USMALLINT) value.
202    ///
203    /// # Safety
204    ///
205    /// See [`read_bool`][Self::read_bool].
206    #[inline]
207    pub unsafe fn read_u16(&self, row: usize, field_idx: usize) -> u16 {
208        unsafe { self.fields[field_idx].read_u16(row) }
209    }
210
211    /// Reads a `u32` (UINTEGER) value.
212    ///
213    /// # Safety
214    ///
215    /// See [`read_bool`][Self::read_bool].
216    #[inline]
217    pub unsafe fn read_u32(&self, row: usize, field_idx: usize) -> u32 {
218        unsafe { self.fields[field_idx].read_u32(row) }
219    }
220
221    /// Reads a `u64` (UBIGINT) value.
222    ///
223    /// # Safety
224    ///
225    /// See [`read_bool`][Self::read_bool].
226    #[inline]
227    pub unsafe fn read_u64(&self, row: usize, field_idx: usize) -> u64 {
228        unsafe { self.fields[field_idx].read_u64(row) }
229    }
230
231    /// Reads an `f32` (FLOAT) value.
232    ///
233    /// # Safety
234    ///
235    /// See [`read_bool`][Self::read_bool].
236    #[inline]
237    pub unsafe fn read_f32(&self, row: usize, field_idx: usize) -> f32 {
238        unsafe { self.fields[field_idx].read_f32(row) }
239    }
240
241    /// Reads an `f64` (DOUBLE) value.
242    ///
243    /// # Safety
244    ///
245    /// See [`read_bool`][Self::read_bool].
246    #[inline]
247    pub unsafe fn read_f64(&self, row: usize, field_idx: usize) -> f64 {
248        unsafe { self.fields[field_idx].read_f64(row) }
249    }
250
251    /// Reads an INTERVAL value.
252    ///
253    /// # Safety
254    ///
255    /// See [`read_bool`][Self::read_bool].
256    #[inline]
257    pub unsafe fn read_interval(&self, row: usize, field_idx: usize) -> DuckInterval {
258        unsafe { self.fields[field_idx].read_interval(row) }
259    }
260
261    /// Reads a DATE value (days since epoch).
262    ///
263    /// # Safety
264    ///
265    /// See [`read_bool`][Self::read_bool].
266    #[inline]
267    pub unsafe fn read_date(&self, row: usize, field_idx: usize) -> i32 {
268        unsafe { self.fields[field_idx].read_date(row) }
269    }
270
271    /// Reads a TIMESTAMP value (microseconds since epoch).
272    ///
273    /// # Safety
274    ///
275    /// See [`read_bool`][Self::read_bool].
276    #[inline]
277    pub unsafe fn read_timestamp(&self, row: usize, field_idx: usize) -> i64 {
278        unsafe { self.fields[field_idx].read_timestamp(row) }
279    }
280
281    /// Reads a TIME value (microseconds since midnight).
282    ///
283    /// # Safety
284    ///
285    /// See [`read_bool`][Self::read_bool].
286    #[inline]
287    pub unsafe fn read_time(&self, row: usize, field_idx: usize) -> i64 {
288        unsafe { self.fields[field_idx].read_time(row) }
289    }
290
291    /// Reads a `BLOB` (binary) value from field `field_idx` at row `row`.
292    ///
293    /// # Safety
294    ///
295    /// See [`read_bool`][Self::read_bool].
296    #[inline]
297    pub unsafe fn read_blob(&self, row: usize, field_idx: usize) -> &[u8] {
298        unsafe { self.fields[field_idx].read_blob(row) }
299    }
300
301    /// Reads a `UUID` value (as i128) from field `field_idx` at row `row`.
302    ///
303    /// # Safety
304    ///
305    /// See [`read_bool`][Self::read_bool].
306    #[inline]
307    pub unsafe fn read_uuid(&self, row: usize, field_idx: usize) -> i128 {
308        unsafe { self.fields[field_idx].read_uuid(row) }
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    #[test]
317    fn struct_reader_field_count() {
318        let sr = StructReader {
319            vector: std::ptr::null_mut(),
320            fields: Vec::new(),
321        };
322        assert_eq!(sr.field_count(), 0);
323    }
324
325    #[test]
326    fn size_of_struct_reader() {
327        assert_eq!(
328            std::mem::size_of::<StructReader>(),
329            4 * std::mem::size_of::<usize>() // vector ptr + Vec (ptr + len + cap)
330        );
331    }
332}