Skip to main content

duckdb/core/vector/
list.rs

1use std::cell::Cell;
2
3use crate::{
4    Result,
5    error::duckdb_failure_from_message,
6    ffi::{
7        DuckDBSuccess, duckdb_list_entry, duckdb_list_vector_get_child, duckdb_list_vector_get_size,
8        duckdb_list_vector_reserve, duckdb_list_vector_set_size, duckdb_state, duckdb_validity_set_row_invalid,
9        duckdb_vector, duckdb_vector_ensure_validity_writable, duckdb_vector_get_validity, duckdb_vector_size,
10    },
11};
12
13use super::{ArrayVector, FlatVector, StructVector};
14
15/// Mirrors DuckDB's `DConstants::MAX_VECTOR_SIZE`, the largest allocation a
16/// single vector can hold (not the per-chunk row count reported by
17/// `duckdb_vector_size()`). `ListVector::Reserve` throws a C++ exception for
18/// larger requests, and `duckdb_list_vector_reserve` does not catch it, so the
19/// exception would cross the non-unwinding C boundary and abort the process.
20/// Rejecting such requests up front keeps them recoverable errors.
21pub(super) const MAX_VECTOR_SIZE: u64 = 1 << 37;
22
23fn list_vector_state_result(state: duckdb_state, action: &str, size: usize) -> Result<()> {
24    if state == DuckDBSuccess {
25        Ok(())
26    } else {
27        Err(duckdb_failure_from_message(format!(
28            "failed to {action} {size} elements in DuckDB list vector"
29        )))
30    }
31}
32
33/// A list vector borrowed from a [`DataChunkHandle`][crate::core::DataChunkHandle].
34///
35/// Stores list entry offsets and lengths; elements live in a separate child vector.
36///
37/// Regression guard for #673 — a `ListVector` must not outlive its parent chunk:
38///
39/// ```compile_fail
40/// use duckdb::core::{DataChunkHandle, LogicalTypeHandle, LogicalTypeId};
41///
42/// let vec;
43/// {
44///     let list_type = LogicalTypeHandle::list(&LogicalTypeId::Integer.into());
45///     let chunk = DataChunkHandle::new(&[list_type]);
46///     chunk.set_len(1);
47///     let v = chunk.list_vector(0);
48///     vec = v;
49/// }
50/// // chunk goes out of scope here; borrow checking rejects the outer use.
51/// let _ = vec.get_entry(0);
52/// ```
53pub struct ListVector<'a> {
54    /// ListVector does not own the vector pointer.
55    entries: FlatVector<'a>,
56    reserved_child_capacity: Cell<usize>,
57}
58
59impl<'a> ListVector<'a> {
60    /// Wrap a raw `duckdb_vector` pointer.
61    ///
62    /// # Safety
63    /// `ptr` must be a valid `duckdb_vector` that remains valid for all of `'a`.
64    pub(crate) unsafe fn from_raw(ptr: duckdb_vector) -> Self {
65        Self {
66            entries: unsafe { FlatVector::from_raw(ptr) },
67            reserved_child_capacity: Cell::new(0),
68        }
69    }
70
71    /// Wrap a raw list vector pointer with a caller-supplied capacity.
72    ///
73    /// # Safety
74    /// `ptr` must be a valid list `duckdb_vector` that remains valid for all of
75    /// `'a`, and `capacity` must not exceed the backing allocation available for
76    /// this vector's list entries. Any committed child size reported by DuckDB
77    /// must also be covered by the child allocation, as it is when the size was
78    /// committed through this wrapper or produced by DuckDB.
79    pub(crate) unsafe fn from_raw_with_capacity(ptr: duckdb_vector, capacity: usize) -> Self {
80        Self {
81            entries: unsafe { FlatVector::with_capacity(ptr, capacity) },
82            reserved_child_capacity: Cell::new(0),
83        }
84    }
85
86    /// Returns the number of child elements stored by the list vector.
87    ///
88    /// This is the upper bound for the end of non-empty list entries, not the
89    /// number of parent list rows.
90    pub fn len(&self) -> usize {
91        unsafe { duckdb_list_vector_get_size(self.entries.ptr) as usize }
92    }
93
94    /// Returns true if the list vector is empty.
95    pub fn is_empty(&self) -> bool {
96        self.len() == 0
97    }
98
99    /// Returns the child vector, reserving space for `capacity` child values.
100    ///
101    /// # Panics
102    /// Panics if DuckDB rejects the reserve.
103    // TODO: not ideal interface. Where should we keep capacity.
104    pub fn child(&self, capacity: usize) -> FlatVector<'a> {
105        let ptr = self
106            .try_child_ptr_with_capacity(capacity)
107            .unwrap_or_else(|err| panic!("{err}"));
108        // SAFETY: DuckDB accepted the reserve for `capacity` child values.
109        unsafe { FlatVector::with_capacity(ptr, capacity) }
110    }
111
112    /// Take the child as [StructVector], reserving space for `capacity` child
113    /// values.
114    ///
115    /// # Panics
116    /// Panics if DuckDB rejects the reserve.
117    pub fn struct_child(&self, capacity: usize) -> StructVector<'a> {
118        let ptr = self
119            .try_child_ptr_with_capacity(capacity)
120            .unwrap_or_else(|err| panic!("{err}"));
121        // SAFETY: DuckDB accepted the reserve for `capacity` child values.
122        unsafe { StructVector::from_raw_with_capacity(ptr, capacity) }
123    }
124
125    /// Capacity of the child vector's backing storage.
126    ///
127    /// DuckDB initializes list children with standard vector capacity. This
128    /// wrapper tracks larger explicit reservations, and larger committed sizes
129    /// must already have been reserved; the safe size setters and
130    /// DuckDB-produced chunks preserve that invariant.
131    fn child_capacity(&self) -> usize {
132        self.len()
133            .max(self.reserved_child_capacity.get())
134            .max(unsafe { duckdb_vector_size() as usize })
135    }
136
137    /// Take the child as [ArrayVector].
138    pub fn array_child(&self) -> ArrayVector<'a> {
139        // SAFETY: `child_capacity` is covered by the child's backing storage.
140        unsafe {
141            ArrayVector::from_raw_with_capacity(duckdb_list_vector_get_child(self.entries.ptr), self.child_capacity())
142        }
143    }
144
145    /// Take the child as [ListVector].
146    pub fn list_child(&self) -> ListVector<'a> {
147        // SAFETY: `child_capacity` is covered by the child's backing storage.
148        unsafe {
149            ListVector::from_raw_with_capacity(duckdb_list_vector_get_child(self.entries.ptr), self.child_capacity())
150        }
151    }
152
153    pub(crate) fn try_child_ptr_with_capacity(&self, capacity: usize) -> Result<duckdb_vector> {
154        self.try_reserve(capacity)?;
155        Ok(unsafe { duckdb_list_vector_get_child(self.entries.ptr) })
156    }
157
158    #[cfg(feature = "vtab-arrow")]
159    pub(crate) fn child_ptr(&self) -> duckdb_vector {
160        unsafe { duckdb_list_vector_get_child(self.entries.ptr) }
161    }
162
163    /// Returns true if the row at the given index is null.
164    ///
165    /// # Panics
166    /// Panics if `row` is outside the vector capacity.
167    pub fn row_is_null(&self, row: u64) -> bool {
168        self.entries.row_is_null(row)
169    }
170
171    /// Returns whether the row is null, or an error if it is out of range.
172    pub fn try_row_is_null(&self, row: u64) -> Result<bool> {
173        self.entries.try_row_is_null(row)
174    }
175
176    /// Set primitive data to the child node.
177    ///
178    /// Reserves child storage and commits the list length to `data.len()`.
179    ///
180    /// # Panics
181    /// Panics if DuckDB rejects the reserve or size update.
182    ///
183    /// # Safety
184    /// The caller must ensure `T` matches the child vector's physical storage
185    /// and no other references to the same storage exist during the copy.
186    pub unsafe fn set_child<T: Copy>(&self, data: &[T]) {
187        unsafe { self.child(data.len()).copy(data) };
188        self.set_len(data.len());
189    }
190
191    /// Set offset and length to the entry.
192    ///
193    /// # Panics
194    /// Panics if `idx` is outside the vector capacity.
195    pub fn set_entry(&mut self, idx: usize, offset: usize, length: usize) {
196        assert!(
197            idx < self.entries.capacity(),
198            "list entry row {idx} exceeds vector capacity {}",
199            self.entries.capacity()
200        );
201        // SAFETY: the bounds check above keeps the write inside the entry
202        // allocation, and a single-slot write avoids materializing a slice
203        // over neighboring entries DuckDB may have left uninitialized.
204        unsafe {
205            self.entries
206                .as_mut_ptr::<duckdb_list_entry>()
207                .add(idx)
208                .write(duckdb_list_entry {
209                    offset: offset as u64,
210                    length: length as u64,
211                });
212        }
213    }
214
215    /// Get offset and length for the entry at index.
216    ///
217    /// # Panics
218    /// Panics if the entry is out of range or its offset or length cannot fit
219    /// in `usize` on the current target.
220    pub fn get_entry(&self, idx: usize) -> (usize, usize) {
221        self.try_get_entry(idx).unwrap_or_else(|err| panic!("{err}"))
222    }
223
224    /// Returns the entry offset and length, or an error if the entry is out of
225    /// range or cannot be represented on the current target.
226    pub fn try_get_entry(&self, idx: usize) -> Result<(usize, usize)> {
227        if idx >= self.entries.capacity() {
228            return Err(duckdb_failure_from_message(format!(
229                "list entry row {idx} exceeds vector capacity {}",
230                self.entries.capacity()
231            )));
232        }
233        // SAFETY: the bounds check above keeps the read inside the entry
234        // allocation, and a single-slot read avoids materializing a slice
235        // over neighboring entries DuckDB may have left uninitialized.
236        let entry = unsafe { self.entries.as_mut_ptr::<duckdb_list_entry>().add(idx).read() };
237        let offset = usize::try_from(entry.offset).map_err(|_| {
238            duckdb_failure_from_message(format!(
239                "DuckDB list entry offset {} at row {idx} exceeds usize range",
240                entry.offset
241            ))
242        })?;
243        let length = usize::try_from(entry.length).map_err(|_| {
244            duckdb_failure_from_message(format!(
245                "DuckDB list entry length {} at row {idx} exceeds usize range",
246                entry.length
247            ))
248        })?;
249        Ok((offset, length))
250    }
251
252    /// Set row as null
253    pub fn set_null(&mut self, row: usize) {
254        unsafe {
255            duckdb_vector_ensure_validity_writable(self.entries.ptr);
256            let idx = duckdb_vector_get_validity(self.entries.ptr);
257            duckdb_validity_set_row_invalid(idx, row as u64);
258        }
259    }
260
261    /// Reserve space for `capacity` child values.
262    ///
263    /// This is public so callers can handle reservation errors without the
264    /// panic-based convenience methods. Requests beyond DuckDB's maximum
265    /// vector size (`2^37` elements) are rejected here as `Err`, because
266    /// DuckDB reports them with a C++ exception the C API does not catch.
267    /// Allocation failure inside DuckDB escapes the same way and aborts the
268    /// process; it cannot be returned as an error through the current C API.
269    pub fn try_reserve(&self, capacity: usize) -> Result<()> {
270        if capacity as u64 > MAX_VECTOR_SIZE {
271            return Err(duckdb_failure_from_message(format!(
272                "cannot reserve {capacity} elements in DuckDB list vector: exceeds maximum vector size {MAX_VECTOR_SIZE}"
273            )));
274        }
275        let state = unsafe { duckdb_list_vector_reserve(self.entries.ptr, capacity as u64) };
276        list_vector_state_result(state, "reserve child storage for", capacity)?;
277        self.reserved_child_capacity
278            .set(self.reserved_child_capacity.get().max(capacity));
279        Ok(())
280    }
281
282    /// Reserve child storage and set the length of the list vector.
283    ///
284    /// Panics if DuckDB rejects the reserve or size update. Use
285    /// [`Self::try_set_len`] to handle that error explicitly.
286    pub fn set_len(&self, new_len: usize) {
287        self.try_set_len(new_len).unwrap_or_else(|err| panic!("{err}"));
288    }
289
290    /// Reserve child storage and set the length of the list vector.
291    pub fn try_set_len(&self, new_len: usize) -> Result<()> {
292        self.try_reserve(new_len)?;
293        let state = unsafe { duckdb_list_vector_set_size(self.entries.ptr, new_len as u64) };
294        list_vector_state_result(state, "set size to", new_len)
295    }
296}