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}