Skip to main content

rudb_vector/
selection.rs

1//! Selection vectors.
2//!
3//! `spec/07-execution.md` section 7.1: a filter produces a `u32` selection vector rather than
4//! compacting. Compaction happens when a measured selectivity threshold is crossed and the
5//! downstream operator is one that benefits, and the threshold is per operator and measured rather
6//! than one global constant somebody picked.
7//!
8//! The reason not to compact by default is that a filter over five columns which compacts has
9//! copied five columns to save the next operator a redirection. On a query that filters and then
10//! projects two of those columns, three of the copies were free work.
11
12/// Which positions of a vector are still in play, as indices into it.
13///
14/// An empty selection means nothing survived, which is different from no selection at all. The
15/// distinction is why this is a type rather than an `Option<Vec<u32>>` that everybody interprets
16/// slightly differently.
17#[derive(Debug, Clone, Default, PartialEq, Eq)]
18pub struct Selection {
19    indices: Vec<u32>,
20}
21
22impl Selection {
23    /// A selection of nothing.
24    #[must_use]
25    pub fn empty() -> Self {
26        Self::default()
27    }
28
29    /// A selection of nothing, with room for `capacity` positions.
30    #[must_use]
31    pub fn with_capacity(capacity: usize) -> Self {
32        Self { indices: Vec::with_capacity(capacity) }
33    }
34
35    /// A selection of the first `len` positions in order.
36    ///
37    /// Materialized rather than represented as an absent selection, so this is what a caller uses
38    /// when it genuinely wants the identity written down. A scan that has not filtered anything
39    /// carries no selection at all, which is cheaper and is the common case.
40    #[must_use]
41    pub fn identity(len: usize) -> Self {
42        Self { indices: (0..len as u32).collect() }
43    }
44
45    /// A selection from positions a caller has already worked out, in order.
46    ///
47    /// For a kernel that fills a buffer of its own and counts as it goes, which is how a selection
48    /// loop is written without a branch in it: every row writes its index at the current length and
49    /// only a row that is kept moves the length on. Pushing one at a time would put a capacity check
50    /// and a conversion on a loop whose whole point is that it has neither.
51    #[must_use]
52    pub fn from_indices(indices: Vec<u32>) -> Self {
53        Self { indices }
54    }
55
56    /// A selection of the positions a predicate accepts.
57    pub fn from_predicate(len: usize, keep: impl Fn(usize) -> bool) -> Self {
58        let mut selection = Self::with_capacity(len);
59        for index in 0..len {
60            if keep(index) {
61                selection.push(index);
62            }
63        }
64        selection
65    }
66
67    /// Adds a position to the end.
68    ///
69    /// # Panics
70    ///
71    /// If the index does not fit in a `u32`. A vector holds 1024 values and a row group holds
72    /// 122,880, so an index that large is a bug several layers up rather than a large query.
73    pub fn push(&mut self, index: usize) {
74        self.indices.push(u32::try_from(index).expect("a position past four billion"));
75    }
76
77    /// How many positions survived.
78    #[must_use]
79    pub fn len(&self) -> usize {
80        self.indices.len()
81    }
82
83    /// Whether nothing survived.
84    #[must_use]
85    pub fn is_empty(&self) -> bool {
86        self.indices.is_empty()
87    }
88
89    /// The position at `slot`, where `slot` counts through the survivors.
90    #[must_use]
91    pub fn get(&self, slot: usize) -> Option<usize> {
92        self.indices.get(slot).map(|&index| index as usize)
93    }
94
95    /// The positions, in order.
96    #[must_use]
97    pub fn indices(&self) -> &[u32] {
98        &self.indices
99    }
100
101    /// The positions as `usize`, in order.
102    pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
103        self.indices.iter().map(|&index| index as usize)
104    }
105
106    /// What fraction of `len` positions survived.
107    ///
108    /// This is the number the compaction decision is made on, and it is measured rather than
109    /// assumed, per section 7.1. Zero length reports 1.0, because a filter over nothing has not
110    /// rejected anything.
111    #[must_use]
112    pub fn selectivity(&self, len: usize) -> f64 {
113        if len == 0 { 1.0 } else { self.len() as f64 / len as f64 }
114    }
115
116    /// This selection composed with an earlier one, so that filtering twice does not need the
117    /// intermediate to be materialized.
118    ///
119    /// `self` indexes into `earlier`, and the result indexes into whatever `earlier` indexed into.
120    /// Getting this backwards produces a query that returns the wrong rows rather than an error,
121    /// which is why the direction is spelled out here and tested below.
122    #[must_use]
123    pub fn compose(&self, earlier: &Self) -> Self {
124        let indices =
125            self.indices.iter().filter_map(|&slot| earlier.indices.get(slot as usize).copied());
126        Self { indices: indices.collect() }
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::Selection;
133
134    #[test]
135    fn nothing_selected_is_not_the_same_as_no_selection() {
136        // The reason this is a type. An operator that treats an empty selection as "everything"
137        // returns every row for a predicate that matched none, which is a wrong answer and the
138        // worst thing this project can ship.
139        let none = Selection::empty();
140        assert_eq!(none.len(), 0);
141        assert!(none.is_empty());
142        assert_eq!(none.selectivity(1024), 0.0);
143    }
144
145    #[test]
146    fn a_predicate_selection_keeps_the_positions_in_order() {
147        let selection = Selection::from_predicate(10, |i| i % 3 == 0);
148        assert_eq!(selection.indices(), &[0, 3, 6, 9]);
149        assert_eq!(selection.get(2), Some(6));
150        assert_eq!(selection.get(4), None);
151        assert!((selection.selectivity(10) - 0.4).abs() < f64::EPSILON);
152    }
153
154    #[test]
155    fn composing_two_filters_indexes_all_the_way_back() {
156        // First filter keeps the even positions of sixteen. Second keeps every third survivor,
157        // meaning slots 0, 3 and 6 of the first result, which are positions 0, 6 and 12.
158        let first = Selection::from_predicate(16, |i| i % 2 == 0);
159        let second = Selection::from_predicate(first.len(), |i| i % 3 == 0);
160        assert_eq!(second.compose(&first).indices(), &[0, 6, 12]);
161    }
162
163    #[test]
164    fn composing_with_the_identity_changes_nothing() {
165        let selection = Selection::from_predicate(8, |i| i > 4);
166        assert_eq!(selection.compose(&Selection::identity(8)), selection);
167    }
168
169    #[test]
170    fn selectivity_over_nothing_is_one_rather_than_a_division_by_zero() {
171        assert!((Selection::empty().selectivity(0) - 1.0).abs() < f64::EPSILON);
172    }
173}