better_duck_core/raw/owned_vector.rs
1//! RAII wrapper for a standalone, owned `duckdb_vector`.
2//!
3//! DuckDB vectors are usually *borrowed* from a data chunk (see the `udf` module's
4//! `VectorRef`/`VectorMut`), but `duckdb_create_vector` also builds a standalone,
5//! owned vector of a given logical type and capacity. [`OwnedVector`] owns one and
6//! destroys it exactly once on drop. Ownership lives here, outside the UDF-only
7//! internals, so later vector-mutation helpers can build typed read/write
8//! views on top of a single owner; access is through `&mut self`, so a mutable view
9//! is exclusive.
10// FFI pointer args are used safely inside `unsafe` blocks.
11#![allow(clippy::not_unsafe_ptr_arg_deref)]
12
13use crate::{
14 error::{EngineError, Error, Result},
15 ffi::{
16 duckdb_create_vector, duckdb_destroy_vector, duckdb_list_vector_get_size,
17 duckdb_list_vector_reserve, duckdb_list_vector_set_size, duckdb_slice_vector,
18 duckdb_validity_set_row_valid, duckdb_validity_set_row_validity, duckdb_vector,
19 duckdb_vector_copy_sel, duckdb_vector_ensure_validity_writable,
20 duckdb_vector_get_column_type, duckdb_vector_get_validity, idx_t,
21 },
22 helpers::duck_result::check_state,
23 raw::selection_vector::SelectionVector,
24 types::LogicalType,
25};
26
27/// An owned standalone `duckdb_vector` (destroyed once on drop).
28pub struct OwnedVector {
29 ptr: duckdb_vector,
30}
31
32impl OwnedVector {
33 /// Creates a flat vector of logical type `ty` with room for `capacity` rows.
34 ///
35 /// # Errors
36 ///
37 /// Returns `None` if DuckDB fails to allocate the vector.
38 #[must_use]
39 pub fn new(
40 ty: &LogicalType,
41 capacity: u64,
42 ) -> Option<OwnedVector> {
43 // SAFETY: `ty.as_raw()` is a valid logical type owned by `ty` for the call;
44 // DuckDB copies what it needs. A null return (allocation failure) → None.
45 let ptr = unsafe { duckdb_create_vector(ty.as_raw(), capacity as idx_t) };
46 if ptr.is_null() {
47 return None;
48 }
49 Some(OwnedVector { ptr })
50 }
51
52 /// The vector's column (logical) type.
53 #[must_use]
54 pub fn column_type(&self) -> Option<LogicalType> {
55 // SAFETY: `self.ptr` is a valid vector; the returned logical type is owned
56 // (destroy once) and wrapped in RAII. Null → None.
57 LogicalType::from_raw(unsafe { duckdb_vector_get_column_type(self.ptr) }).ok()
58 }
59
60 /// The raw handle, borrowed exclusively for `&mut self` (for the typed
61 /// read/write views built in later vector tasks). The caller must not destroy it.
62 #[allow(dead_code)]
63 pub(crate) fn raw_mut(&mut self) -> duckdb_vector {
64 self.ptr
65 }
66
67 /// The number of elements in a `LIST` vector's child (flattened) buffer.
68 ///
69 /// Meaningful only for a `LIST` vector.
70 #[must_use]
71 pub fn list_size(&self) -> u64 {
72 // SAFETY: `self.ptr` is a valid vector; the value is meaningful for LIST types.
73 unsafe { duckdb_list_vector_get_size(self.ptr) as u64 }
74 }
75
76 /// Sets the size of a `LIST` vector's child buffer.
77 ///
78 /// This does **not** reserve capacity (use [`list_reserve`](OwnedVector::list_reserve)
79 /// first); a size may exceed capacity, so callers must reserve before writing.
80 ///
81 /// # Errors
82 ///
83 /// Returns an error if DuckDB rejects the call (e.g. a null/non-list vector).
84 pub fn list_set_size(
85 &mut self,
86 size: u64,
87 ) -> Result<()> {
88 // SAFETY: `self.ptr` is a valid vector; `size` is copied by value.
89 check_state(unsafe { duckdb_list_vector_set_size(self.ptr, size as idx_t) })
90 }
91
92 /// Reserves capacity for `required_capacity` elements in a `LIST` vector's child
93 /// buffer.
94 ///
95 /// # Errors
96 ///
97 /// Returns an error if DuckDB rejects the call (e.g. a null/non-list vector).
98 pub fn list_reserve(
99 &mut self,
100 required_capacity: u64,
101 ) -> Result<()> {
102 // SAFETY: `self.ptr` is a valid vector; the capacity is copied by value.
103 check_state(unsafe { duckdb_list_vector_reserve(self.ptr, required_capacity as idx_t) })
104 }
105
106 /// Sets whether the value at `row` is valid (`true`) or `NULL` (`false`),
107 /// ensuring the validity mask is writable first.
108 pub fn set_row_validity(
109 &mut self,
110 row: u64,
111 valid: bool,
112 ) {
113 let validity = self.writable_validity();
114 // SAFETY: `validity` is a valid, writable mask (ensured above); `row` indexes it.
115 unsafe { duckdb_validity_set_row_validity(validity, row as idx_t, valid) };
116 }
117
118 /// Marks the value at `row` valid (the inverse of setting it `NULL`), ensuring
119 /// the validity mask is writable first.
120 pub fn set_row_valid(
121 &mut self,
122 row: u64,
123 ) {
124 let validity = self.writable_validity();
125 // SAFETY: `validity` is a valid, writable mask (ensured above); `row` indexes it.
126 unsafe { duckdb_validity_set_row_valid(validity, row as idx_t) };
127 }
128
129 /// Ensures the validity mask is allocated/writable and returns it.
130 fn writable_validity(&mut self) -> *mut u64 {
131 // SAFETY: `self.ptr` is valid; this allocates the mask if absent, so the
132 // subsequent `get_validity` returns a non-null writable pointer.
133 unsafe { duckdb_vector_ensure_validity_writable(self.ptr) };
134 // SAFETY: `self.ptr` is valid and its validity mask is now writable.
135 unsafe { duckdb_vector_get_validity(self.ptr) }
136 }
137
138 /// Destructively re-orders this vector in place so its first `len` logical rows
139 /// are `sel`'s selected rows (`duckdb_slice_vector`).
140 ///
141 /// # Errors
142 ///
143 /// Returns an error if `len` exceeds `sel`'s length.
144 pub fn slice(
145 &mut self,
146 sel: &SelectionVector,
147 len: u64,
148 ) -> Result<()> {
149 if len > sel.len() {
150 return Err(Error::Engine(EngineError::unavailable(Some(format!(
151 "slice len {len} exceeds selection length {}",
152 sel.len()
153 )))));
154 }
155 // SAFETY: `self.ptr` is a valid vector; `sel.raw()` is a valid selection of at
156 // least `len` entries (checked above); `duckdb_slice_vector` remaps in place.
157 unsafe { duckdb_slice_vector(self.ptr, sel.raw(), len as idx_t) };
158 Ok(())
159 }
160
161 /// Copies `src_count` rows from `self` (starting at `src_offset`) into `dst`
162 /// (starting at `dst_offset`), picking rows through `sel`
163 /// (`duckdb_vector_copy_sel`).
164 ///
165 /// # Errors
166 ///
167 /// Returns an error if `sel` holds fewer than `src_count` indices.
168 pub fn copy_sel_into(
169 &self,
170 dst: &mut OwnedVector,
171 sel: &SelectionVector,
172 src_count: u64,
173 src_offset: u64,
174 dst_offset: u64,
175 ) -> Result<()> {
176 if sel.len() < src_count {
177 return Err(Error::Engine(EngineError::unavailable(Some(format!(
178 "selection length {} is smaller than src_count {src_count}",
179 sel.len()
180 )))));
181 }
182 // SAFETY: `self.ptr`/`dst.ptr` are valid vectors of the same type; `sel` has at
183 // least `src_count` indices (checked above); the offsets/count are copied by
184 // value and DuckDB bounds them against the vectors' own capacities.
185 unsafe {
186 duckdb_vector_copy_sel(
187 self.ptr,
188 dst.ptr,
189 sel.raw(),
190 src_count as idx_t,
191 src_offset as idx_t,
192 dst_offset as idx_t,
193 );
194 }
195 Ok(())
196 }
197
198 /// Makes this vector a constant vector that references `value`
199 /// (`duckdb_vector_reference_value`): every logical row reads as `value`,
200 /// sharing its storage rather than copying it.
201 ///
202 /// # Safety
203 ///
204 /// This creates a zero-copy alias: `value` must outlive every use of this vector,
205 /// and `value` must not be mutated or destroyed while the vector still references
206 /// it. The wrapper cannot encode that lifetime for two independently-owned
207 /// handles, so the invariant is the caller's to uphold.
208 pub unsafe fn reference_value(
209 &mut self,
210 value: &crate::raw::owned_value::OwnedValue,
211 ) {
212 // SAFETY: `self.ptr` is a valid vector; `value.raw()` is a valid duckdb_value.
213 // The caller upholds the outlives/no-mutation contract documented above.
214 unsafe { crate::ffi::duckdb_vector_reference_value(self.ptr, value.raw()) };
215 }
216
217 /// Makes this vector a zero-copy, read-only alias of `from`'s storage
218 /// (`duckdb_vector_reference_vector`).
219 ///
220 /// # Safety
221 ///
222 /// `from` must outlive every use of this vector, and neither vector's shared
223 /// storage may be mutated while the alias is live (no overlapping mutable views).
224 /// The wrapper cannot encode that across two independently-owned vectors, so the
225 /// invariant is the caller's to uphold.
226 pub unsafe fn reference_vector(
227 &mut self,
228 from: &OwnedVector,
229 ) {
230 // SAFETY: both handles are valid vectors of the same type; the caller upholds
231 // the outlives / no-aliased-mutation contract documented above.
232 unsafe { crate::ffi::duckdb_vector_reference_vector(self.ptr, from.ptr) };
233 }
234}
235
236impl Drop for OwnedVector {
237 fn drop(&mut self) {
238 if !self.ptr.is_null() {
239 // SAFETY: `self.ptr` is a valid, non-null vector created by
240 // `duckdb_create_vector` and not yet destroyed; destroyed exactly once here.
241 unsafe { duckdb_destroy_vector(&mut self.ptr) };
242 }
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use crate::ffi::{
250 duckdb_validity_row_is_valid, duckdb_vector_get_data, DUCKDB_TYPE_DUCKDB_TYPE_INTEGER,
251 };
252 use crate::types::TypeInfo;
253
254 /// Writes `values` into an `INTEGER` vector's flat data buffer.
255 fn write_i32s(
256 v: &mut OwnedVector,
257 values: &[i32],
258 ) {
259 // SAFETY: `v` is a valid INTEGER vector with capacity >= values.len(); its data
260 // buffer holds `i32` inline, so writing `values.len()` in-bounds `i32`s is sound.
261 unsafe {
262 let data = duckdb_vector_get_data(v.raw_mut()) as *mut i32;
263 for (i, &val) in values.iter().enumerate() {
264 *data.add(i) = val;
265 }
266 }
267 }
268
269 /// Reads `n` `i32`s out of a vector's flat data buffer.
270 fn read_i32s(
271 v: &mut OwnedVector,
272 n: usize,
273 ) -> Vec<i32> {
274 // SAFETY: `v` is a valid INTEGER vector with at least `n` rows written.
275 unsafe {
276 let data = duckdb_vector_get_data(v.raw_mut()) as *const i32;
277 (0..n).map(|i| *data.add(i)).collect()
278 }
279 }
280
281 #[test]
282 fn creates_and_reports_its_type() {
283 let ty = LogicalType::of::<i32>().unwrap();
284 let mut vector = OwnedVector::new(&ty, 16).expect("vector allocation");
285 assert_eq!(vector.column_type().unwrap().type_id(), DUCKDB_TYPE_DUCKDB_TYPE_INTEGER);
286 // The exclusive raw handle is available for typed views.
287 assert!(!vector.raw_mut().is_null());
288 // Dropped here: the vector is destroyed exactly once (no leak, no double free).
289 }
290
291 #[test]
292 fn list_child_size_is_reserved_and_set() {
293 // A LIST(INTEGER) vector; manage its flattened child-element count.
294 let list_ty = TypeInfo::List(Box::new(TypeInfo::Scalar(DUCKDB_TYPE_DUCKDB_TYPE_INTEGER)))
295 .to_logical_type()
296 .unwrap();
297 let mut v = OwnedVector::new(&list_ty, 8).expect("list vector allocation");
298 assert_eq!(v.list_size(), 0);
299 v.list_reserve(16).unwrap();
300 v.list_set_size(5).unwrap();
301 assert_eq!(v.list_size(), 5, "child size reflects the set value");
302 }
303
304 #[test]
305 fn validity_round_trips_valid_null_valid() {
306 let ty = LogicalType::of::<i32>().unwrap();
307 let mut v = OwnedVector::new(&ty, 4).expect("vector allocation");
308 // Reads `row`'s validity bit from `v`'s (now-writable) mask.
309 fn is_valid(
310 v: &mut OwnedVector,
311 row: u64,
312 ) -> bool {
313 // SAFETY: `v.raw_mut()` is a valid vector; its validity mask was made
314 // writable by the setters before this is called. `duckdb_validity_row_is_valid`
315 // treats a null mask as "all valid", so it is safe regardless.
316 unsafe { duckdb_validity_row_is_valid(duckdb_vector_get_validity(v.raw_mut()), row) }
317 }
318
319 // Mark row 0 NULL, then valid again.
320 v.set_row_validity(0, false);
321 assert!(!is_valid(&mut v, 0), "row 0 is NULL");
322 v.set_row_valid(0);
323 assert!(is_valid(&mut v, 0), "row 0 is valid again");
324 }
325
326 #[test]
327 fn copy_sel_picks_selected_rows_into_the_destination() {
328 use crate::raw::selection_vector::SelectionVector;
329 let ty = LogicalType::of::<i32>().unwrap();
330 let mut src = OwnedVector::new(&ty, 4).unwrap();
331 write_i32s(&mut src, &[10, 20, 30, 40]);
332 let mut dst = OwnedVector::new(&ty, 4).unwrap();
333
334 // Pick rows 3 and 1 of src into dst[0], dst[1].
335 let mut sel = SelectionVector::new(2).unwrap();
336 sel.set(0, 3);
337 sel.set(1, 1);
338 src.copy_sel_into(&mut dst, &sel, 2, 0, 0).unwrap();
339 assert_eq!(read_i32s(&mut dst, 2), vec![40, 20]);
340
341 // A selection shorter than src_count is rejected before the FFI call.
342 assert!(src.copy_sel_into(&mut dst, &sel, 3, 0, 0).is_err());
343 }
344
345 #[test]
346 fn slice_reorders_the_vector_in_place() {
347 use crate::raw::selection_vector::SelectionVector;
348 let ty = LogicalType::of::<i32>().unwrap();
349 let mut v = OwnedVector::new(&ty, 4).unwrap();
350 write_i32s(&mut v, &[10, 20, 30, 40]);
351
352 // Reverse the four rows: v[i] becomes original[3 - i].
353 let mut rev = SelectionVector::new(4).unwrap();
354 for i in 0..4u64 {
355 rev.set(i, (3 - i) as u32);
356 }
357 v.slice(&rev, 4).unwrap();
358
359 // The sliced (dictionary) vector's logical values, copied out flat, are reversed.
360 let mut flat = OwnedVector::new(&ty, 4).unwrap();
361 let mut identity = SelectionVector::new(4).unwrap();
362 for i in 0..4u64 {
363 identity.set(i, i as u32);
364 }
365 v.copy_sel_into(&mut flat, &identity, 4, 0, 0).unwrap();
366 assert_eq!(read_i32s(&mut flat, 4), vec![40, 30, 20, 10]);
367
368 // `len` beyond the selection is rejected.
369 assert!(v.slice(&rev, 5).is_err());
370 }
371
372 #[test]
373 fn reference_value_makes_a_constant_vector() {
374 use crate::types::value::DuckValue;
375 let ty = LogicalType::of::<i32>().unwrap();
376 let mut v = OwnedVector::new(&ty, 4).unwrap();
377 // `value` outlives every use of `v` below (dropped at end of scope, after v).
378 let value = DuckValue::Int(99).to_owned_value().unwrap();
379 // SAFETY: `value` lives for the rest of this scope, longer than every read of
380 // `v`, and is neither mutated nor destroyed while `v` references it.
381 unsafe { v.reference_value(&value) };
382 // A constant vector reads the referenced value at row 0.
383 assert_eq!(read_i32s(&mut v, 1), vec![99]);
384 }
385
386 #[test]
387 fn reference_vector_aliases_source_storage() {
388 let ty = LogicalType::of::<i32>().unwrap();
389 let mut src = OwnedVector::new(&ty, 4).unwrap();
390 write_i32s(&mut src, &[7, 8]);
391 let mut view = OwnedVector::new(&ty, 4).unwrap();
392 // SAFETY: `src` outlives `view` (dropped after it) and its storage is not
393 // mutated while `view` aliases it.
394 unsafe { view.reference_vector(&src) };
395 assert_eq!(read_i32s(&mut view, 2), vec![7, 8]);
396 }
397}