use rudb_common::{Error, LogicalType, Result, Value};
use crate::string::StringView;
use crate::validity::Validity;
use crate::vector::{Data, NOWHERE, Vector, copy_of, empty_data_for, layout_of};
#[derive(Debug)]
pub struct Assembly {
ty: LogicalType,
rows: usize,
data: Data,
at: Vec<usize>,
live: Vec<bool>,
values: Option<Vec<Value>>,
}
impl Assembly {
pub fn new(ty: LogicalType, rows: usize) -> Result<Self> {
let nested =
matches!(ty, LogicalType::List(_) | LogicalType::Struct(_) | LogicalType::Map(_, _));
let values = if nested { Some(vec![Value::Null; rows]) } else { None };
let data = if nested { Data::Empty } else { empty_data_for(&ty)? };
Ok(Self { ty, rows, data, at: vec![NOWHERE; rows], live: vec![false; rows], values })
}
#[must_use]
pub fn rows(&self) -> usize {
self.rows
}
pub fn place(&mut self, positions: &[u32], piece: &Vector) -> Result<()> {
if positions.len() != piece.len() {
return Err(Error::internal(format!(
"a piece of {} rows placed at {} positions",
piece.len(),
positions.len()
)));
}
for &row in positions {
if row as usize >= self.rows {
return Err(Error::internal(format!(
"row {row} placed in an assembly of {} rows",
self.rows
)));
}
}
if let Some(values) = &mut self.values {
for (slot, &row) in positions.iter().enumerate() {
values[row as usize] = piece.value_at(slot);
}
return Ok(());
}
let flat = piece.flatten()?;
let Some(from) = flat.data() else {
return Err(Error::internal("a flattened vector with no run of data in it"));
};
let start = self.data.len();
let appended = extend(&mut self.data, from)?;
for (slot, &row) in positions.iter().enumerate() {
let row = row as usize;
if slot < appended {
self.at[row] = start + slot;
self.live[row] = !piece.is_null_at(slot);
} else {
self.at[row] = NOWHERE;
self.live[row] = false;
}
}
Ok(())
}
pub fn finish(self) -> Result<Vector> {
if let Some(values) = self.values {
return Vector::from_values(self.ty, &values);
}
if matches!(self.data, Data::Empty) {
return Ok(Vector::constant(self.ty, Value::Null, self.rows));
}
let gathered = copy_of(&self.data, &self.at);
Ok(Vector::flat(self.ty, gathered)?.with_validity(Validity::from_run(&self.live)))
}
}
fn extend(into: &mut Data, from: &Data) -> Result<usize> {
macro_rules! extended {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match (&mut *into, from) {
(_, Data::Empty) => Ok(0),
$((Data::$variant(out), Data::$variant(values)) => {
out.extend_from_slice(values.as_slice());
Ok(values.len())
})+
(Data::Varlen(out), Data::Varlen(values)) => {
out.reserve_views(values.len());
out.reserve_bytes(
values
.views()
.iter()
.filter(|view| !view.is_inline())
.map(StringView::len)
.sum(),
);
for index in 0..values.len() {
out.push_from(values, index);
}
Ok(values.len())
}
(out, from) => Err(Error::internal(format!(
"a run of {:?} values cannot be laid after a run of {:?} ones",
layout_of(from),
layout_of(out)
))),
}
};
}
crate::for_each_layout!(fixed, extended)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Chunk;
fn values(vector: &Vector) -> Vec<Value> {
(0..vector.len()).map(|row| vector.value_at(row)).collect()
}
fn scattered(ty: &LogicalType, rows: usize, pieces: &[(Vec<u32>, Vector)]) -> Vector {
let mut answers = vec![Value::Null; rows];
for (positions, piece) in pieces {
for (slot, &row) in positions.iter().enumerate() {
answers[row as usize] = piece.value_at(slot);
}
}
Vector::from_values(ty.clone(), &answers).expect("the reference builds")
}
fn agrees(ty: &LogicalType, rows: usize, pieces: &[(Vec<u32>, Vector)]) -> Vector {
let mut assembly = Assembly::new(ty.clone(), rows).expect("an assembly of this type");
for (positions, piece) in pieces {
assembly.place(positions, piece).expect("the piece is placed");
}
let built = assembly.finish().expect("the assembly finishes");
assert_eq!(built.len(), rows, "an assembly of {rows} rows");
assert_eq!(values(&built), values(&scattered(ty, rows, pieces)), "against the slow way");
built
}
#[test]
fn two_pieces_interleave_back_into_the_order_the_rows_came_in() {
let evens = Vector::from_values(LogicalType::BigInt, &[Value::BigInt(0), Value::BigInt(2)])
.expect("a vector");
let odds = Vector::from_values(LogicalType::BigInt, &[Value::BigInt(1), Value::BigInt(3)])
.expect("a vector");
let built = agrees(&LogicalType::BigInt, 4, &[(vec![0, 2], evens), (vec![1, 3], odds)]);
assert_eq!(
values(&built),
vec![Value::BigInt(0), Value::BigInt(1), Value::BigInt(2), Value::BigInt(3)]
);
}
#[test]
fn a_row_no_piece_claims_is_null() {
let piece =
Vector::from_values(LogicalType::BigInt, &[Value::BigInt(7)]).expect("a vector");
let built = agrees(&LogicalType::BigInt, 3, &[(vec![1], piece)]);
assert_eq!(values(&built), vec![Value::Null, Value::BigInt(7), Value::Null]);
}
#[test]
fn no_pieces_at_all_is_a_column_of_nulls_of_the_right_length() {
let built = agrees(&LogicalType::Integer, 5, &[]);
assert!(built.is_null_at(4), "every row of it is null");
}
#[test]
fn a_null_inside_a_piece_stays_null_where_the_piece_put_it() {
let piece = Vector::from_values(
LogicalType::BigInt,
&[Value::BigInt(1), Value::Null, Value::BigInt(3)],
)
.expect("a vector");
let built = agrees(&LogicalType::BigInt, 3, &[(vec![2, 0, 1], piece)]);
assert!(built.is_null_at(0), "the null landed where the piece put it");
assert_eq!(built.value_at(2), Value::BigInt(1));
}
#[test]
fn strings_are_assembled_without_going_through_a_value_each() {
let left = Vector::from_values(
LogicalType::Varchar,
&[Value::Varchar("a short one".into()), Value::Varchar("another".into())],
)
.expect("a vector");
let right = Vector::from_values(
LogicalType::Varchar,
&[Value::Varchar("a string that is far too long to live inline in a view".into())],
)
.expect("a vector");
let built = agrees(&LogicalType::Varchar, 3, &[(vec![0, 2], left), (vec![1], right)]);
assert_eq!(built.value_at(0), Value::Varchar("a short one".into()));
assert_eq!(
built.value_at(1),
Value::Varchar("a string that is far too long to live inline in a view".into())
);
assert_eq!(built.value_at(2), Value::Varchar("another".into()));
}
#[test]
fn a_constant_piece_is_written_out_rather_than_read_a_row_at_a_time() {
let arm = Vector::from_values(LogicalType::Varchar, &[Value::Varchar("kept".into())])
.expect("a vector");
let otherwise = Vector::constant(LogicalType::Varchar, Value::Varchar("".into()), 3);
let built = agrees(&LogicalType::Varchar, 4, &[(vec![2], arm), (vec![0, 1, 3], otherwise)]);
assert_eq!(built.value_at(0), Value::Varchar("".into()));
assert_eq!(built.value_at(2), Value::Varchar("kept".into()));
}
#[test]
fn a_dictionary_piece_is_walked_to_its_values() {
let dictionary = Vector::from_values(
LogicalType::Varchar,
&[Value::Varchar("one".into()), Value::Varchar("two".into())],
)
.expect("a dictionary");
let piece = Vector::dictionary(vec![1, 0, 1], dictionary).expect("a dictionary vector");
let built = agrees(&LogicalType::Varchar, 3, &[(vec![0, 1, 2], piece)]);
assert_eq!(
values(&built),
vec![
Value::Varchar("two".into()),
Value::Varchar("one".into()),
Value::Varchar("two".into())
]
);
}
#[test]
fn a_piece_placed_at_the_wrong_number_of_positions_is_an_error() {
let piece =
Vector::from_values(LogicalType::BigInt, &[Value::BigInt(1)]).expect("a vector");
let mut assembly = Assembly::new(LogicalType::BigInt, 4).expect("an assembly");
assert!(assembly.place(&[0, 1], &piece).is_err(), "two positions for one row");
}
#[test]
fn a_position_past_the_end_is_an_error_rather_than_a_lost_row() {
let piece =
Vector::from_values(LogicalType::BigInt, &[Value::BigInt(1)]).expect("a vector");
let mut assembly = Assembly::new(LogicalType::BigInt, 2).expect("an assembly");
assert!(assembly.place(&[9], &piece).is_err(), "a row past the end of the assembly");
}
#[test]
fn a_piece_of_the_wrong_layout_is_an_error_rather_than_a_wrong_answer() {
let piece =
Vector::from_values(LogicalType::Varchar, &[Value::Varchar("x".into())]).expect("text");
let mut assembly = Assembly::new(LogicalType::BigInt, 1).expect("an assembly");
assert!(assembly.place(&[0], &piece).is_err(), "text laid after integers");
}
#[test]
fn every_layout_assembles_the_way_it_scatters() {
let cases: Vec<(LogicalType, Vec<Value>)> = vec![
(LogicalType::Boolean, vec![Value::Boolean(true), Value::Boolean(false)]),
(LogicalType::TinyInt, vec![Value::TinyInt(1), Value::TinyInt(-2)]),
(LogicalType::SmallInt, vec![Value::SmallInt(3), Value::SmallInt(-4)]),
(LogicalType::Integer, vec![Value::Integer(5), Value::Integer(-6)]),
(LogicalType::BigInt, vec![Value::BigInt(7), Value::BigInt(-8)]),
(LogicalType::HugeInt, vec![Value::HugeInt(9), Value::HugeInt(-10)]),
(LogicalType::UTinyInt, vec![Value::UTinyInt(11), Value::UTinyInt(12)]),
(LogicalType::USmallInt, vec![Value::USmallInt(13), Value::USmallInt(14)]),
(LogicalType::UInteger, vec![Value::UInteger(15), Value::UInteger(16)]),
(LogicalType::UBigInt, vec![Value::UBigInt(17), Value::UBigInt(18)]),
(LogicalType::Float, vec![Value::Float(1.5), Value::Float(-2.5)]),
(LogicalType::Double, vec![Value::Double(3.5), Value::Double(-4.5)]),
(
LogicalType::Varchar,
vec![Value::Varchar("first".into()), Value::Varchar("second".into())],
),
(LogicalType::Date, vec![Value::Date(19), Value::Date(20)]),
];
for (ty, pair) in cases {
let left = Vector::from_values(ty.clone(), &pair[..1]).expect("a vector");
let right = Vector::from_values(ty.clone(), &pair[1..]).expect("a vector");
let built = agrees(&ty, 2, &[(vec![1], left), (vec![0], right)]);
assert_eq!(built.value_at(0), pair[1], "{ty:?} at row 0");
assert_eq!(built.value_at(1), pair[0], "{ty:?} at row 1");
}
}
#[test]
fn an_assembly_is_a_chunk_column_like_any_other() {
let piece = Vector::from_values(LogicalType::BigInt, &[Value::BigInt(1), Value::BigInt(2)])
.expect("a vector");
let built = agrees(&LogicalType::BigInt, 2, &[(vec![1, 0], piece)]);
let chunk = Chunk::new(vec![built]).expect("a chunk of one column");
assert_eq!(chunk.len(), 2, "two rows");
}
}