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}