use vibesql_ast::Expression;
use vibesql_storage::Row;
use vibesql_types::SqlValue;
#[derive(Debug, Clone)]
pub struct Partition {
pub rows: Vec<Row>,
pub original_indices: Vec<usize>,
pub column_map: std::collections::HashMap<String, usize>,
}
impl Partition {
pub fn new(rows: Vec<Row>) -> Self {
let original_indices = (0..rows.len()).collect();
Self { rows, original_indices, column_map: std::collections::HashMap::new() }
}
pub fn with_indices(rows: Vec<Row>, original_indices: Vec<usize>) -> Self {
Self { rows, original_indices, column_map: std::collections::HashMap::new() }
}
pub fn with_column_map(
rows: Vec<Row>,
original_indices: Vec<usize>,
column_map: std::collections::HashMap<String, usize>,
) -> Self {
Self { rows, original_indices, column_map }
}
pub fn len(&self) -> usize {
self.rows.len()
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
pub fn get_column_index(&self, name: &str) -> Option<usize> {
if let Some(&idx) = self.column_map.get(name) {
return Some(idx);
}
let lower = name.to_lowercase();
self.column_map.get(&lower).copied()
}
}
pub fn partition_rows<F>(
rows: Vec<Row>,
partition_by: &Option<Vec<Expression>>,
eval_fn: F,
) -> Result<Vec<Partition>, String>
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
partition_rows_with_collations(rows, partition_by, &[], eval_fn)
}
pub fn partition_rows_with_collations<F>(
rows: Vec<Row>,
partition_by: &Option<Vec<Expression>>,
collations: &[Option<String>],
eval_fn: F,
) -> Result<Vec<Partition>, String>
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
let Some(partition_exprs) = partition_by else {
return Ok(vec![Partition::new(rows)]);
};
if partition_exprs.is_empty() {
return Ok(vec![Partition::new(rows)]);
}
let mut partitions_map: std::collections::BTreeMap<Vec<SqlValue>, Vec<(usize, Row)>> =
std::collections::BTreeMap::new();
for (original_idx, row) in rows.into_iter().enumerate() {
let mut partition_key = Vec::new();
for (i, expr) in partition_exprs.iter().enumerate() {
let value = eval_fn(expr, &row)?;
let collation = collations.get(i).and_then(|c| c.as_deref());
if let Some(coll) = collation {
if coll.eq_ignore_ascii_case("nocase") {
let normalized = match &value {
SqlValue::Varchar(s) => {
SqlValue::Varchar(arcstr::ArcStr::from(s.to_uppercase()))
}
SqlValue::Character(s) => {
SqlValue::Character(arcstr::ArcStr::from(s.to_uppercase()))
}
other => other.clone(),
};
partition_key.push(normalized);
continue;
}
}
partition_key.push(value);
}
partitions_map.entry(partition_key).or_default().push((original_idx, row));
}
Ok(partitions_map
.into_values()
.map(|rows_with_indices| {
let (indices, rows): (Vec<_>, Vec<_>) = rows_with_indices.into_iter().unzip();
Partition::with_indices(rows, indices)
})
.collect())
}