1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// SPDX-License-Identifier: MIT
// Copyright 2026 Tom F. <https://github.com/tomtom215/>
// My way of giving something small back to the open source community
// and encouraging more Rust development!
//! Selection vectors (`DuckDB` 1.5.0+).
//!
//! A [`SelectionVector`] is a list of row indices used to logically reorder or
//! filter a data vector without copying its payload — the building block behind
//! `DuckDB`'s zero-copy filtering. Extensions that implement custom filtering or
//! reordering in vectorized callbacks can allocate one, fill in the indices, and
//! hand it to the relevant `DuckDB` vector operations.
//!
//! # Example
//!
//! ```rust,no_run
//! use quack_rs::selection_vector::SelectionVector;
//!
//! // Select rows 3, 1, 4, 1, 5 (in that order) from a source vector.
//! let mut sel = SelectionVector::new(5);
//! sel.as_mut_slice().copy_from_slice(&[3, 1, 4, 1, 5]);
//! assert_eq!(sel.len(), 5);
//! ```
use libduckdb_sys::{
duckdb_create_selection_vector, duckdb_destroy_selection_vector, duckdb_selection_vector,
duckdb_selection_vector_get_data_ptr, idx_t, sel_t,
};
/// RAII wrapper for a `duckdb_selection_vector`.
///
/// Owns `size` 32-bit row indices ([`sel_t`]). Automatically destroyed on drop.
pub struct SelectionVector {
sel: duckdb_selection_vector,
len: usize,
}
impl SelectionVector {
/// Allocates a selection vector holding `size` indices.
///
/// The indices are uninitialised; fill them via
/// [`as_mut_slice`][SelectionVector::as_mut_slice].
#[must_use]
pub fn new(size: usize) -> Self {
let raw_size = idx_t::try_from(size).unwrap_or(idx_t::MAX);
// SAFETY: duckdb_create_selection_vector allocates an owned handle.
let sel = unsafe { duckdb_create_selection_vector(raw_size) };
let len = if sel.is_null() { 0 } else { size };
Self { sel, len }
}
/// Returns the number of indices in this selection vector.
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
/// Returns `true` if the selection vector holds no indices.
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
/// Returns the indices as a read-only slice.
#[must_use]
pub fn as_slice(&self) -> &[sel_t] {
if self.sel.is_null() || self.len == 0 {
return &[];
}
// SAFETY: self.sel is valid; the data pointer addresses `self.len` sel_t
// elements that live as long as the selection vector.
let ptr = unsafe { duckdb_selection_vector_get_data_ptr(self.sel) };
if ptr.is_null() {
return &[];
}
// SAFETY: ptr points to `self.len` valid, aligned sel_t values.
unsafe { std::slice::from_raw_parts(ptr, self.len) }
}
/// Returns the indices as a mutable slice for filling in.
#[must_use]
pub fn as_mut_slice(&mut self) -> &mut [sel_t] {
if self.sel.is_null() || self.len == 0 {
return &mut [];
}
// SAFETY: self.sel is valid; the data pointer addresses `self.len` sel_t
// elements that live as long as the selection vector.
let ptr = unsafe { duckdb_selection_vector_get_data_ptr(self.sel) };
if ptr.is_null() {
return &mut [];
}
// SAFETY: ptr points to `self.len` valid, aligned sel_t values and we hold
// a unique borrow of `self`.
unsafe { std::slice::from_raw_parts_mut(ptr, self.len) }
}
/// Returns the raw handle.
#[inline]
#[must_use]
pub const fn as_raw(&self) -> duckdb_selection_vector {
self.sel
}
}
impl Drop for SelectionVector {
fn drop(&mut self) {
if !self.sel.is_null() {
// SAFETY: self.sel is a valid handle that we own. This destroy variant
// takes the handle by value.
unsafe { duckdb_destroy_selection_vector(self.sel) };
}
}
}
#[cfg(all(test, feature = "bundled-test"))]
mod tests {
use super::*;
#[test]
fn round_trips_indices() {
// Ensure the dispatch table is populated.
let _db = crate::testing::InMemoryDb::open().unwrap();
let mut sel = SelectionVector::new(4);
assert_eq!(sel.len(), 4);
assert!(!sel.is_empty());
sel.as_mut_slice().copy_from_slice(&[7, 0, 3, 1]);
assert_eq!(sel.as_slice(), &[7, 0, 3, 1]);
}
}