use std::collections::HashMap;
use super::{builder::ColumnBuilder, data::ColumnData, types::ColumnTypeClass};
use crate::Row;
#[derive(Debug, Clone)]
pub struct ColumnarTable {
columns: HashMap<String, ColumnData>,
column_names: Vec<String>,
row_count: usize,
}
impl ColumnarTable {
pub fn new() -> Self {
ColumnarTable { columns: HashMap::new(), column_names: Vec::new(), row_count: 0 }
}
pub fn from_rows(rows: &[Row], column_names: &[String]) -> Result<Self, String> {
if rows.is_empty() {
return Ok(ColumnarTable {
columns: HashMap::new(),
column_names: column_names.to_vec(),
row_count: 0,
});
}
let row_count = rows.len();
let col_count = column_names.len();
if rows[0].len() != col_count {
return Err(format!("Row 0 has {} columns, expected {}", rows[0].len(), col_count));
}
let col_types: Vec<_> = (0..col_count)
.map(|col_idx| {
rows.iter()
.filter_map(|row| row.get(col_idx))
.find(|v| !v.is_null())
.map(ColumnTypeClass::from_sql_value)
.unwrap_or(ColumnTypeClass::Null)
})
.collect();
let mut column_builders: Vec<ColumnBuilder> =
col_types.iter().map(|t| ColumnBuilder::new(*t, row_count)).collect();
for (row_idx, row) in rows.iter().enumerate() {
if row.len() != col_count {
return Err(format!(
"Row {} has {} columns, expected {}",
row_idx,
row.len(),
col_count
));
}
for (col_idx, value) in row.values.iter().enumerate() {
column_builders[col_idx].push(value)?;
}
}
let mut columns = HashMap::with_capacity(col_count);
for (col_name, builder) in column_names.iter().zip(column_builders.into_iter()) {
columns.insert(col_name.clone(), builder.build());
}
Ok(ColumnarTable { columns, column_names: column_names.to_vec(), row_count })
}
pub fn from_row_refs(rows: &[&Row], column_names: &[String]) -> Result<Self, String> {
if rows.is_empty() {
return Ok(ColumnarTable {
columns: HashMap::new(),
column_names: column_names.to_vec(),
row_count: 0,
});
}
let row_count = rows.len();
let col_count = column_names.len();
if rows[0].len() != col_count {
return Err(format!("Row 0 has {} columns, expected {}", rows[0].len(), col_count));
}
let col_types: Vec<_> = (0..col_count)
.map(|col_idx| {
rows.iter()
.filter_map(|row| row.get(col_idx))
.find(|v| !v.is_null())
.map(ColumnTypeClass::from_sql_value)
.unwrap_or(ColumnTypeClass::Null)
})
.collect();
let mut column_builders: Vec<ColumnBuilder> =
col_types.iter().map(|t| ColumnBuilder::new(*t, row_count)).collect();
for (row_idx, row) in rows.iter().enumerate() {
if row.len() != col_count {
return Err(format!(
"Row {} has {} columns, expected {}",
row_idx,
row.len(),
col_count
));
}
for (col_idx, value) in row.values.iter().enumerate() {
column_builders[col_idx].push(value)?;
}
}
let mut columns = HashMap::with_capacity(col_count);
for (col_name, builder) in column_names.iter().zip(column_builders.into_iter()) {
columns.insert(col_name.clone(), builder.build());
}
Ok(ColumnarTable { columns, column_names: column_names.to_vec(), row_count })
}
pub fn to_rows(&self) -> Vec<Row> {
let mut rows = Vec::with_capacity(self.row_count);
for row_idx in 0..self.row_count {
let mut values = Vec::with_capacity(self.column_names.len());
for col_name in &self.column_names {
if let Some(column) = self.columns.get(col_name) {
values.push(column.get(row_idx));
} else {
values.push(vibesql_types::SqlValue::Null);
}
}
rows.push(Row::new(values));
}
rows
}
pub fn row_count(&self) -> usize {
self.row_count
}
pub fn column_count(&self) -> usize {
self.column_names.len()
}
pub fn get_column(&self, name: &str) -> Option<&ColumnData> {
self.columns.get(name)
}
pub fn get_column_mut(&mut self, name: &str) -> Option<&mut ColumnData> {
self.columns.get_mut(name)
}
pub fn column_names(&self) -> &[String] {
&self.column_names
}
pub fn append_row(&mut self, row: &crate::Row) -> Result<(), String> {
if row.len() != self.column_names.len() {
return Err(format!(
"Row has {} columns, expected {}",
row.len(),
self.column_names.len()
));
}
if self.columns.is_empty() && !self.column_names.is_empty() {
let col_types: Vec<_> = row
.values
.iter()
.map(|v| ColumnTypeClass::from_sql_value(v))
.collect();
for (col_idx, col_name) in self.column_names.iter().enumerate() {
let mut builder = ColumnBuilder::new(col_types[col_idx], 1);
builder.push(&row.values[col_idx])?;
self.columns.insert(col_name.clone(), builder.build());
}
self.row_count = 1;
return Ok(());
}
for (col_idx, col_name) in self.column_names.iter().enumerate() {
let column = self.columns.get_mut(col_name).ok_or_else(|| {
format!("Column '{}' not found in columnar table", col_name)
})?;
column.push_value(&row.values[col_idx])?;
}
self.row_count += 1;
Ok(())
}
pub fn append_rows(&mut self, rows: &[crate::Row]) -> Result<(), String> {
for row in rows {
self.append_row(row)?;
}
Ok(())
}
pub fn update_row_at(&mut self, index: usize, row: &crate::Row) -> Result<(), String> {
if index >= self.row_count {
return Err(format!(
"Row index {} out of bounds (row_count: {})",
index, self.row_count
));
}
if row.len() != self.column_names.len() {
return Err(format!(
"Row has {} columns, expected {}",
row.len(),
self.column_names.len()
));
}
for (col_idx, col_name) in self.column_names.iter().enumerate() {
let column = self.columns.get_mut(col_name).ok_or_else(|| {
format!("Column '{}' not found in columnar table", col_name)
})?;
column.set_value(index, &row.values[col_idx])?;
}
Ok(())
}
pub fn remove_row_at(&mut self, index: usize) -> Result<(), String> {
if index >= self.row_count {
return Err(format!(
"Row index {} out of bounds (row_count: {})",
index, self.row_count
));
}
for col_name in self.column_names.clone() {
if let Some(column) = self.columns.get_mut(&col_name) {
column.remove_at(index);
}
}
self.row_count -= 1;
Ok(())
}
pub fn remove_rows_sorted(&mut self, indices: &[usize]) -> Result<(), String> {
for &index in indices.iter().rev() {
self.remove_row_at(index)?;
}
Ok(())
}
pub fn clear(&mut self) {
self.columns.clear();
self.row_count = 0;
}
pub fn size_in_bytes(&self) -> usize {
const VEC_OVERHEAD: usize = 3 * std::mem::size_of::<usize>();
const HASHMAP_BASE_OVERHEAD: usize = 48;
const HASHMAP_ENTRY_OVERHEAD: usize = 8;
let columns_size: usize = self.columns.values().map(|c| c.size_in_bytes()).sum();
let column_names_size: usize =
self.column_names.iter().map(|s| std::mem::size_of::<String>() + s.capacity()).sum();
let hashmap_keys_size: usize =
self.columns.keys().map(|s| std::mem::size_of::<String>() + s.capacity()).sum();
std::mem::size_of::<Self>() + columns_size
+ HASHMAP_BASE_OVERHEAD
+ self.columns.len() * HASHMAP_ENTRY_OVERHEAD
+ hashmap_keys_size
+ VEC_OVERHEAD
+ column_names_size
}
}
impl Default for ColumnarTable {
fn default() -> Self {
Self::new()
}
}