Skip to main content

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}