Skip to main content

rudb_vector/
assemble.rs

1//! One vector built out of pieces that each answer a different set of its rows.
2//!
3//! The inverse of [`Vector::gather`]. A gather says where each output row reads from, so it wants
4//! one source and a position per row. An assembly says where each input row writes to, so it takes
5//! several sources and a position per row of each of them, and the rows no source claims come out
6//! null.
7//!
8//! `CASE` is the shape that wants this. Each arm is evaluated over the rows no earlier arm claimed,
9//! which is a correctness rule rather than a performance one, since `CASE WHEN x <> 0 THEN 1 / x
10//! ELSE 0 END` divides by zero on the rows the arm excludes if the arm is evaluated for them. So the
11//! arms produce several short answers that have to end up interleaved in the order the rows arrived
12//! in, and interleaving them is what this is. A join assembling a payload out of a matched side and
13//! an unmatched side wants the same thing.
14//!
15//! # Why it is not a `Vec<Value>`
16//!
17//! Because that is a heap allocation per string and a drop per string afterwards, on top of the
18//! walk through the enum that a `Value` is. On the ClickBench query that groups by a `CASE` over
19//! `Referer`, building the answer that way was about a quarter of the whole query: a quarter of the
20//! instructions were in `value_at`, the `Value` drop glue, `malloc` and `free`, for an answer whose
21//! bytes were already sitting in a string arena and only needed to be pointed at.
22//!
23//! What happens instead is that the pieces are laid end to end into one run of data and the
24//! interleave is then a single gather over that run, which is a typed loop per physical layout and
25//! is the same loop [`Vector::gather`] already goes down. A string's bytes are copied once, into one
26//! arena that was sized before any of them moved.
27//!
28//! # The shape of the interface
29//!
30//! A builder rather than a function taking a slice of pieces, because the caller producing the
31//! pieces is usually borrowing scratch space to produce each one and cannot hold two of them at
32//! once. [`Assembly::place`] reads a piece and is done with it, so the borrow ends between arms and
33//! nothing has to be cloned to keep it alive.
34//!
35//! # The simpler thing next to it
36//!
37//! [`concat()`] is the case where the pieces arrive in order and claim every row, which is what a row
38//! group of a stored table is built out of. An assembly would answer it, and it would pay for a
39//! position per row and a flatten per piece to answer something that is a run of `memcpy`s, so it is
40//! its own function. What the two share is the typed append underneath both of them.
41
42use std::sync::Arc;
43
44use rudb_common::{Error, LogicalType, Result, Value};
45
46use crate::string::StringView;
47use crate::validity::Validity;
48use crate::vector::{Data, Form, NOWHERE, Vector, copy_of, data_for, empty_data_for, layout_of};
49
50/// A vector being built out of pieces, each landing at the positions it is given.
51///
52/// Build one with [`Assembly::new`], call [`Assembly::place`] once per piece, and finish it with
53/// [`Assembly::finish`]. A row no piece claims is null.
54#[derive(Debug)]
55pub struct Assembly {
56    ty: LogicalType,
57    rows: usize,
58    /// The pieces laid end to end, for a type that has a flat layout to lay them in.
59    data: Data,
60    /// Where each output row reads from in `data`, or [`NOWHERE`] for a row no piece claimed.
61    at: Vec<usize>,
62    /// Whether each output row holds a value rather than a null.
63    live: Vec<bool>,
64    /// The fallback for the nested types, which have no run of data to lay anything end to end in.
65    ///
66    /// A `LIST`, a `STRUCT` and a `MAP` are a child vector and a run of entries rather than a run of
67    /// values, so laying two of them end to end is not an append to one buffer and the copy loop
68    /// this is built around has nothing to walk. They go through values, which is what they did
69    /// before this existed and is not a regression for them. Nothing on a ClickBench or TPC-H path
70    /// reaches it.
71    values: Option<Vec<Value>>,
72}
73
74impl Assembly {
75    /// An assembly of `rows` rows of `ty`, with every row null until a piece claims it.
76    ///
77    /// # Errors
78    ///
79    /// If the type is one there is no flat layout for yet, which today means `ARRAY` and `UNION`.
80    pub fn new(ty: LogicalType, rows: usize) -> Result<Self> {
81        let nested =
82            matches!(ty, LogicalType::List(_) | LogicalType::Struct(_) | LogicalType::Map(_, _));
83        let values = if nested { Some(vec![Value::Null; rows]) } else { None };
84        let data = if nested { Data::Empty } else { empty_data_for(&ty)? };
85        Ok(Self { ty, rows, data, at: vec![NOWHERE; rows], live: vec![false; rows], values })
86    }
87
88    /// How many rows the finished vector will have.
89    #[must_use]
90    pub fn rows(&self) -> usize {
91        self.rows
92    }
93
94    /// Writes row `n` of `piece` at output row `positions[n]`, for every row of `piece`.
95    ///
96    /// A row claimed twice takes the value the later call gave it, which is not a case `CASE`
97    /// produces, since its arms run over disjoint sets of rows, and is defined rather than left
98    /// open so that a caller that does it gets an answer instead of whichever of the two the copy
99    /// loop happened to reach.
100    ///
101    /// # Errors
102    ///
103    /// If `piece` has a different number of rows than there are positions, if a position is past the
104    /// end of the assembly, or if `piece` is not of a layout that can be laid after what is already
105    /// there.
106    pub fn place(&mut self, positions: &[u32], piece: &Vector) -> Result<()> {
107        if positions.len() != piece.len() {
108            return Err(Error::internal(format!(
109                "a piece of {} rows placed at {} positions",
110                piece.len(),
111                positions.len()
112            )));
113        }
114        for &row in positions {
115            if row as usize >= self.rows {
116                return Err(Error::internal(format!(
117                    "row {row} placed in an assembly of {} rows",
118                    self.rows
119                )));
120            }
121        }
122        if let Some(values) = &mut self.values {
123            // row at a time: the nested fallback named on the field above. A `LIST`, a `STRUCT` and
124            // a `MAP` are a child vector and a run of entries rather than a run of values, so there
125            // is no buffer to lay one after another and no typed copy to do the interleave with.
126            for (slot, &row) in positions.iter().enumerate() {
127                values[row as usize] = piece.value_at(slot);
128            }
129            return Ok(());
130        }
131        // flatten: the copy loop that does the interleave reads a run of data, and a piece can
132        // arrive constant, dictionary encoded or bit packed. Flattening is itself a typed loop per
133        // layout, so writing the piece out once here is what stops it being read a value at a time
134        // later, and a piece that is already flat is not copied at all.
135        let flat = piece.flatten()?;
136        let Some(from) = flat.data() else {
137            return Err(Error::internal("a flattened vector with no run of data in it"));
138        };
139        let start = self.data.len();
140        let appended = extend(&mut self.data, from)?;
141        for (slot, &row) in positions.iter().enumerate() {
142            let row = row as usize;
143            // A piece whose data is empty is the untyped null, so it claims its rows and they are
144            // null, which is what leaving them at `NOWHERE` says.
145            if slot < appended {
146                self.at[row] = start + slot;
147                self.live[row] = !piece.is_null_at(slot);
148            } else {
149                self.at[row] = NOWHERE;
150                self.live[row] = false;
151            }
152        }
153        Ok(())
154    }
155
156    /// The finished vector.
157    ///
158    /// # Errors
159    ///
160    /// If the run of data that came out of the pieces is not one the type can hold.
161    pub fn finish(self) -> Result<Vector> {
162        if let Some(values) = self.values {
163            return Vector::from_values(self.ty, &values);
164        }
165        // An untyped null has no run of data to gather out of, and a gather over one would give a
166        // vector of no values calling itself `rows` long.
167        if matches!(self.data, Data::Empty) {
168            return Ok(Vector::constant(self.ty, Value::Null, self.rows));
169        }
170        let validity = Validity::from_run(&self.live);
171        // A string column is finished by permuting views rather than by copying bytes. Sixteen
172        // bytes a row move and the payload stays in the arena the pieces were appended into, which
173        // is the same trade [`crate::vector::Body::Views`] is for a page. It matters most where the
174        // permutation is the identity and the whole thing is a move, which is every column of a
175        // hash join's gathered side.
176        if let Data::Varlen(column) = self.data {
177            let (laid, arena) = column.into_parts();
178            let arena = Arc::new(arena);
179            if straight(&self.at) {
180                return Ok(Vector::string_views(self.ty, laid, arena)?.with_validity(validity));
181            }
182            let views = self
183                .at
184                .iter()
185                .map(|&index| laid.get(index).copied().unwrap_or_else(StringView::empty))
186                .collect();
187            return Ok(Vector::string_views(self.ty, views, arena)?.with_validity(validity));
188        }
189        // Row `n` reads position `n`, so the copy below would be a copy of the run onto itself. An
190        // assembly whose pieces arrived in order and claimed every row is exactly that, and laying
191        // the chunks of a join's gathered side end to end is exactly that.
192        if straight(&self.at) {
193            return Ok(Vector::flat(self.ty, self.data)?.with_validity(validity));
194        }
195        let gathered = copy_of(&self.data, &self.at);
196        Ok(Vector::flat(self.ty, gathered)?.with_validity(validity))
197    }
198}
199
200/// Several vectors of one type laid end to end as one page, or `None` for a run this will not lay.
201///
202/// What a row group of a stored table is built out of. The chunks arrive one at a time and each one
203/// is a separate allocation, and holding a hundred and twenty of them is a hundred and twenty places
204/// a scan of the column has to jump to instead of one run it walks. So they are copied once, into a
205/// page, and every chunk the table hands out afterwards is a window cut out of that page.
206///
207/// # What it will not lay
208///
209/// Anything that is not already a flat run of values, which is answered with `None` rather than with
210/// an error, because a caller that gets one has somewhere to put the pieces and this is a choice
211/// about layout rather than a failure. The reason is that laying an encoded piece end to end means
212/// flattening it, and a dictionary encoded string column flattened is larger than it was and has
213/// thrown away the thing that made it small. A column whose chunks arrive encoded is better left as
214/// the chunks it arrived as, and that is what `None` tells the caller to do.
215///
216/// It is worth saying that this is not a permanent answer. Two encoded chunks that share a
217/// dictionary can be laid end to end by appending their codes, and two that do not can be laid by
218/// merging the two dictionaries, and both are worth doing once there is a measurement asking for
219/// them. Neither is this, and the fallback has to exist either way for the run that mixes forms.
220///
221/// # Strings
222///
223/// A flat varchar piece owns its arena, so a window cut out of a flat varchar page copies every byte
224/// of every long string in the window, which is the whole reason [`Form::StringView`] exists.
225/// So the varlen page comes back as views over one shared arena: the bytes are copied once
226/// here and never again, and a cut afterwards moves sixteen bytes a row the same way it does for a
227/// column of integers.
228///
229/// # Errors
230///
231/// If the type has no flat layout, or if a piece holds fewer values than it says it has rows.
232pub fn concat(ty: &LogicalType, pieces: &[Vector]) -> Result<Option<Vector>> {
233    if pieces.is_empty() {
234        return Ok(None);
235    }
236    // Checked before anything is copied, because the fallback is for the caller to keep the pieces
237    // it already has and a half built page would be work thrown away.
238    let laid = pieces
239        .iter()
240        .all(|piece| piece.form() == Form::Flat && piece.logical_type() == ty && !piece.is_empty());
241    if !laid {
242        return Ok(None);
243    }
244    let rows = pieces.iter().map(Vector::len).sum();
245    // Sized before the first value moves, so the page is one allocation and holds no more than the
246    // rows that went into it. Growing from empty instead ends at the next power of two, which on a
247    // full row group is eight thousand values of slack carried for the life of the table.
248    let mut data = data_for(ty, rows)?;
249    for piece in pieces {
250        let from = piece
251            .data()
252            .ok_or_else(|| Error::internal("a flat vector with no run of data in it"))?;
253        let appended = extend(&mut data, from)?;
254        if appended != piece.len() {
255            return Err(Error::internal(format!(
256                "a piece of {} rows laid {appended} values end to end",
257                piece.len()
258            )));
259        }
260    }
261    let validity = run_of(pieces, rows);
262    if let Data::Varlen(column) = data {
263        let (views, arena) = column.into_parts();
264        let page = Vector::string_views(ty.clone(), views, Arc::new(arena))?;
265        return Ok(Some(page.with_validity(validity)));
266    }
267    Ok(Some(Vector::flat(ty.clone(), data)?.with_validity(validity).into_pages()))
268}
269
270/// The validity of the pieces laid end to end, in `rows` rows.
271///
272/// The two cheap answers are checked for first because they are the answers real data gives. A
273/// column that was never null anywhere is a page with no mask on it at all, and a bit per row read
274/// out of every piece to build a mask that is all ones would be throwing that away.
275fn run_of(pieces: &[Vector], rows: usize) -> Validity {
276    if pieces.iter().all(|piece| matches!(piece.validity(), Validity::AllValid)) {
277        return Validity::AllValid;
278    }
279    if pieces.iter().all(|piece| matches!(piece.validity(), Validity::AllInvalid)) {
280        return Validity::AllInvalid;
281    }
282    let mut live = Vec::with_capacity(rows);
283    for piece in pieces {
284        // row at a time: the mixed case, which is a bit per row however it is written, and it runs
285        // once per column per row group rather than once per chunk.
286        for row in 0..piece.len() {
287            live.push(!piece.is_null_at(row));
288        }
289    }
290    Validity::from_run(&live)
291}
292
293/// Whether row `n` reads position `n` for every row, which makes the final gather a copy onto itself.
294fn straight(at: &[usize]) -> bool {
295    at.iter().enumerate().all(|(row, &index)| row == index)
296}
297
298/// Lays a run of data end to end after another, answering how many values it appended.
299///
300/// The typed loop per layout is the whole point: an append of a thousand `i64` is one `memcpy` and
301/// an append of a thousand strings is one arena growth and a thousand sixteen byte views, neither of
302/// which touches a `Value`.
303fn extend(into: &mut Data, from: &Data) -> Result<usize> {
304    macro_rules! extended {
305        ($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
306            match (&mut *into, from) {
307                // Nothing to append, which is what an untyped null piece is. The caller reads the
308                // count and leaves those rows null rather than pointing them anywhere.
309                (_, Data::Empty) => Ok(0),
310                $((Data::$variant(out), Data::$variant(values)) => {
311                    out.extend_from_slice(values.as_slice());
312                    Ok(values.len())
313                })+
314                // The one layout where an append is a copy of bytes rather than a copy of fixed
315                // width slots. The arena is grown once for all of them, because a view carries its
316                // length so the total is known before any of the bytes move.
317                (Data::Varlen(out), Data::Varlen(values)) => {
318                    out.reserve_views(values.len());
319                    out.reserve_bytes(
320                        values
321                            .views()
322                            .iter()
323                            .filter(|view| !view.is_inline())
324                            .map(StringView::len)
325                            .sum(),
326                    );
327                    for index in 0..values.len() {
328                        out.push_from(values, index);
329                    }
330                    Ok(values.len())
331                }
332                (out, from) => Err(Error::internal(format!(
333                    "a run of {:?} values cannot be laid after a run of {:?} ones",
334                    layout_of(from),
335                    layout_of(out)
336                ))),
337            }
338        };
339    }
340    crate::for_each_layout!(fixed, extended)
341}
342
343/// Unit tests for the assembly.
344///
345/// That the engine actually goes through here rather than through the old path was checked rather
346/// than assumed, by gating a panic on [`Assembly::place`] and running the `rudb` suite with it
347/// armed. Three tests failed and no others: the one that is a `CASE` by name, the one that runs the
348/// catalog views the engine ships with, and the one over the native frequency synopsis. All three
349/// have a `CASE` in them and nothing else in the suite does.
350#[cfg(test)]
351mod tests {
352    use super::*;
353    use crate::{Chunk, Form};
354
355    /// Every row of a vector, as values, which is what an assembly is checked against.
356    fn values(vector: &Vector) -> Vec<Value> {
357        (0..vector.len()).map(|row| vector.value_at(row)).collect()
358    }
359
360    /// The answer the assembly has to reach, written the slow obvious way.
361    ///
362    /// A `Vec<Value>` filled by scattering and then handed to [`Vector::from_values`] is exactly
363    /// what `CASE` used to do, so this is the reference rather than a second opinion.
364    fn scattered(ty: &LogicalType, rows: usize, pieces: &[(Vec<u32>, Vector)]) -> Vector {
365        let mut answers = vec![Value::Null; rows];
366        for (positions, piece) in pieces {
367            for (slot, &row) in positions.iter().enumerate() {
368                answers[row as usize] = piece.value_at(slot);
369            }
370        }
371        Vector::from_values(ty.clone(), &answers).expect("the reference builds")
372    }
373
374    /// Builds an assembly out of pieces and checks it against the slow way of getting there.
375    fn agrees(ty: &LogicalType, rows: usize, pieces: &[(Vec<u32>, Vector)]) -> Vector {
376        let mut assembly = Assembly::new(ty.clone(), rows).expect("an assembly of this type");
377        for (positions, piece) in pieces {
378            assembly.place(positions, piece).expect("the piece is placed");
379        }
380        let built = assembly.finish().expect("the assembly finishes");
381        assert_eq!(built.len(), rows, "an assembly of {rows} rows");
382        assert_eq!(values(&built), values(&scattered(ty, rows, pieces)), "against the slow way");
383        built
384    }
385
386    #[test]
387    fn two_pieces_interleave_back_into_the_order_the_rows_came_in() {
388        let evens = Vector::from_values(LogicalType::BigInt, &[Value::BigInt(0), Value::BigInt(2)])
389            .expect("a vector");
390        let odds = Vector::from_values(LogicalType::BigInt, &[Value::BigInt(1), Value::BigInt(3)])
391            .expect("a vector");
392        let built = agrees(&LogicalType::BigInt, 4, &[(vec![0, 2], evens), (vec![1, 3], odds)]);
393        assert_eq!(
394            values(&built),
395            vec![Value::BigInt(0), Value::BigInt(1), Value::BigInt(2), Value::BigInt(3)]
396        );
397    }
398
399    #[test]
400    fn a_row_no_piece_claims_is_null() {
401        // Which is what a `CASE` with no `ELSE` leaves behind, and the case where a run of data with
402        // a hole in it would put every value after the hole at the wrong index.
403        let piece =
404            Vector::from_values(LogicalType::BigInt, &[Value::BigInt(7)]).expect("a vector");
405        let built = agrees(&LogicalType::BigInt, 3, &[(vec![1], piece)]);
406        assert_eq!(values(&built), vec![Value::Null, Value::BigInt(7), Value::Null]);
407    }
408
409    #[test]
410    fn no_pieces_at_all_is_a_column_of_nulls_of_the_right_length() {
411        let built = agrees(&LogicalType::Integer, 5, &[]);
412        assert!(built.is_null_at(4), "every row of it is null");
413    }
414
415    #[test]
416    fn a_null_inside_a_piece_stays_null_where_the_piece_put_it() {
417        // The validity has to survive the copy, and the value under it has to not be read, which is
418        // two different things a single run of data with a mask over it can get wrong separately.
419        let piece = Vector::from_values(
420            LogicalType::BigInt,
421            &[Value::BigInt(1), Value::Null, Value::BigInt(3)],
422        )
423        .expect("a vector");
424        let built = agrees(&LogicalType::BigInt, 3, &[(vec![2, 0, 1], piece)]);
425        assert!(built.is_null_at(0), "the null landed where the piece put it");
426        assert_eq!(built.value_at(2), Value::BigInt(1));
427    }
428
429    #[test]
430    fn strings_are_assembled_without_going_through_a_value_each() {
431        let left = Vector::from_values(
432            LogicalType::Varchar,
433            &[Value::Varchar("a short one".into()), Value::Varchar("another".into())],
434        )
435        .expect("a vector");
436        let right = Vector::from_values(
437            LogicalType::Varchar,
438            &[Value::Varchar("a string that is far too long to live inline in a view".into())],
439        )
440        .expect("a vector");
441        let built = agrees(&LogicalType::Varchar, 3, &[(vec![0, 2], left), (vec![1], right)]);
442        assert_eq!(built.value_at(0), Value::Varchar("a short one".into()));
443        assert_eq!(
444            built.value_at(1),
445            Value::Varchar("a string that is far too long to live inline in a view".into())
446        );
447        assert_eq!(built.value_at(2), Value::Varchar("another".into()));
448    }
449
450    #[test]
451    fn strings_laid_end_to_end_in_order_come_back_as_views_over_the_arena_they_went_into() {
452        // The shape a hash join's gathered side is: chunk after chunk, each claiming the rows
453        // straight after the last, so the permutation is the identity and nothing needs moving.
454        let first = Vector::from_values(
455            LogicalType::Varchar,
456            &[Value::Varchar("one".into()), Value::Varchar("two".into())],
457        )
458        .expect("a vector");
459        let second = Vector::from_values(
460            LogicalType::Varchar,
461            &[Value::Varchar("a third one long enough to be out of line".into())],
462        )
463        .expect("a vector");
464        let built = agrees(&LogicalType::Varchar, 3, &[(vec![0, 1], first), (vec![2], second)]);
465        assert_eq!(built.form(), Form::StringView, "the bytes stay where they were appended");
466        assert_eq!(
467            built.value_at(2),
468            Value::Varchar("a third one long enough to be out of line".into())
469        );
470    }
471
472    #[test]
473    fn a_string_row_no_piece_claims_is_null_rather_than_empty() {
474        // The hole a `CASE` with no `ELSE` leaves, on the path that permutes views instead of
475        // copying bytes, where an unclaimed row has no view to read and has to come out null.
476        let piece = Vector::from_values(
477            LogicalType::Varchar,
478            &[Value::Varchar("a value long enough to be out of line".into())],
479        )
480        .expect("a vector");
481        let built = agrees(&LogicalType::Varchar, 3, &[(vec![2], piece)]);
482        assert_eq!(built.value_at(0), Value::Null);
483        assert_eq!(built.value_at(1), Value::Null);
484        assert_eq!(
485            built.value_at(2),
486            Value::Varchar("a value long enough to be out of line".into())
487        );
488    }
489
490    #[test]
491    fn a_constant_piece_is_written_out_rather_than_read_a_row_at_a_time() {
492        // The `ELSE ''` half of the ClickBench query this was built for, which arrives as a constant
493        // over however many rows the arms did not claim.
494        let arm = Vector::from_values(LogicalType::Varchar, &[Value::Varchar("kept".into())])
495            .expect("a vector");
496        let otherwise = Vector::constant(LogicalType::Varchar, Value::Varchar("".into()), 3);
497        let built = agrees(&LogicalType::Varchar, 4, &[(vec![2], arm), (vec![0, 1, 3], otherwise)]);
498        assert_eq!(built.value_at(0), Value::Varchar("".into()));
499        assert_eq!(built.value_at(2), Value::Varchar("kept".into()));
500    }
501
502    #[test]
503    fn a_dictionary_piece_is_walked_to_its_values() {
504        // A scanned string column arrives as codes over a shared dictionary, so this is the form the
505        // `THEN Referer` arm of the ClickBench query actually hands over.
506        let dictionary = Vector::from_values(
507            LogicalType::Varchar,
508            &[Value::Varchar("one".into()), Value::Varchar("two".into())],
509        )
510        .expect("a dictionary");
511        let piece = Vector::dictionary(vec![1, 0, 1], dictionary).expect("a dictionary vector");
512        let built = agrees(&LogicalType::Varchar, 3, &[(vec![0, 1, 2], piece)]);
513        assert_eq!(
514            values(&built),
515            vec![
516                Value::Varchar("two".into()),
517                Value::Varchar("one".into()),
518                Value::Varchar("two".into())
519            ]
520        );
521    }
522
523    #[test]
524    fn a_piece_placed_at_the_wrong_number_of_positions_is_an_error() {
525        let piece =
526            Vector::from_values(LogicalType::BigInt, &[Value::BigInt(1)]).expect("a vector");
527        let mut assembly = Assembly::new(LogicalType::BigInt, 4).expect("an assembly");
528        assert!(assembly.place(&[0, 1], &piece).is_err(), "two positions for one row");
529    }
530
531    #[test]
532    fn a_position_past_the_end_is_an_error_rather_than_a_lost_row() {
533        let piece =
534            Vector::from_values(LogicalType::BigInt, &[Value::BigInt(1)]).expect("a vector");
535        let mut assembly = Assembly::new(LogicalType::BigInt, 2).expect("an assembly");
536        assert!(assembly.place(&[9], &piece).is_err(), "a row past the end of the assembly");
537    }
538
539    #[test]
540    fn a_piece_of_the_wrong_layout_is_an_error_rather_than_a_wrong_answer() {
541        // Two runs of data that cannot be laid end to end, which is a bug in whoever built the
542        // pieces and has to say so rather than silently keep the first one.
543        let piece =
544            Vector::from_values(LogicalType::Varchar, &[Value::Varchar("x".into())]).expect("text");
545        let mut assembly = Assembly::new(LogicalType::BigInt, 1).expect("an assembly");
546        assert!(assembly.place(&[0], &piece).is_err(), "text laid after integers");
547    }
548
549    #[test]
550    fn every_layout_assembles_the_way_it_scatters() {
551        // One case per physical layout, because the copy loop is generated per layout and a layout
552        // missing from it is a wrong answer for that type alone, which no single typed test finds.
553        let cases: Vec<(LogicalType, Vec<Value>)> = vec![
554            (LogicalType::Boolean, vec![Value::Boolean(true), Value::Boolean(false)]),
555            (LogicalType::TinyInt, vec![Value::TinyInt(1), Value::TinyInt(-2)]),
556            (LogicalType::SmallInt, vec![Value::SmallInt(3), Value::SmallInt(-4)]),
557            (LogicalType::Integer, vec![Value::Integer(5), Value::Integer(-6)]),
558            (LogicalType::BigInt, vec![Value::BigInt(7), Value::BigInt(-8)]),
559            (LogicalType::HugeInt, vec![Value::HugeInt(9), Value::HugeInt(-10)]),
560            (LogicalType::UTinyInt, vec![Value::UTinyInt(11), Value::UTinyInt(12)]),
561            (LogicalType::USmallInt, vec![Value::USmallInt(13), Value::USmallInt(14)]),
562            (LogicalType::UInteger, vec![Value::UInteger(15), Value::UInteger(16)]),
563            (LogicalType::UBigInt, vec![Value::UBigInt(17), Value::UBigInt(18)]),
564            (LogicalType::Float, vec![Value::Float(1.5), Value::Float(-2.5)]),
565            (LogicalType::Double, vec![Value::Double(3.5), Value::Double(-4.5)]),
566            (
567                LogicalType::Varchar,
568                vec![Value::Varchar("first".into()), Value::Varchar("second".into())],
569            ),
570            (LogicalType::Date, vec![Value::Date(19), Value::Date(20)]),
571        ];
572        for (ty, pair) in cases {
573            let left = Vector::from_values(ty.clone(), &pair[..1]).expect("a vector");
574            let right = Vector::from_values(ty.clone(), &pair[1..]).expect("a vector");
575            let built = agrees(&ty, 2, &[(vec![1], left), (vec![0], right)]);
576            assert_eq!(built.value_at(0), pair[1], "{ty:?} at row 0");
577            assert_eq!(built.value_at(1), pair[0], "{ty:?} at row 1");
578        }
579    }
580
581    #[test]
582    fn an_assembly_is_a_chunk_column_like_any_other() {
583        // The point of building a vector rather than a `Vec<Value>` is that what comes out goes
584        // straight into a chunk, so this checks it actually does.
585        let piece = Vector::from_values(LogicalType::BigInt, &[Value::BigInt(1), Value::BigInt(2)])
586            .expect("a vector");
587        let built = agrees(&LogicalType::BigInt, 2, &[(vec![1, 0], piece)]);
588        let chunk = Chunk::new(vec![built]).expect("a chunk of one column");
589        assert_eq!(chunk.len(), 2, "two rows");
590    }
591
592    /// A run of pieces, as values, in the order they were given.
593    fn all_of(pieces: &[Vector]) -> Vec<Value> {
594        pieces.iter().flat_map(values).collect()
595    }
596
597    /// The pieces laid end to end, checked against the values that went in.
598    fn laid(ty: &LogicalType, pieces: &[Vector]) -> Vector {
599        let built = concat(ty, pieces).expect("the pieces lay").expect("this run lays");
600        assert_eq!(built.len(), pieces.iter().map(Vector::len).sum::<usize>(), "the row count");
601        assert_eq!(values(&built), all_of(pieces), "the values laid end to end");
602        built
603    }
604
605    #[test]
606    fn pieces_laid_end_to_end_read_back_in_the_order_they_were_given() {
607        let piece = |from: i64, to: i64| {
608            let held: Vec<Value> = (from..to).map(Value::BigInt).collect();
609            Vector::from_values(LogicalType::BigInt, &held).expect("a run of bigints")
610        };
611        let pieces = [piece(0, 4), piece(4, 9), piece(9, 10)];
612        let built = laid(&LogicalType::BigInt, &pieces);
613        assert_eq!(built.form(), Form::Flat, "a run of flat pieces lays flat");
614        // The point of the page: a window cut out of it is a reference count bump and not a copy,
615        // which is what the table cuts a chunk with.
616        let window = built.slice(4, 5).expect("a window into the page");
617        assert_eq!(values(&window), all_of(&pieces[1..2]), "the second piece, cut back out");
618    }
619
620    #[test]
621    fn a_null_in_a_piece_is_a_null_in_the_same_row_of_the_page() {
622        let ty = LogicalType::Integer;
623        let whole = Vector::from_values(ty.clone(), &[Value::Integer(1), Value::Integer(2)])
624            .expect("no nulls");
625        let holed =
626            Vector::from_values(ty.clone(), &[Value::Null, Value::Integer(4)]).expect("one null");
627        let built = laid(&ty, &[whole.clone(), holed.clone()]);
628        assert!(!built.is_null_at(1), "a row that was not null became one");
629        assert!(built.is_null_at(2), "the null did not come through");
630        // A run with no null anywhere keeps the cheap answer rather than growing a mask of ones.
631        let clean = laid(&ty, &[whole.clone(), whole]);
632        assert_eq!(clean.validity(), &Validity::AllValid, "a mask nothing needed");
633        let empty = laid(&ty, &[holed.clone(), holed]);
634        assert!(empty.is_null_at(0) && empty.is_null_at(2), "both nulls came through");
635    }
636
637    /// The string case, which is the one that would be a byte copy per cut if it laid flat.
638    #[test]
639    fn strings_lay_into_one_arena_and_come_back_as_views() {
640        let ty = LogicalType::Varchar;
641        let word = |text: &str| {
642            Vector::from_values(ty.clone(), &[Value::Varchar(text.to_string())]).expect("a string")
643        };
644        let pieces = [word("a string too long to sit inside a view"), word("short")];
645        let built = laid(&ty, &pieces);
646        assert_eq!(
647            built.form(),
648            Form::StringView,
649            "a varchar page that is not views cuts by copying"
650        );
651        let window = built.slice(0, 1).expect("a window into the page");
652        assert_eq!(values(&window), all_of(&pieces[..1]), "the long string, cut back out");
653    }
654
655    /// What will not lay, which is a layout answer and not an error.
656    #[test]
657    fn an_encoded_piece_is_left_alone_rather_than_flattened() {
658        let ty = LogicalType::BigInt;
659        let flat = Vector::from_values(ty.clone(), &[Value::BigInt(1)]).expect("a flat piece");
660        let values = Vector::from_values(ty.clone(), &[Value::BigInt(7), Value::BigInt(8)])
661            .expect("two distinct values");
662        let coded = Vector::dictionary(vec![0, 1, 0], values).expect("a dictionary piece");
663        let one = std::slice::from_ref(&coded);
664        assert!(concat(&ty, one).expect("no error").is_none(), "a dictionary laid");
665        assert!(
666            concat(&ty, &[flat.clone(), coded]).expect("no error").is_none(),
667            "a mixed run laid"
668        );
669        assert!(concat(&ty, &[]).expect("no error").is_none(), "nothing laid into something");
670        // A piece of another type is the caller's mistake and is still answered as a layout it will
671        // not build, because the fallback keeps the pieces and keeping them is always correct.
672        let other =
673            Vector::from_values(LogicalType::Integer, &[Value::Integer(1)]).expect("an int");
674        assert!(concat(&ty, &[flat, other]).expect("no error").is_none(), "two types laid");
675    }
676}