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}