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}