use rudb_common::{Error, LogicalType, Result, Value};
use crate::selection::Selection;
use crate::vector::{VECTOR_SIZE, Vector};
#[derive(Debug, Clone, PartialEq)]
pub struct Chunk {
columns: Vec<Vector>,
rows: usize,
}
impl Chunk {
pub fn new(columns: Vec<Vector>) -> Result<Self> {
let rows = columns.first().map_or(0, Vector::len);
Self::with_rows(columns, rows)
}
pub fn with_rows(columns: Vec<Vector>, rows: usize) -> Result<Self> {
if rows > VECTOR_SIZE {
return Err(Error::internal(format!(
"a chunk of {rows} rows is longer than the {VECTOR_SIZE} row vector"
)));
}
for (index, column) in columns.iter().enumerate() {
if column.len() != rows {
return Err(Error::internal(format!(
"column {index} of a chunk is {} rows and the chunk is {rows}",
column.len()
)));
}
}
Ok(Self { columns, rows })
}
#[must_use]
pub fn empty(types: &[LogicalType]) -> Self {
let columns =
types.iter().map(|ty| Vector::constant(ty.clone(), Value::Null, 0)).collect::<Vec<_>>();
Self { columns, rows: 0 }
}
#[must_use]
pub fn columns(&self) -> &[Vector] {
&self.columns
}
pub fn column(&self, index: usize) -> Result<&Vector> {
self.columns.get(index).ok_or_else(|| {
Error::internal(format!(
"column {index} of a chunk that has {} columns",
self.columns.len()
))
})
}
#[must_use]
pub fn into_columns(self) -> Vec<Vector> {
self.columns
}
#[must_use]
pub fn width(&self) -> usize {
self.columns.len()
}
#[must_use]
pub fn len(&self) -> usize {
self.rows
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.rows == 0
}
#[must_use]
pub fn types(&self) -> Vec<LogicalType> {
self.columns.iter().map(|column| column.logical_type().clone()).collect()
}
#[must_use]
pub fn value_at(&self, row: usize, column: usize) -> Value {
match self.columns.get(column) {
Some(held) => held.value_at(row),
None => Value::Null,
}
}
pub fn row(&self, row: usize) -> impl Iterator<Item = Value> + '_ {
self.columns.iter().map(move |column| column.value_at(row))
}
pub fn select(self, selection: &Selection) -> Result<Self> {
if let Some(bad) = selection.iter().find(|&index| index >= self.rows) {
return Err(Error::internal(format!(
"a selection keeps row {bad} of a chunk that has {} rows",
self.rows
)));
}
let rows = selection.len();
let codes = selection.indices();
let mut columns = Vec::with_capacity(self.columns.len());
for column in self.columns {
columns.push(Vector::dictionary(codes.to_vec(), column)?);
}
Self::with_rows(columns, rows)
}
pub fn project(self, positions: &[usize]) -> Result<Self> {
let width = self.columns.len();
if let Some(&bad) = positions.iter().find(|&&position| position >= width) {
return Err(Error::internal(format!(
"column {bad} of a chunk that has {width} columns"
)));
}
let rows = self.rows;
let mut sources: Vec<Option<Vector>> = self.columns.into_iter().map(Some).collect();
let mut columns = Vec::with_capacity(positions.len());
for (at, &position) in positions.iter().enumerate() {
let last_use = !positions[at + 1..].contains(&position);
let taken = if last_use { sources[position].take() } else { sources[position].clone() };
match taken {
Some(column) => columns.push(column),
None => {
return Err(Error::internal(format!("column {position} was taken twice")));
}
}
}
Self::with_rows(columns, rows)
}
pub fn flatten(&self) -> Result<Self> {
let mut columns = Vec::with_capacity(self.columns.len());
for column in &self.columns {
columns.push(column.flatten()?);
}
Self::with_rows(columns, self.rows)
}
}
#[cfg(test)]
mod tests {
use rudb_common::LogicalType;
use super::*;
use crate::vector::{Data, Form};
fn integers(values: &[i32]) -> Vector {
Vector::flat(LogicalType::Integer, Data::Int32(values.to_vec()))
.expect("integers are an i32 layout")
}
#[test]
fn a_chunk_takes_its_length_from_its_columns() {
let chunk = Chunk::new(vec![integers(&[1, 2, 3]), integers(&[4, 5, 6])])
.expect("two columns of three");
assert_eq!(chunk.len(), 3);
assert_eq!(chunk.width(), 2);
assert_eq!(chunk.value_at(2, 1), Value::Integer(6));
}
#[test]
fn a_ragged_chunk_is_caught() {
let error = Chunk::new(vec![integers(&[1, 2, 3]), integers(&[4])])
.expect_err("a chunk is not ragged");
assert!(error.message().contains("column 1"), "{error}");
}
#[test]
fn a_chunk_with_no_columns_can_still_have_rows() {
let chunk = Chunk::with_rows(Vec::new(), 900).expect("no columns and nine hundred rows");
assert_eq!(chunk.len(), 900);
assert_eq!(chunk.width(), 0);
assert!(!chunk.is_empty(), "nine hundred rows is not empty");
}
#[test]
fn a_chunk_longer_than_a_vector_is_caught() {
let error = Chunk::with_rows(Vec::new(), VECTOR_SIZE + 1).expect_err("too long");
assert!(error.message().contains("longer than"), "{error}");
}
#[test]
fn an_empty_chunk_keeps_its_types() {
let chunk = Chunk::empty(&[LogicalType::Integer, LogicalType::Varchar]);
assert_eq!(chunk.len(), 0);
assert_eq!(chunk.types(), vec![LogicalType::Integer, LogicalType::Varchar]);
}
#[test]
fn selecting_keeps_the_rows_it_selected_and_no_others() {
let chunk = Chunk::new(vec![integers(&[10, 20, 30, 40]), integers(&[1, 2, 3, 4])])
.expect("four rows");
let kept = Selection::from_predicate(4, |index| index % 2 == 1);
let chunk = chunk.select(&kept).expect("rows one and three exist");
assert_eq!(chunk.len(), 2);
assert_eq!(chunk.row(0).collect::<Vec<_>>(), vec![Value::Integer(20), Value::Integer(2)]);
assert_eq!(chunk.row(1).collect::<Vec<_>>(), vec![Value::Integer(40), Value::Integer(4)]);
}
#[test]
fn selecting_leaves_the_values_where_they_were() {
let chunk = Chunk::new(vec![integers(&[10, 20, 30, 40])]).expect("four rows");
let kept = Selection::from_predicate(4, |index| index == 0);
let chunk = chunk.select(&kept).expect("row zero exists");
assert_eq!(chunk.column(0).expect("one column").form(), Form::Dictionary);
}
#[test]
fn a_selection_past_the_end_is_caught() {
let chunk = Chunk::new(vec![integers(&[1, 2])]).expect("two rows");
let mut kept = Selection::empty();
kept.push(7);
let error = chunk.select(&kept).expect_err("row seven does not exist");
assert!(error.message().contains("row 7"), "{error}");
}
#[test]
fn projecting_reorders_and_can_repeat_a_column() {
let chunk = Chunk::new(vec![integers(&[1, 2]), integers(&[3, 4])]).expect("two by two");
let chunk = chunk.project(&[1, 0, 1]).expect("both columns exist");
assert_eq!(chunk.width(), 3);
assert_eq!(
chunk.row(0).collect::<Vec<_>>(),
vec![Value::Integer(3), Value::Integer(1), Value::Integer(3)]
);
}
#[test]
fn projecting_a_column_that_is_not_there_is_caught() {
let chunk = Chunk::new(vec![integers(&[1, 2])]).expect("one column");
let error = chunk.project(&[0, 4]).expect_err("there is no column four");
assert!(error.message().contains("column 4"), "{error}");
}
#[test]
fn flattening_a_selected_chunk_gives_the_same_values() {
let chunk = Chunk::new(vec![integers(&[10, 20, 30])]).expect("three rows");
let kept = Selection::from_predicate(3, |index| index != 1);
let selected = chunk.select(&kept).expect("rows zero and two exist");
let flat = selected.flatten().expect("integers flatten");
assert_eq!(flat.column(0).expect("one column").form(), Form::Flat);
for row in 0..flat.len() {
assert_eq!(flat.value_at(row, 0), selected.value_at(row, 0), "row {row}");
}
}
}