use std::cmp::Ordering;
use crate::executor::streaming::chunk::core::DataChunk;
use linkrs_core::value::NullType;
use linkrs_core::Value;
use super::BatchColumn;
#[derive(Debug, Clone, Default)]
pub struct ColumnarBatch {
columns: Vec<BatchColumn>,
num_rows: usize,
}
impl ColumnarBatch {
pub fn new(num_columns: usize) -> Self {
Self {
columns: vec![BatchColumn::Empty; num_columns],
num_rows: 0,
}
}
pub fn num_columns(&self) -> usize {
self.columns.len()
}
pub fn num_rows(&self) -> usize {
self.num_rows
}
pub fn is_empty(&self) -> bool {
self.num_rows == 0
}
pub fn column(&self, idx: usize) -> &BatchColumn {
&self.columns[idx]
}
pub fn append_chunk(&mut self, chunk: &DataChunk) -> usize {
let indices = chunk.visible_indices();
if indices.is_empty() {
return self.num_rows;
}
let num_cols = chunk.num_columns();
if self.columns.is_empty() {
self.columns = vec![BatchColumn::Empty; num_cols];
}
if self.columns.len() < num_cols {
self.columns.resize(num_cols, BatchColumn::Empty);
}
for j in 0..num_cols {
let column = &mut self.columns[j];
if let Some(typed) = chunk.typed_column(j) {
column.append_typed(typed, &indices);
} else {
let is_typed = column.is_typed();
if is_typed {
for &i in &indices {
let value = chunk
.rows
.get(i)
.and_then(|r| r.get(j))
.cloned()
.unwrap_or_else(|| Value::Null(NullType::Null));
column.append_row_value(&value);
}
} else {
for &i in &indices {
let value = chunk
.rows
.get(i)
.and_then(|r| r.get(j))
.cloned()
.unwrap_or_else(|| Value::Null(NullType::Null));
if matches!(column, BatchColumn::Empty) {
*column = BatchColumn::Fallback(vec![value]);
} else {
column.append_row_value(&value);
}
}
}
}
}
self.num_rows += indices.len();
self.num_rows
}
pub fn append_chunk_row(&mut self, chunk: &DataChunk, idx: usize) {
let num_cols = chunk.num_columns();
if self.columns.is_empty() {
self.columns = vec![BatchColumn::Empty; num_cols];
}
if self.columns.len() < num_cols {
self.columns.resize(num_cols, BatchColumn::Empty);
}
for (j, column) in self.columns.iter_mut().enumerate().take(num_cols) {
if let Some(typed) = chunk.typed_column(j) {
column.append_typed(typed, std::slice::from_ref(&idx));
} else {
let value = chunk
.rows
.get(idx)
.and_then(|r| r.get(j))
.cloned()
.unwrap_or_else(|| Value::Null(NullType::Null));
if matches!(column, BatchColumn::Empty) {
*column = BatchColumn::Fallback(vec![value]);
} else {
column.append_row_value(&value);
}
}
}
self.num_rows += 1;
}
pub fn append_row(&mut self, row: &[Value]) {
if self.columns.is_empty() {
self.columns = vec![BatchColumn::Empty; row.len()];
}
if self.columns.len() < row.len() {
self.columns.resize(row.len(), BatchColumn::Empty);
}
for (j, column) in self.columns.iter_mut().enumerate() {
let value = row
.get(j)
.cloned()
.unwrap_or_else(|| Value::Null(NullType::Null));
column.append_row_value(&value);
}
self.num_rows += 1;
}
pub fn to_rows(&self) -> Vec<Vec<Value>> {
let mut rows = Vec::with_capacity(self.num_rows);
for i in 0..self.num_rows {
let mut row = Vec::with_capacity(self.columns.len());
for column in &self.columns {
row.push(column.value_at(i));
}
rows.push(row);
}
rows
}
pub fn compare_rows_at(&self, col: usize, a: usize, b: usize) -> Ordering {
self.columns[col].compare_at(a, b)
}
pub fn compare_value_at(&self, col: usize, v: &Value, idx: usize) -> Ordering {
self.columns[col].compare_value_at(v, idx)
}
pub fn permute(&mut self, perm: &[usize]) {
for column in &mut self.columns {
column.permute(perm);
}
}
pub fn truncate(&mut self, len: usize) {
for column in &mut self.columns {
column.truncate(len);
}
self.num_rows = len.min(self.num_rows);
}
pub fn clear(&mut self) {
for column in &mut self.columns {
*column = BatchColumn::Empty;
}
self.num_rows = 0;
}
pub fn estimated_size(&self) -> usize {
self.columns.iter().map(|c| c.estimated_size()).sum()
}
}