use rudb_common::{Error, LogicalType, Result, Value};
use rudb_vector::{Assembly, Chunk, Vector};
pub(crate) const PAD: u32 = u32::MAX;
#[derive(Debug, Default)]
pub(crate) struct Build {
columns: Vec<Vector>,
rows: usize,
}
impl Build {
pub(crate) fn new(types: &[LogicalType], chunks: &[Chunk]) -> Result<Self> {
let rows: usize = chunks.iter().map(Chunk::len).sum();
if rows >= PAD as usize {
return Err(Error::out_of_range(format!(
"a join cannot gather {rows} rows, which is more than a position can name"
)));
}
let mut columns = Vec::with_capacity(types.len());
let mut at: Vec<u32> = Vec::new();
for (index, ty) in types.iter().enumerate() {
let mut assembly = Assembly::new(ty.clone(), rows)?;
let mut base: u32 = 0;
for chunk in chunks {
let len = u32::try_from(chunk.len()).unwrap_or(PAD);
at.clear();
at.extend(base..base + len);
assembly.place(&at, chunk.column(index)?)?;
base += len;
}
columns.push(assembly.finish()?);
}
Ok(Self { columns, rows })
}
pub(crate) fn rows(&self) -> usize {
self.rows
}
pub(crate) fn footprint(&self) -> u64 {
self.columns
.iter()
.map(|column| u64::try_from(column.footprint()).unwrap_or(u64::MAX))
.sum()
}
pub(crate) fn gather(&self, at: &[u32]) -> Result<Vec<Vector>> {
self.columns.iter().map(|column| column.gather(at)).collect()
}
pub(crate) fn chunk(&self, at: &[u32]) -> Result<Chunk> {
Chunk::with_rows(self.gather(at)?, at.len())
}
pub(crate) fn row(&self, at: u32) -> Vec<Value> {
self.columns.iter().map(|column| column.value_at(at as usize)).collect()
}
}
#[cfg(test)]
mod tests {
use rudb_common::{LogicalType, Value};
use rudb_vector::{Chunk, Data, Vector};
use super::{Build, PAD};
fn chunk(values: &[i32], text: &[&str]) -> Chunk {
let numbers = Vector::flat(LogicalType::Integer, Data::Int32(values.to_vec().into()))
.expect("integers are an i32 layout");
let strings = Vector::from_values(
LogicalType::Varchar,
&text.iter().map(|&word| Value::Varchar(word.to_string())).collect::<Vec<_>>(),
)
.expect("strings are a varlen layout");
Chunk::new(vec![numbers, strings]).expect("two columns of the same length")
}
fn types() -> Vec<LogicalType> {
vec![LogicalType::Integer, LogicalType::Varchar]
}
#[test]
fn chunks_laid_end_to_end_read_back_in_the_order_they_were_given() {
let side = Build::new(&types(), &[chunk(&[1, 2], &["a", "b"]), chunk(&[3], &["c"])])
.expect("two chunks of two columns");
assert_eq!(side.rows(), 3);
let gathered = side.gather(&[0, 1, 2]).expect("three positions in range");
assert_eq!(gathered[0].value_at(0), Value::Integer(1));
assert_eq!(gathered[0].value_at(2), Value::Integer(3));
assert_eq!(gathered[1].value_at(1), Value::Varchar("b".to_string()));
assert_eq!(gathered[1].value_at(2), Value::Varchar("c".to_string()));
}
#[test]
fn a_position_may_be_asked_for_more_than_once_and_in_any_order() {
let side = Build::new(&types(), &[chunk(&[10, 20], &["x", "y"])])
.expect("one chunk of two columns");
let gathered = side.gather(&[1, 1, 0]).expect("three positions in range");
assert_eq!(gathered[0].value_at(0), Value::Integer(20));
assert_eq!(gathered[0].value_at(1), Value::Integer(20));
assert_eq!(gathered[0].value_at(2), Value::Integer(10));
}
#[test]
fn the_padding_position_reads_as_null_in_every_column() {
let side = Build::new(&types(), &[chunk(&[7], &["z"])]).expect("one chunk of two columns");
let gathered = side.gather(&[PAD, 0]).expect("a padded position and a real one");
assert_eq!(gathered[0].value_at(0), Value::Null);
assert_eq!(gathered[1].value_at(0), Value::Null);
assert_eq!(gathered[0].value_at(1), Value::Integer(7));
}
#[test]
fn a_side_with_no_chunks_still_has_its_columns_and_every_one_of_them_is_null() {
let side = Build::new(&types(), &[]).expect("no chunks at all");
assert_eq!(side.rows(), 0);
let gathered = side.gather(&[PAD, PAD]).expect("two padded positions");
assert_eq!(gathered.len(), 2);
assert_eq!(gathered[0].value_at(0), Value::Null);
assert_eq!(gathered[1].value_at(1), Value::Null);
}
#[test]
fn a_row_read_as_values_is_the_row_that_went_in() {
let side = Build::new(&types(), &[chunk(&[4, 5], &["p", "q"])]).expect("one chunk");
assert_eq!(side.row(1), vec![Value::Integer(5), Value::Varchar("q".to_string())]);
}
}