use std::cmp::Ordering;
use vibesql_ast::Expression;
use vibesql_storage::Row;
use vibesql_types::SqlValue;
use super::{partitioning::Partition, sorting::compare_values};
use crate::evaluator::casting::string_to_number;
fn coerce_text_to_number(s: &str) -> (f64, bool) {
let trimmed = s.trim();
if !trimmed.is_empty() {
if let Ok(n) = trimmed.parse::<i64>() {
return (n as f64, false);
}
}
let (_, float_val, _) = string_to_number(s);
(float_val, true)
}
#[inline]
fn passes_filter<F>(filter: Option<&Expression>, row: &Row, eval_fn: &F) -> bool
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
{
if let Some(filter_expr) = filter {
if let Ok(filter_result) = eval_fn(filter_expr, row) {
is_truthy_value(&filter_result)
} else {
false }
} else {
true }
}
fn is_truthy_value(value: &SqlValue) -> bool {
match value {
SqlValue::Boolean(b) => *b,
SqlValue::Null => false,
SqlValue::Integer(n) => *n != 0,
SqlValue::Smallint(n) => *n != 0,
SqlValue::Bigint(n) => *n != 0,
SqlValue::Unsigned(n) => *n != 0,
SqlValue::Float(f) => *f != 0.0,
SqlValue::Real(f) => *f != 0.0,
SqlValue::Double(f) => *f != 0.0,
SqlValue::Numeric(f) => *f != 0.0,
SqlValue::Varchar(s) | SqlValue::Character(s) => {
s.parse::<f64>().map_or(false, |n| n != 0.0)
}
_ => false,
}
}
pub fn evaluate_count_window<F, I>(
partition: &Partition,
frame_indices: I,
arg_expr: Option<&Expression>,
filter: Option<&Expression>,
eval_fn: F,
) -> SqlValue
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
I: IntoIterator<Item = usize>,
{
let mut count = 0i64;
for idx in frame_indices {
if idx >= partition.len() {
continue;
}
let row = &partition.rows[idx];
if !passes_filter(filter, row, &eval_fn) {
continue;
}
if arg_expr.is_none() {
count += 1;
continue;
}
if let Some(expr) = arg_expr {
if let Ok(val) = eval_fn(expr, row) {
if !matches!(val, SqlValue::Null) {
count += 1;
}
}
}
}
SqlValue::Integer(count)
}
pub fn evaluate_sum_window<F, I>(
partition: &Partition,
frame_indices: I,
arg_expr: &Expression,
filter: Option<&Expression>,
eval_fn: F,
) -> SqlValue
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
I: IntoIterator<Item = usize>,
{
let mut sum = 0.0f64;
let mut has_value = false;
let mut saw_real = false;
for idx in frame_indices {
if idx >= partition.len() {
continue;
}
let row = &partition.rows[idx];
if !passes_filter(filter, row, &eval_fn) {
continue;
}
if let Ok(val) = eval_fn(arg_expr, row) {
match val {
SqlValue::Integer(n) => {
sum += n as f64;
has_value = true;
}
SqlValue::Smallint(n) => {
sum += n as f64;
has_value = true;
}
SqlValue::Bigint(n) => {
sum += n as f64;
has_value = true;
}
SqlValue::Numeric(n) => {
sum += n;
has_value = true;
saw_real = true;
}
SqlValue::Float(n) => {
sum += n as f64;
has_value = true;
saw_real = true;
}
SqlValue::Real(n) => {
sum += n as f64;
has_value = true;
saw_real = true;
}
SqlValue::Double(n) => {
sum += n;
has_value = true;
saw_real = true;
}
SqlValue::Varchar(ref s) | SqlValue::Character(ref s) => {
let (v, is_real) = coerce_text_to_number(s);
sum += v;
has_value = true;
if is_real {
saw_real = true;
}
}
SqlValue::Blob(ref bytes) => {
let text = std::str::from_utf8(bytes).unwrap_or("");
let (v, _) = coerce_text_to_number(text);
sum += v;
has_value = true;
saw_real = true;
}
SqlValue::Null => {} _ => {} }
}
}
if has_value {
if saw_real {
SqlValue::Numeric(sum)
} else if sum.fract() == 0.0 && sum >= i64::MIN as f64 && sum <= i64::MAX as f64 {
SqlValue::Integer(sum as i64)
} else {
SqlValue::Numeric(sum)
}
} else {
SqlValue::Null
}
}
pub fn evaluate_total_window<F, I>(
partition: &Partition,
frame_indices: I,
arg_expr: &Expression,
filter: Option<&Expression>,
eval_fn: F,
) -> SqlValue
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
I: IntoIterator<Item = usize>,
{
let mut sum = 0.0f64;
for idx in frame_indices {
if idx >= partition.len() {
continue;
}
let row = &partition.rows[idx];
if !passes_filter(filter, row, &eval_fn) {
continue;
}
if let Ok(val) = eval_fn(arg_expr, row) {
match val {
SqlValue::Integer(n) => {
sum += n as f64;
}
SqlValue::Smallint(n) => {
sum += n as f64;
}
SqlValue::Bigint(n) => {
sum += n as f64;
}
SqlValue::Numeric(n) => {
sum += n;
}
SqlValue::Float(n) => {
sum += n as f64;
}
SqlValue::Real(n) => {
sum += n as f64;
}
SqlValue::Double(n) => {
sum += n;
}
SqlValue::Varchar(ref s) | SqlValue::Character(ref s) => {
sum += coerce_text_to_number(s).0;
}
SqlValue::Blob(ref bytes) => {
let text = std::str::from_utf8(bytes).unwrap_or("");
sum += coerce_text_to_number(text).0;
}
SqlValue::Null => {} _ => {} }
}
}
SqlValue::Numeric(sum)
}
pub fn evaluate_avg_window<F, I>(
partition: &Partition,
frame_indices: I,
arg_expr: &Expression,
filter: Option<&Expression>,
eval_fn: F,
) -> SqlValue
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
I: IntoIterator<Item = usize>,
{
let mut sum = 0.0f64;
let mut count = 0i64;
for idx in frame_indices {
if idx >= partition.len() {
continue;
}
let row = &partition.rows[idx];
if !passes_filter(filter, row, &eval_fn) {
continue;
}
if let Ok(val) = eval_fn(arg_expr, row) {
match val {
SqlValue::Integer(n) => {
sum += n as f64;
count += 1;
}
SqlValue::Smallint(n) => {
sum += n as f64;
count += 1;
}
SqlValue::Bigint(n) => {
sum += n as f64;
count += 1;
}
SqlValue::Numeric(n) => {
sum += n;
count += 1;
}
SqlValue::Float(n) => {
sum += n as f64;
count += 1;
}
SqlValue::Real(n) => {
sum += n as f64;
count += 1;
}
SqlValue::Double(n) => {
sum += n;
count += 1;
}
SqlValue::Varchar(ref s) | SqlValue::Character(ref s) => {
sum += coerce_text_to_number(s).0;
count += 1;
}
SqlValue::Blob(ref bytes) => {
let text = std::str::from_utf8(bytes).unwrap_or("");
sum += coerce_text_to_number(text).0;
count += 1;
}
SqlValue::Null => {} _ => {} }
}
}
if count > 0 {
SqlValue::Numeric(sum / count as f64)
} else {
SqlValue::Null
}
}
pub fn evaluate_min_window<F, I>(
partition: &Partition,
frame_indices: I,
arg_expr: &Expression,
filter: Option<&Expression>,
eval_fn: F,
) -> SqlValue
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
I: IntoIterator<Item = usize>,
{
let mut min_val: Option<SqlValue> = None;
for idx in frame_indices {
if idx >= partition.len() {
continue;
}
let row = &partition.rows[idx];
if !passes_filter(filter, row, &eval_fn) {
continue;
}
if let Ok(val) = eval_fn(arg_expr, row) {
if matches!(val, SqlValue::Null) {
continue; }
if let Some(ref current_min) = min_val {
if compare_values(&val, current_min) == Ordering::Less {
min_val = Some(val);
}
} else {
min_val = Some(val);
}
}
}
min_val.unwrap_or(SqlValue::Null)
}
pub fn evaluate_max_window<F, I>(
partition: &Partition,
frame_indices: I,
arg_expr: &Expression,
filter: Option<&Expression>,
eval_fn: F,
) -> SqlValue
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
I: IntoIterator<Item = usize>,
{
let mut max_val: Option<SqlValue> = None;
for idx in frame_indices {
if idx >= partition.len() {
continue;
}
let row = &partition.rows[idx];
if !passes_filter(filter, row, &eval_fn) {
continue;
}
if let Ok(val) = eval_fn(arg_expr, row) {
if matches!(val, SqlValue::Null) {
continue; }
if let Some(ref current_max) = max_val {
if compare_values(&val, current_max) == Ordering::Greater {
max_val = Some(val);
}
} else {
max_val = Some(val);
}
}
}
max_val.unwrap_or(SqlValue::Null)
}
pub fn evaluate_group_concat_window<F, I>(
partition: &Partition,
frame_indices: I,
arg_expr: &Expression,
separator: &str,
filter: Option<&Expression>,
eval_fn: F,
) -> SqlValue
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
I: IntoIterator<Item = usize>,
{
evaluate_group_concat_window_with_expr(
partition,
frame_indices,
arg_expr,
None,
separator,
filter,
eval_fn,
)
}
pub fn evaluate_group_concat_window_with_expr<F, I>(
partition: &Partition,
frame_indices: I,
arg_expr: &Expression,
separator_expr: Option<&Expression>,
default_separator: &str,
filter: Option<&Expression>,
eval_fn: F,
) -> SqlValue
where
F: Fn(&Expression, &Row) -> Result<SqlValue, String>,
I: IntoIterator<Item = usize>,
{
let mut entries: Vec<(String, String)> = Vec::new();
for idx in frame_indices {
if idx >= partition.len() {
continue;
}
let row = &partition.rows[idx];
if !passes_filter(filter, row, &eval_fn) {
continue;
}
if let Ok(val) = eval_fn(arg_expr, row) {
let val_str = match val {
SqlValue::Null => continue, SqlValue::Varchar(s) | SqlValue::Character(s) => s.to_string(),
SqlValue::Integer(n) => n.to_string(),
SqlValue::Bigint(n) => n.to_string(),
SqlValue::Smallint(n) => n.to_string(),
SqlValue::Numeric(n) => {
if n.fract() == 0.0 {
(n as i64).to_string()
} else {
n.to_string()
}
}
SqlValue::Float(n) => {
if n.fract() == 0.0 {
(n as i64).to_string()
} else {
n.to_string()
}
}
SqlValue::Real(n) => {
if n.fract() == 0.0 {
(n as i64).to_string()
} else {
n.to_string()
}
}
SqlValue::Double(n) => {
if n.fract() == 0.0 {
(n as i64).to_string()
} else {
n.to_string()
}
}
other => format!("{}", other),
};
let sep = if let Some(sep_expr) = separator_expr {
if let Ok(sep_val) = eval_fn(sep_expr, row) {
match sep_val {
SqlValue::Varchar(s) | SqlValue::Character(s) => s.to_string(),
SqlValue::Null => default_separator.to_string(),
other => format!("{}", other),
}
} else {
default_separator.to_string()
}
} else {
default_separator.to_string()
};
entries.push((val_str, sep));
}
}
if entries.is_empty() {
SqlValue::Null
} else {
let mut result = String::new();
for (i, (val, sep)) in entries.iter().enumerate() {
if i > 0 {
result.push_str(sep);
}
result.push_str(val);
}
SqlValue::Varchar(result.into())
}
}