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 of the positions a predicate accepts.
46    pub fn from_predicate(len: usize, keep: impl Fn(usize) -> bool) -> Self {
47        let mut selection = Self::with_capacity(len);
48        for index in 0..len {
49            if keep(index) {
50                selection.push(index);
51            }
52        }
53        selection
54    }
55
56    /// Adds a position to the end.
57    ///
58    /// # Panics
59    ///
60    /// If the index does not fit in a `u32`. A vector holds 1024 values and a row group holds
61    /// 122,880, so an index that large is a bug several layers up rather than a large query.
62    pub fn push(&mut self, index: usize) {
63        self.indices.push(u32::try_from(index).expect("a position past four billion"));
64    }
65
66    /// How many positions survived.
67    #[must_use]
68    pub fn len(&self) -> usize {
69        self.indices.len()
70    }
71
72    /// Whether nothing survived.
73    #[must_use]
74    pub fn is_empty(&self) -> bool {
75        self.indices.is_empty()
76    }
77
78    /// The position at `slot`, where `slot` counts through the survivors.
79    #[must_use]
80    pub fn get(&self, slot: usize) -> Option<usize> {
81        self.indices.get(slot).map(|&index| index as usize)
82    }
83
84    /// The positions, in order.
85    #[must_use]
86    pub fn indices(&self) -> &[u32] {
87        &self.indices
88    }
89
90    /// The positions as `usize`, in order.
91    pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
92        self.indices.iter().map(|&index| index as usize)
93    }
94
95    /// What fraction of `len` positions survived.
96    ///
97    /// This is the number the compaction decision is made on, and it is measured rather than
98    /// assumed, per section 7.1. Zero length reports 1.0, because a filter over nothing has not
99    /// rejected anything.
100    #[must_use]
101    pub fn selectivity(&self, len: usize) -> f64 {
102        if len == 0 { 1.0 } else { self.len() as f64 / len as f64 }
103    }
104
105    /// This selection composed with an earlier one, so that filtering twice does not need the
106    /// intermediate to be materialized.
107    ///
108    /// `self` indexes into `earlier`, and the result indexes into whatever `earlier` indexed into.
109    /// Getting this backwards produces a query that returns the wrong rows rather than an error,
110    /// which is why the direction is spelled out here and tested below.
111    #[must_use]
112    pub fn compose(&self, earlier: &Self) -> Self {
113        let indices =
114            self.indices.iter().filter_map(|&slot| earlier.indices.get(slot as usize).copied());
115        Self { indices: indices.collect() }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::Selection;
122
123    #[test]
124    fn nothing_selected_is_not_the_same_as_no_selection() {
125        // The reason this is a type. An operator that treats an empty selection as "everything"
126        // returns every row for a predicate that matched none, which is a wrong answer and the
127        // worst thing this project can ship.
128        let none = Selection::empty();
129        assert_eq!(none.len(), 0);
130        assert!(none.is_empty());
131        assert_eq!(none.selectivity(1024), 0.0);
132    }
133
134    #[test]
135    fn a_predicate_selection_keeps_the_positions_in_order() {
136        let selection = Selection::from_predicate(10, |i| i % 3 == 0);
137        assert_eq!(selection.indices(), &[0, 3, 6, 9]);
138        assert_eq!(selection.get(2), Some(6));
139        assert_eq!(selection.get(4), None);
140        assert!((selection.selectivity(10) - 0.4).abs() < f64::EPSILON);
141    }
142
143    #[test]
144    fn composing_two_filters_indexes_all_the_way_back() {
145        // First filter keeps the even positions of sixteen. Second keeps every third survivor,
146        // meaning slots 0, 3 and 6 of the first result, which are positions 0, 6 and 12.
147        let first = Selection::from_predicate(16, |i| i % 2 == 0);
148        let second = Selection::from_predicate(first.len(), |i| i % 3 == 0);
149        assert_eq!(second.compose(&first).indices(), &[0, 6, 12]);
150    }
151
152    #[test]
153    fn composing_with_the_identity_changes_nothing() {
154        let selection = Selection::from_predicate(8, |i| i > 4);
155        assert_eq!(selection.compose(&Selection::identity(8)), selection);
156    }
157
158    #[test]
159    fn selectivity_over_nothing_is_one_rather_than_a_division_by_zero() {
160        assert!((Selection::empty().selectivity(0) - 1.0).abs() < f64::EPSILON);
161    }
162}