Skip to main content

quack_rs/vector/
complex.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//! Complex type vector operations: STRUCT fields, LIST elements, MAP entries.
7//!
8//! `DuckDB` stores complex types as nested vectors:
9//!
10//! - **STRUCT**: a parent vector with N child vectors, one per field.
11//! - **LIST**: a parent vector holding `duckdb_list_entry { offset, length }` per row,
12//!   plus a single flat child vector containing all elements end-to-end.
13//! - **MAP**: stored as `LIST<STRUCT{key, value}>` — the list's child vector is a
14//!   STRUCT with two children: `key` (index 0) and `value` (index 1).
15//!
16//! # Reading vs writing
17//!
18//! - Use [`StructVector`] / [`ListVector`] / [`MapVector`] to access child vectors
19//!   from input or output vectors.
20//! - Child vectors are themselves `duckdb_vector` handles — pass them to
21//!   [`VectorReader`] or
22//!   [`VectorWriter`] to read/write the actual values.
23//!
24//! # Example: Reading a STRUCT column
25//!
26//! ```rust,no_run
27//! use quack_rs::vector::{VectorReader, complex::StructVector};
28//! use libduckdb_sys::{duckdb_data_chunk, duckdb_data_chunk_get_vector};
29//!
30//! // Inside a table function scan callback:
31//! // let parent_vec = unsafe { duckdb_data_chunk_get_vector(chunk, 0) };
32//! // let x_vec = StructVector::get_child(parent_vec, 0); // field index 0
33//! // let x_reader = unsafe { VectorReader::from_vector(x_vec, row_count) };
34//! // let x: f64 = unsafe { x_reader.read_f64(row_idx) };
35//! ```
36//!
37//! # Example: Writing a LIST column
38//!
39//! ```rust,no_run
40//! use quack_rs::vector::{VectorWriter, complex::ListVector};
41//! use libduckdb_sys::{duckdb_data_chunk_get_vector, duckdb_data_chunk};
42//!
43//! // Inside a scan callback:
44//! // let list_vec = unsafe { duckdb_data_chunk_get_vector(output, 0) };
45//! // // Write 3 elements for row 0: [10, 20, 30]
46//! // ListVector::reserve(list_vec, 3);
47//! // ListVector::set_size(list_vec, 3);
48//! // // Write the list offset/length entry for row 0.
49//! // ListVector::set_entry(list_vec, 0, 0, 3); // row=0, offset=0, length=3
50//! // // Write values into the child vector.
51//! // let child = ListVector::get_child(list_vec);
52//! // let mut writer = unsafe { VectorWriter::from_vector(child) };
53//! // unsafe { writer.write_i64(0, 10); writer.write_i64(1, 20); writer.write_i64(2, 30); }
54//! ```
55
56use libduckdb_sys::{
57    duckdb_array_vector_get_child, duckdb_list_entry, duckdb_list_vector_get_child,
58    duckdb_list_vector_get_size, duckdb_list_vector_reserve, duckdb_list_vector_set_size,
59    duckdb_struct_vector_get_child, duckdb_vector, duckdb_vector_get_data, idx_t,
60};
61
62use crate::vector::{VectorReader, VectorWriter};
63
64// ─── STRUCT ──────────────────────────────────────────────────────────────────
65
66/// Operations on STRUCT vectors (accessing child field vectors).
67#[derive(Debug)]
68pub struct StructVector;
69
70impl StructVector {
71    /// Returns the child vector for the given field index of a STRUCT vector.
72    ///
73    /// Field indices correspond to the order of fields in the STRUCT type definition.
74    ///
75    /// # Safety
76    ///
77    /// - `vector` must be a valid `DuckDB` STRUCT vector.
78    /// - `field_idx` must be a valid field index (0 ≤ `field_idx` < number of struct fields).
79    /// - The returned vector is borrowed from `vector` and must not outlive it.
80    #[inline]
81    #[must_use]
82    pub unsafe fn get_child(vector: duckdb_vector, field_idx: usize) -> duckdb_vector {
83        // SAFETY: caller guarantees vector is a valid STRUCT vector and field_idx is valid.
84        unsafe { duckdb_struct_vector_get_child(vector, field_idx as idx_t) }
85    }
86
87    /// Creates a [`VectorReader`] for the given field of a STRUCT vector.
88    ///
89    /// # Safety
90    ///
91    /// - `vector` must be a valid `DuckDB` STRUCT vector.
92    /// - `field_idx` must be a valid field index.
93    /// - `row_count` must match the number of rows in the parent chunk.
94    pub unsafe fn field_reader(
95        vector: duckdb_vector,
96        field_idx: usize,
97        row_count: usize,
98    ) -> VectorReader {
99        let child = unsafe { Self::get_child(vector, field_idx) };
100        // SAFETY: child is a valid vector with row_count rows.
101        unsafe { VectorReader::from_vector(child, row_count) }
102    }
103
104    /// Creates a [`VectorWriter`] for the given field of a STRUCT vector.
105    ///
106    /// # Safety
107    ///
108    /// - `vector` must be a valid `DuckDB` STRUCT vector.
109    /// - `field_idx` must be a valid field index.
110    pub unsafe fn field_writer(vector: duckdb_vector, field_idx: usize) -> VectorWriter {
111        let child = unsafe { Self::get_child(vector, field_idx) };
112        // SAFETY: child is a valid writable vector.
113        unsafe { VectorWriter::from_vector(child) }
114    }
115}
116
117// ─── LIST ────────────────────────────────────────────────────────────────────
118
119/// Operations on LIST vectors.
120///
121/// A LIST vector stores a `duckdb_list_entry { offset: u64, length: u64 }` per row
122/// in the parent vector, and all element values in a flat child vector.
123///
124/// # Write workflow
125///
126/// 1. [`reserve`][ListVector::reserve] — ensure child vector has capacity.
127/// 2. Write element values into the child via [`get_child`][ListVector::get_child] + [`VectorWriter`].
128/// 3. [`set_size`][ListVector::set_size] — tell `DuckDB` how many elements were written.
129/// 4. [`set_entry`][ListVector::set_entry] — write the offset/length for each parent row.
130#[derive(Debug)]
131pub struct ListVector;
132
133impl ListVector {
134    /// Returns the child vector containing all list elements (flat, across all rows).
135    ///
136    /// # Safety
137    ///
138    /// - `vector` must be a valid `DuckDB` LIST vector.
139    /// - The returned handle is borrowed from `vector`.
140    #[inline]
141    #[must_use]
142    pub unsafe fn get_child(vector: duckdb_vector) -> duckdb_vector {
143        // SAFETY: caller guarantees vector is a valid LIST vector.
144        unsafe { duckdb_list_vector_get_child(vector) }
145    }
146
147    /// Returns the total number of elements currently in the child vector.
148    ///
149    /// # Safety
150    ///
151    /// `vector` must be a valid `DuckDB` LIST vector.
152    #[inline]
153    #[must_use]
154    pub unsafe fn get_size(vector: duckdb_vector) -> usize {
155        usize::try_from(unsafe { duckdb_list_vector_get_size(vector) }).unwrap_or(0)
156    }
157
158    /// Sets the number of elements in the child vector.
159    ///
160    /// Call after writing all element values. `DuckDB` uses this to know how many
161    /// child elements are valid.
162    ///
163    /// # Safety
164    ///
165    /// - `vector` must be a valid `DuckDB` LIST vector.
166    /// - `size` must equal the number of elements written into the child vector.
167    #[inline]
168    pub unsafe fn set_size(vector: duckdb_vector, size: usize) {
169        // SAFETY: caller guarantees vector is valid.
170        unsafe { duckdb_list_vector_set_size(vector, size as idx_t) };
171    }
172
173    /// Reserves capacity in the child vector for at least `capacity` elements.
174    ///
175    /// Call before writing elements to ensure the child vector has enough space.
176    ///
177    /// # Safety
178    ///
179    /// `vector` must be a valid `DuckDB` LIST vector.
180    #[inline]
181    pub unsafe fn reserve(vector: duckdb_vector, capacity: usize) {
182        // SAFETY: caller guarantees vector is valid.
183        unsafe { duckdb_list_vector_reserve(vector, capacity as idx_t) };
184    }
185
186    /// Writes the offset/length metadata entry for a parent row.
187    ///
188    /// This tells `DuckDB` where in the flat child vector this row's elements start
189    /// and how many elements it has.
190    ///
191    /// # Safety
192    ///
193    /// - `vector` must be a valid `DuckDB` LIST vector.
194    /// - `row_idx` must be a valid row index in the parent vector.
195    /// - `offset + length` must not exceed the size of the child vector.
196    pub unsafe fn set_entry(vector: duckdb_vector, row_idx: usize, offset: u64, length: u64) {
197        // SAFETY: vector is valid; we write to the parent vector's data at row_idx.
198        let data = unsafe { duckdb_vector_get_data(vector) };
199        // The parent stores duckdb_list_entry per row. Each entry is { offset: u64, length: u64 }.
200        let entry_ptr = unsafe { data.cast::<duckdb_list_entry>().add(row_idx) };
201        // SAFETY: entry_ptr is in bounds for the allocated vector.
202        unsafe {
203            (*entry_ptr).offset = offset;
204            (*entry_ptr).length = length;
205        }
206    }
207
208    /// Returns the `duckdb_list_entry` for a given row (for reading).
209    ///
210    /// # Safety
211    ///
212    /// - `vector` must be a valid `DuckDB` LIST vector.
213    /// - `row_idx` must be a valid row index.
214    #[must_use]
215    pub unsafe fn get_entry(vector: duckdb_vector, row_idx: usize) -> duckdb_list_entry {
216        let data = unsafe { duckdb_vector_get_data(vector) };
217        let entry_ptr = unsafe { data.cast::<duckdb_list_entry>().add(row_idx) };
218        // SAFETY: entry_ptr is valid and initialized by DuckDB or a prior set_entry call.
219        unsafe { core::ptr::read_unaligned(entry_ptr) }
220    }
221
222    /// Creates a [`VectorWriter`] for the child vector (elements).
223    ///
224    /// # Safety
225    ///
226    /// - `vector` must be a valid `DuckDB` LIST vector.
227    /// - The child must have been reserved with at least `capacity` elements.
228    pub unsafe fn child_writer(vector: duckdb_vector) -> VectorWriter {
229        let child = unsafe { Self::get_child(vector) };
230        unsafe { VectorWriter::from_vector(child) }
231    }
232
233    /// Creates a [`VectorReader`] for the child vector (reading list elements).
234    ///
235    /// # Safety
236    ///
237    /// - `vector` must be a valid `DuckDB` LIST vector.
238    /// - `element_count` must equal the total number of elements in the child.
239    pub unsafe fn child_reader(vector: duckdb_vector, element_count: usize) -> VectorReader {
240        let child = unsafe { Self::get_child(vector) };
241        unsafe { VectorReader::from_vector(child, element_count) }
242    }
243}
244
245// ─── MAP ─────────────────────────────────────────────────────────────────────
246
247/// Operations on MAP vectors.
248///
249/// `DuckDB` stores maps as `LIST<STRUCT{key: K, value: V}>`.
250/// The child of the list vector is a STRUCT vector with two fields:
251/// - field index 0: keys
252/// - field index 1: values
253///
254/// # Example
255///
256/// ```rust,no_run
257/// use quack_rs::vector::complex::MapVector;
258/// use libduckdb_sys::duckdb_vector;
259///
260/// // Reading MAP keys from a MAP vector:
261/// // let keys_vec = unsafe { MapVector::keys(map_vector) };
262/// // let vals_vec = unsafe { MapVector::values(map_vector) };
263/// ```
264#[derive(Debug)]
265pub struct MapVector;
266
267impl MapVector {
268    /// Returns the child STRUCT vector (contains both keys and values as fields).
269    ///
270    /// # Safety
271    ///
272    /// `vector` must be a valid `DuckDB` MAP vector.
273    #[inline]
274    #[must_use]
275    pub unsafe fn struct_child(vector: duckdb_vector) -> duckdb_vector {
276        // MAP is LIST<STRUCT{key,value}>, so the list child is a STRUCT vector.
277        unsafe { duckdb_list_vector_get_child(vector) }
278    }
279
280    /// Returns the keys vector (STRUCT field 0 of the MAP's child).
281    ///
282    /// # Safety
283    ///
284    /// `vector` must be a valid `DuckDB` MAP vector.
285    #[inline]
286    #[must_use]
287    pub unsafe fn keys(vector: duckdb_vector) -> duckdb_vector {
288        let struct_vec = unsafe { Self::struct_child(vector) };
289        // SAFETY: MAP child STRUCT always has key at field 0, value at field 1.
290        unsafe { duckdb_struct_vector_get_child(struct_vec, 0) }
291    }
292
293    /// Returns the values vector (STRUCT field 1 of the MAP's child).
294    ///
295    /// # Safety
296    ///
297    /// `vector` must be a valid `DuckDB` MAP vector.
298    #[inline]
299    #[must_use]
300    pub unsafe fn values(vector: duckdb_vector) -> duckdb_vector {
301        let struct_vec = unsafe { Self::struct_child(vector) };
302        // SAFETY: MAP child STRUCT always has key at field 0, value at field 1.
303        unsafe { duckdb_struct_vector_get_child(struct_vec, 1) }
304    }
305
306    /// Returns the total number of key-value pairs across all rows.
307    ///
308    /// # Safety
309    ///
310    /// `vector` must be a valid `DuckDB` MAP vector.
311    #[inline]
312    #[must_use]
313    pub unsafe fn total_entry_count(vector: duckdb_vector) -> usize {
314        usize::try_from(unsafe { duckdb_list_vector_get_size(vector) }).unwrap_or(0)
315    }
316
317    /// Reserves capacity in the MAP's child vector for at least `capacity` entries.
318    ///
319    /// # Safety
320    ///
321    /// `vector` must be a valid `DuckDB` MAP vector.
322    #[inline]
323    pub unsafe fn reserve(vector: duckdb_vector, capacity: usize) {
324        unsafe { duckdb_list_vector_reserve(vector, capacity as idx_t) };
325    }
326
327    /// Sets the total number of key-value entries written.
328    ///
329    /// # Safety
330    ///
331    /// `vector` must be a valid `DuckDB` MAP vector.
332    #[inline]
333    pub unsafe fn set_size(vector: duckdb_vector, size: usize) {
334        unsafe { duckdb_list_vector_set_size(vector, size as idx_t) };
335    }
336
337    /// Writes the offset/length metadata for a parent MAP row.
338    ///
339    /// This has the same semantics as [`ListVector::set_entry`], since MAP is a LIST.
340    ///
341    /// # Safety
342    ///
343    /// Same as [`ListVector::set_entry`].
344    #[inline]
345    pub unsafe fn set_entry(vector: duckdb_vector, row_idx: usize, offset: u64, length: u64) {
346        // SAFETY: same layout as ListVector.
347        unsafe { ListVector::set_entry(vector, row_idx, offset, length) };
348    }
349
350    /// Returns the `duckdb_list_entry` for a given MAP row (for reading).
351    ///
352    /// # Safety
353    ///
354    /// Same as [`ListVector::get_entry`].
355    #[must_use]
356    pub unsafe fn get_entry(vector: duckdb_vector, row_idx: usize) -> duckdb_list_entry {
357        unsafe { ListVector::get_entry(vector, row_idx) }
358    }
359
360    /// Creates a [`VectorWriter`] for the keys vector (STRUCT field 0).
361    ///
362    /// # Safety
363    ///
364    /// `vector` must be a valid `DuckDB` MAP vector.
365    pub unsafe fn key_writer(vector: duckdb_vector) -> VectorWriter {
366        let keys = unsafe { Self::keys(vector) };
367        // SAFETY: keys is a valid writable child vector.
368        unsafe { VectorWriter::from_vector(keys) }
369    }
370
371    /// Creates a [`VectorWriter`] for the values vector (STRUCT field 1).
372    ///
373    /// # Safety
374    ///
375    /// `vector` must be a valid `DuckDB` MAP vector.
376    pub unsafe fn value_writer(vector: duckdb_vector) -> VectorWriter {
377        let vals = unsafe { Self::values(vector) };
378        // SAFETY: vals is a valid writable child vector.
379        unsafe { VectorWriter::from_vector(vals) }
380    }
381
382    /// Creates a [`VectorReader`] for the keys vector.
383    ///
384    /// # Safety
385    ///
386    /// - `vector` must be a valid `DuckDB` MAP vector.
387    /// - `element_count` must equal the total number of key-value entries.
388    pub unsafe fn key_reader(vector: duckdb_vector, element_count: usize) -> VectorReader {
389        let keys = unsafe { Self::keys(vector) };
390        // SAFETY: keys is a valid vector with element_count elements.
391        unsafe { VectorReader::from_vector(keys, element_count) }
392    }
393
394    /// Creates a [`VectorReader`] for the values vector.
395    ///
396    /// # Safety
397    ///
398    /// - `vector` must be a valid `DuckDB` MAP vector.
399    /// - `element_count` must equal the total number of key-value entries.
400    pub unsafe fn value_reader(vector: duckdb_vector, element_count: usize) -> VectorReader {
401        let vals = unsafe { Self::values(vector) };
402        // SAFETY: vals is a valid vector with element_count elements.
403        unsafe { VectorReader::from_vector(vals, element_count) }
404    }
405}
406
407// ─── ARRAY ──────────────────────────────────────────────────────────────────
408
409/// Helpers for working with `ARRAY` vectors (fixed-size arrays).
410#[derive(Debug)]
411pub struct ArrayVector;
412
413impl ArrayVector {
414    /// Returns the child vector of an array vector.
415    ///
416    /// # Safety
417    ///
418    /// - `vector` must be a valid `DuckDB` ARRAY vector.
419    /// - The returned handle is borrowed from `vector` and must not outlive it.
420    #[inline]
421    #[must_use]
422    pub unsafe fn get_child(vector: duckdb_vector) -> duckdb_vector {
423        unsafe { duckdb_array_vector_get_child(vector) }
424    }
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430    use libduckdb_sys::duckdb_list_entry;
431
432    #[test]
433    fn list_entry_layout() {
434        // Verify duckdb_list_entry has the expected size (2 × u64 = 16 bytes).
435        assert_eq!(
436            core::mem::size_of::<duckdb_list_entry>(),
437            16,
438            "duckdb_list_entry should be {{ offset: u64, length: u64 }}"
439        );
440    }
441
442    #[test]
443    fn set_and_get_list_entry() {
444        // Simulate the list parent vector data buffer (one row).
445        let mut data = duckdb_list_entry {
446            offset: 0,
447            length: 0,
448        };
449        let vec_ptr: duckdb_vector = std::ptr::addr_of_mut!(data).cast();
450
451        // Write entry for row 0: offset=5, length=3.
452        // We bypass the actual DuckDB call and test the pointer arithmetic directly.
453        let entry_ptr = std::ptr::addr_of_mut!(data);
454        unsafe {
455            (*entry_ptr).offset = 5;
456            (*entry_ptr).length = 3;
457        }
458        assert_eq!(data.offset, 5);
459        assert_eq!(data.length, 3);
460        let _ = vec_ptr; // suppress unused warning; no FFI call possible without runtime
461    }
462}