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    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}