use std::cmp::Ordering;
use vibesql_ast::{Expression, NullsOrder, OrderByItem, OrderDirection};
use vibesql_storage::Row;
use vibesql_types::SqlValue;
use super::partitioning::Partition;
pub fn sort_partition<F>(partition: &mut Partition, order_by: &Option<Vec<OrderByItem>>, eval_fn: F)
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
sort_partition_with_collations(partition, order_by, &[], eval_fn);
}
pub fn sort_partition_with_collations<F>(
partition: &mut Partition,
order_by: &Option<Vec<OrderByItem>>,
collations: &[Option<String>],
eval_fn: F,
) where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
let Some(order_items) = order_by else {
return;
};
if order_items.is_empty() {
return;
}
let mut indices: Vec<usize> = (0..partition.rows.len()).collect();
let rows = &partition.rows; indices.sort_by(|&a, &b| {
for (i, order_item) in order_items.iter().enumerate() {
let val_a = eval_fn(&order_item.expr, &rows[a]).unwrap_or(SqlValue::Null);
let val_b = eval_fn(&order_item.expr, &rows[b]).unwrap_or(SqlValue::Null);
let collation = collations.get(i).and_then(|c| c.as_deref());
let a_null = matches!(val_a, SqlValue::Null);
let b_null = matches!(val_b, SqlValue::Null);
let cmp = if a_null != b_null {
if let Some(nulls_order) = order_item.nulls_order {
match (nulls_order, a_null) {
(NullsOrder::First, true) | (NullsOrder::Last, false) => Ordering::Less,
(NullsOrder::First, false) | (NullsOrder::Last, true) => Ordering::Greater,
}
} else {
let cmp = compare_values(&val_a, &val_b);
match order_item.direction {
OrderDirection::Asc => cmp,
OrderDirection::Desc => cmp.reverse(),
}
}
} else {
let cmp = compare_values_with_collation(&val_a, &val_b, collation);
match order_item.direction {
OrderDirection::Asc => cmp,
OrderDirection::Desc => cmp.reverse(),
}
};
if cmp != Ordering::Equal {
return cmp;
}
}
Ordering::Equal
});
let old_rows = std::mem::take(&mut partition.rows);
let old_indices = std::mem::take(&mut partition.original_indices);
partition.rows = indices.iter().map(|&i| old_rows[i].clone()).collect();
partition.original_indices = indices.iter().map(|&i| old_indices[i]).collect();
}
pub fn compare_values_with_collation(
a: &SqlValue,
b: &SqlValue,
collation: Option<&str>,
) -> Ordering {
if let Some(coll) = collation {
if coll.eq_ignore_ascii_case("nocase") {
let a = apply_nocase(a);
let b = apply_nocase(b);
return compare_values(&a, &b);
}
}
compare_values(a, b)
}
fn apply_nocase(val: &SqlValue) -> SqlValue {
match val {
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(),
}
}
pub fn compare_values(a: &SqlValue, b: &SqlValue) -> Ordering {
match (a, b) {
(SqlValue::Null, SqlValue::Null) => Ordering::Equal,
(SqlValue::Null, _) => Ordering::Less, (_, SqlValue::Null) => Ordering::Greater,
(SqlValue::Integer(a), SqlValue::Integer(b)) => a.cmp(b),
(SqlValue::Real(a), SqlValue::Real(b)) => {
if a.is_nan() && b.is_nan() {
Ordering::Equal
} else if a.is_nan() {
Ordering::Greater
} else if b.is_nan() {
Ordering::Less
} else {
a.partial_cmp(b).unwrap_or(Ordering::Equal)
}
}
(SqlValue::Varchar(a), SqlValue::Varchar(b)) => a.cmp(b),
(SqlValue::Character(a), SqlValue::Character(b)) => a.cmp(b),
(SqlValue::Boolean(a), SqlValue::Boolean(b)) => a.cmp(b),
(SqlValue::Integer(a), SqlValue::Real(b)) => {
(*a as f64).partial_cmp(b).unwrap_or(Ordering::Equal)
}
(SqlValue::Real(a), SqlValue::Integer(b)) => {
a.partial_cmp(&(*b as f64)).unwrap_or(Ordering::Equal)
}
(SqlValue::Numeric(a), SqlValue::Numeric(b)) => {
if a.is_nan() && b.is_nan() {
Ordering::Equal
} else if a.is_nan() {
Ordering::Greater
} else if b.is_nan() {
Ordering::Less
} else {
a.partial_cmp(b).unwrap_or(Ordering::Equal)
}
}
(SqlValue::Numeric(a), SqlValue::Integer(b)) => {
a.partial_cmp(&(*b as f64)).unwrap_or(Ordering::Equal)
}
(SqlValue::Integer(a), SqlValue::Numeric(b)) => {
(*a as f64).partial_cmp(b).unwrap_or(Ordering::Equal)
}
(SqlValue::Numeric(a), SqlValue::Real(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
(SqlValue::Real(a), SqlValue::Numeric(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
_ => format!("{:?}", a).cmp(&format!("{:?}", b)),
}
}