rudb_vector/layout.rs
1//! The list of physical layouts, in one place, for the kernels that generate a loop per layout.
2//!
3//! A kernel that reads values has to do the same thing fifteen times, once per variant of
4//! [`Data`](crate::Data), and every kernel in the workspace already writes the body once and lets a
5//! local macro repeat it. What none of them shared was the list of layouts the body is repeated
6//! over, so the list was written out at ten call sites across four files, and a list written ten
7//! times is a list that is wrong in one of them. The failure that causes is not a compile error. It
8//! is a layout quietly missing from a kernel's match, falling through to the row at a time path, and
9//! the only thing anybody notices is that one column type is thirty times slower than the others.
10//!
11//! So the lists live here and [`for_each_layout`] hands one to a caller's macro.
12//!
13//! # How the list is kept honest
14//!
15//! [`Data::len`](crate::Data::len) is generated from the `all` group and its match has no wildcard
16//! arm, so a variant added to `Data` without being added to `all` does not compile. That pins `all`
17//! to the enum.
18//!
19//! The other groups are pinned to `all` by the tests at the bottom of this file, which check that
20//! each group is a subset of `all` and that each group plus the layouts it deliberately leaves out
21//! is exactly the group above it. The chain runs `narrow` to `exact` to `integer` to `ordered` to
22//! `fixed` to `all`, with `signed` and `unsigned` joining at `integer`, so a new fixed width layout
23//! that nobody adds to `ordered` fails a test with the name of the layout in the message.
24//!
25//! It would be better if the groups were derived from one list rather than checked against it, and
26//! that is not possible in a declarative macro. Deriving them means filtering the list by a tag, and
27//! filtering means comparing one identifier against another, and `macro_rules` cannot compare
28//! identifiers. The choice is between a procedural macro crate, which is a build dependency and a
29//! second language for six lists, and writing the groups out with a test that says they agree. The
30//! test is the cheaper of the two and it fails in the same second the build does.
31
32/// Calls `$callback` with one group of physical layouts.
33///
34/// Each entry is `(variant, element type, zero)`, where the variant is the
35/// [`Data`](crate::Data) variant, the element type is what one value of it is, and the zero is the
36/// value that fills a slot whose row is null. A caller that does not need all three ignores the
37/// ones it does not need.
38///
39/// The callback is a macro the caller has already defined, almost always a `macro_rules` inside the
40/// function that needs it, so that its body can refer to the function's own locals. It is passed a
41/// comma separated list of parenthesised triples and should match
42/// `$(($variant:ident, $native:ty, $zero:expr)),+ $(,)?`.
43///
44/// Anything after the callback name is passed through ahead of the list, one token tree each, for
45/// the callers that generate a loop per layout inside another loop per layout and need to hand the
46/// inner one what the outer one bound. A caller taking one of those matches it first, as in
47/// `($values:expr, $(($variant:ident, $native:ty, $zero:expr)),+ $(,)?)`.
48///
49/// ```
50/// use rudb_vector::{Data, for_each_layout};
51///
52/// fn widest(data: &Data) -> Option<i128> {
53/// macro_rules! biggest {
54/// ($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
55/// match data {
56/// $(Data::$variant(values) => {
57/// values.iter().copied().map(i128::from).max().or(Some($zero))
58/// })+
59/// _ => None,
60/// }
61/// };
62/// }
63/// for_each_layout!(narrow, biggest)
64/// }
65///
66/// assert_eq!(widest(&Data::Int32(vec![3, 9, 4].into())), Some(9));
67/// assert_eq!(widest(&Data::Float64(vec![3.0].into())), None);
68/// ```
69///
70/// # The groups
71///
72/// - `all`, every layout that holds values. [`Data::Empty`](crate::Data::Empty) is not in it,
73/// because it holds none and has nothing for a loop to read.
74/// - `fixed`, every layout whose run is a [`Buffer`](crate::Buffer) of a `Copy` element. These are
75/// the ones where a null slot can be filled with a zero and a gather is a copy of fixed width
76/// slots rather than a copy of bytes.
77/// - `ordered`, the layouts whose SQL order is the derived order of the element type. A float is
78/// not in it, because `NaN` orders where SQL says rather than where the hardware says, and a
79/// string is not in it, because its order is over the bytes a view points at.
80/// - `integer`, the ten integer widths.
81/// - `signed` and `unsigned`, the five of each that `integer` is made of. Several kernels want one
82/// half and not the other, negation being the clearest, since negating an unsigned value is an
83/// overflow at every row but zero and a loop for it would be a loop that exists to fail.
84/// - `exact`, the integers whose every value fits in an `i128`, so a kernel can widen the lot into
85/// one accumulator type. `UInt128` is the one that does not.
86/// - `narrow`, the integers narrower than 128 bits. Two things follow from that and both of them
87/// are used. The total of a whole vector of them still fits in an `i128`, which is what lets the
88/// only overflow check in a sum be the one at the vector boundary rather than one per row, and a
89/// run of `i128` narrows into any of them, which is the shape the cast path works in.
90/// - `float`, the two IEEE widths.
91#[macro_export]
92macro_rules! for_each_layout {
93 (all, $callback:ident $(, $extra:tt)*) => {
94 $callback! {
95 $($extra,)*
96 (Bool, bool, false),
97 (Int8, i8, 0),
98 (Int16, i16, 0),
99 (Int32, i32, 0),
100 (Int64, i64, 0),
101 (Int128, i128, 0),
102 (UInt8, u8, 0),
103 (UInt16, u16, 0),
104 (UInt32, u32, 0),
105 (UInt64, u64, 0),
106 (UInt128, u128, 0),
107 (Float32, f32, 0.0),
108 (Float64, f64, 0.0),
109 (Interval, (i32, i32, i64), (0, 0, 0)),
110 (Varlen, &str, ""),
111 }
112 };
113 (fixed, $callback:ident $(, $extra:tt)*) => {
114 $callback! {
115 $($extra,)*
116 (Bool, bool, false),
117 (Int8, i8, 0),
118 (Int16, i16, 0),
119 (Int32, i32, 0),
120 (Int64, i64, 0),
121 (Int128, i128, 0),
122 (UInt8, u8, 0),
123 (UInt16, u16, 0),
124 (UInt32, u32, 0),
125 (UInt64, u64, 0),
126 (UInt128, u128, 0),
127 (Float32, f32, 0.0),
128 (Float64, f64, 0.0),
129 (Interval, (i32, i32, i64), (0, 0, 0)),
130 }
131 };
132 (ordered, $callback:ident $(, $extra:tt)*) => {
133 $callback! {
134 $($extra,)*
135 (Bool, bool, false),
136 (Int8, i8, 0),
137 (Int16, i16, 0),
138 (Int32, i32, 0),
139 (Int64, i64, 0),
140 (Int128, i128, 0),
141 (UInt8, u8, 0),
142 (UInt16, u16, 0),
143 (UInt32, u32, 0),
144 (UInt64, u64, 0),
145 (UInt128, u128, 0),
146 (Interval, (i32, i32, i64), (0, 0, 0)),
147 }
148 };
149 (integer, $callback:ident $(, $extra:tt)*) => {
150 $callback! {
151 $($extra,)*
152 (Int8, i8, 0),
153 (Int16, i16, 0),
154 (Int32, i32, 0),
155 (Int64, i64, 0),
156 (Int128, i128, 0),
157 (UInt8, u8, 0),
158 (UInt16, u16, 0),
159 (UInt32, u32, 0),
160 (UInt64, u64, 0),
161 (UInt128, u128, 0),
162 }
163 };
164 (signed, $callback:ident $(, $extra:tt)*) => {
165 $callback! {
166 $($extra,)*
167 (Int8, i8, 0),
168 (Int16, i16, 0),
169 (Int32, i32, 0),
170 (Int64, i64, 0),
171 (Int128, i128, 0),
172 }
173 };
174 (unsigned, $callback:ident $(, $extra:tt)*) => {
175 $callback! {
176 $($extra,)*
177 (UInt8, u8, 0),
178 (UInt16, u16, 0),
179 (UInt32, u32, 0),
180 (UInt64, u64, 0),
181 (UInt128, u128, 0),
182 }
183 };
184 (exact, $callback:ident $(, $extra:tt)*) => {
185 $callback! {
186 $($extra,)*
187 (Int8, i8, 0),
188 (Int16, i16, 0),
189 (Int32, i32, 0),
190 (Int64, i64, 0),
191 (Int128, i128, 0),
192 (UInt8, u8, 0),
193 (UInt16, u16, 0),
194 (UInt32, u32, 0),
195 (UInt64, u64, 0),
196 }
197 };
198 (narrow, $callback:ident $(, $extra:tt)*) => {
199 $callback! {
200 $($extra,)*
201 (Int8, i8, 0),
202 (Int16, i16, 0),
203 (Int32, i32, 0),
204 (Int64, i64, 0),
205 (UInt8, u8, 0),
206 (UInt16, u16, 0),
207 (UInt32, u32, 0),
208 (UInt64, u64, 0),
209 }
210 };
211 (float, $callback:ident $(, $extra:tt)*) => {
212 $callback! {
213 $($extra,)*
214 (Float32, f32, 0.0),
215 (Float64, f64, 0.0),
216 }
217 };
218}
219
220#[cfg(test)]
221mod tests {
222 use crate::{Buffer, Data};
223
224 /// The variant names of a group, in the order the group lists them.
225 macro_rules! names {
226 ($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
227 &[$(stringify!($variant)),+]
228 };
229 }
230
231 const ALL: &[&str] = for_each_layout!(all, names);
232 const FIXED: &[&str] = for_each_layout!(fixed, names);
233 const ORDERED: &[&str] = for_each_layout!(ordered, names);
234 const INTEGER: &[&str] = for_each_layout!(integer, names);
235 const SIGNED: &[&str] = for_each_layout!(signed, names);
236 const UNSIGNED: &[&str] = for_each_layout!(unsigned, names);
237 const EXACT: &[&str] = for_each_layout!(exact, names);
238 const NARROW: &[&str] = for_each_layout!(narrow, names);
239 const FLOAT: &[&str] = for_each_layout!(float, names);
240
241 /// The names of a group and some extras, sorted, for comparing one group against another.
242 fn sorted(group: &[&str], extra: &[&str]) -> Vec<String> {
243 let mut names: Vec<String> =
244 group.iter().chain(extra).map(|name| (*name).to_string()).collect();
245 names.sort();
246 names
247 }
248
249 #[test]
250 fn no_group_lists_a_layout_twice() {
251 for group in [ALL, FIXED, ORDERED, INTEGER, SIGNED, UNSIGNED, EXACT, NARROW, FLOAT] {
252 let mut seen = group.to_vec();
253 seen.sort_unstable();
254 let mut once = seen.clone();
255 once.dedup();
256 assert_eq!(seen, once, "a group lists the same layout twice");
257 }
258 }
259
260 #[test]
261 fn every_group_is_part_of_the_whole_list() {
262 for group in [FIXED, ORDERED, INTEGER, SIGNED, UNSIGNED, EXACT, NARROW, FLOAT] {
263 for name in group {
264 assert!(ALL.contains(name), "{name} is in a group but not in the all group");
265 }
266 }
267 }
268
269 /// The chain that pins every group to `all`, which the compiler pins to `Data` through
270 /// `Data::len`. Each step names the layouts the smaller group leaves out, so a new layout that
271 /// only reaches `all` fails here with its own name in the message rather than going missing from
272 /// six kernels in silence.
273 #[test]
274 fn each_group_plus_what_it_leaves_out_is_the_group_above_it() {
275 assert_eq!(sorted(ALL, &[]), sorted(FIXED, &["Varlen"]));
276 assert_eq!(sorted(FIXED, &[]), sorted(ORDERED, FLOAT));
277 assert_eq!(sorted(ORDERED, &[]), sorted(INTEGER, &["Bool", "Interval"]));
278 assert_eq!(sorted(INTEGER, &[]), sorted(SIGNED, UNSIGNED));
279 assert_eq!(sorted(INTEGER, &[]), sorted(EXACT, &["UInt128"]));
280 assert_eq!(sorted(EXACT, &[]), sorted(NARROW, &["Int128"]));
281 }
282
283 /// That the element type and the zero next to a variant are the ones that variant holds. Mostly
284 /// a compile time check, since a mismatch would not build, and the assertion is there so that
285 /// the built code is also run.
286 #[test]
287 fn the_element_type_and_the_zero_belong_to_the_variant() {
288 macro_rules! built {
289 ($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
290 vec![$(Data::$variant(Buffer::<$native>::from_vec(vec![$zero])),)+]
291 };
292 }
293 let runs = for_each_layout!(fixed, built);
294 assert_eq!(runs.len(), FIXED.len());
295 for run in runs {
296 assert_eq!(run.len(), 1);
297 }
298 }
299}