use std::cmp::Ordering;
use vibesql_ast::{Expression, OrderByItem};
use vibesql_storage::Row;
use vibesql_types::SqlValue;
use super::{partitioning::Partition, sorting::{compare_values, compare_values_with_collation}};
fn order_values_differ<F>(
order_items: &[OrderByItem],
row_a: &Row,
row_b: &Row,
collations: &[Option<String>],
eval_fn: &F,
) -> bool
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
for (i, order_item) in order_items.iter().enumerate() {
let val_a = eval_fn(&order_item.expr, row_a).unwrap_or(SqlValue::Null);
let val_b = eval_fn(&order_item.expr, row_b).unwrap_or(SqlValue::Null);
let collation = collations.get(i).and_then(|c| c.as_deref());
if compare_values_with_collation(&val_a, &val_b, collation) != Ordering::Equal {
return true;
}
}
false
}
pub fn evaluate_row_number(partition: &Partition) -> Vec<SqlValue> {
(1..=partition.len()).map(|n| SqlValue::Integer(n as i64)).collect()
}
pub fn evaluate_rank<F>(
partition: &Partition,
order_by: &Option<Vec<OrderByItem>>,
eval_fn: F,
) -> Vec<SqlValue>
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
evaluate_rank_with_collations(partition, order_by, &[], eval_fn)
}
pub fn evaluate_rank_with_collations<F>(
partition: &Partition,
order_by: &Option<Vec<OrderByItem>>,
collations: &[Option<String>],
eval_fn: F,
) -> Vec<SqlValue>
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
if order_by.is_none() || order_by.as_ref().unwrap().is_empty() {
return vec![SqlValue::Integer(1); partition.len()];
}
let order_items = order_by.as_ref().unwrap();
let mut ranks = Vec::new();
let mut current_rank = 1i64;
for (idx, row) in partition.rows.iter().enumerate() {
if idx > 0 {
let prev_row = &partition.rows[idx - 1];
if order_values_differ(order_items, row, prev_row, collations, &eval_fn) {
current_rank = (idx + 1) as i64;
}
}
ranks.push(SqlValue::Integer(current_rank));
}
ranks
}
pub fn evaluate_dense_rank<F>(
partition: &Partition,
order_by: &Option<Vec<OrderByItem>>,
eval_fn: F,
) -> Vec<SqlValue>
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
evaluate_dense_rank_with_collations(partition, order_by, &[], eval_fn)
}
pub fn evaluate_dense_rank_with_collations<F>(
partition: &Partition,
order_by: &Option<Vec<OrderByItem>>,
collations: &[Option<String>],
eval_fn: F,
) -> Vec<SqlValue>
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
if order_by.is_none() || order_by.as_ref().unwrap().is_empty() {
return vec![SqlValue::Integer(1); partition.len()];
}
let order_items = order_by.as_ref().unwrap();
let mut ranks = Vec::new();
let mut current_rank = 1i64;
for (idx, row) in partition.rows.iter().enumerate() {
if idx > 0 {
let prev_row = &partition.rows[idx - 1];
if order_values_differ(order_items, row, prev_row, collations, &eval_fn) {
current_rank += 1;
}
}
ranks.push(SqlValue::Integer(current_rank));
}
ranks
}
pub fn evaluate_percent_rank<F>(
partition: &Partition,
order_by: &Option<Vec<OrderByItem>>,
eval_fn: F,
) -> Vec<SqlValue>
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
let n = partition.len();
if n <= 1 {
return vec![SqlValue::Numeric(0.0); n];
}
let ranks = evaluate_rank(partition, order_by, eval_fn);
let denominator = (n - 1) as f64;
ranks
.into_iter()
.map(|rank| {
if let SqlValue::Integer(r) = rank {
SqlValue::Numeric((r - 1) as f64 / denominator)
} else {
SqlValue::Numeric(0.0)
}
})
.collect()
}
pub fn evaluate_cume_dist<F>(
partition: &Partition,
order_by: &Option<Vec<OrderByItem>>,
eval_fn: F,
) -> Vec<SqlValue>
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
evaluate_cume_dist_with_collations(partition, order_by, &[], eval_fn)
}
pub fn evaluate_cume_dist_with_collations<F>(
partition: &Partition,
order_by: &Option<Vec<OrderByItem>>,
collations: &[Option<String>],
eval_fn: F,
) -> Vec<SqlValue>
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
let n = partition.len();
if n == 0 {
return vec![];
}
if order_by.is_none() || order_by.as_ref().unwrap().is_empty() {
return vec![SqlValue::Numeric(1.0); n];
}
let order_items = order_by.as_ref().unwrap();
let mut results = Vec::with_capacity(n);
let mut group_end_indices = Vec::new();
let mut current_group_start = 0;
for idx in 0..n {
let is_last_row = idx == n - 1;
let is_new_group = if is_last_row {
true } else {
let curr_row = &partition.rows[idx];
let next_row = &partition.rows[idx + 1];
order_values_differ(order_items, curr_row, next_row, collations, &eval_fn)
};
if is_new_group {
let group_end = idx + 1;
for _ in current_group_start..=idx {
group_end_indices.push(group_end);
}
current_group_start = idx + 1;
}
}
let denominator = n as f64;
for group_end in group_end_indices {
results.push(SqlValue::Numeric(group_end as f64 / denominator));
}
results
}
pub fn evaluate_ntile(partition: &Partition, n: i64) -> Result<Vec<SqlValue>, String> {
if n <= 0 {
return Err("argument of ntile must be a positive integer".to_string());
}
let total_rows = partition.len();
let n_buckets = n as usize;
if n_buckets >= total_rows {
return Ok((1..=total_rows).map(|i| SqlValue::Integer(i as i64)).collect());
}
let base_size = total_rows / n_buckets;
let remainder = total_rows % n_buckets;
let mut bucket_numbers = Vec::with_capacity(total_rows);
let mut current_bucket = 1i64;
let mut rows_in_current_bucket = 0;
for _ in 0..total_rows {
bucket_numbers.push(SqlValue::Integer(current_bucket));
rows_in_current_bucket += 1;
let bucket_size =
if (current_bucket as usize) <= remainder { base_size + 1 } else { base_size };
if rows_in_current_bucket >= bucket_size {
current_bucket += 1;
rows_in_current_bucket = 0;
}
}
Ok(bucket_numbers)
}