use std::{cmp::Ordering, collections::{HashMap, HashSet}};
use crate::errors::ExecutorError;
fn canonicalize_numeric(val: &vibesql_types::SqlValue) -> vibesql_types::SqlValue {
use vibesql_types::SqlValue;
fn float_to_canonical(f: f64) -> SqlValue {
if f.is_finite() && f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
SqlValue::Bigint(f as i64)
} else {
SqlValue::Numeric(f)
}
}
match val {
SqlValue::Integer(n) => SqlValue::Bigint(*n),
SqlValue::Smallint(n) => SqlValue::Bigint(*n as i64),
SqlValue::Bigint(n) => SqlValue::Bigint(*n),
SqlValue::Unsigned(n) => SqlValue::Bigint(*n as i64),
SqlValue::Real(f) => float_to_canonical(*f as f64),
SqlValue::Float(f) => float_to_canonical(*f as f64),
SqlValue::Double(f) => float_to_canonical(*f),
SqlValue::Numeric(f) => float_to_canonical(*f),
_ => val.clone(),
}
}
fn normalize_row_for_comparison(
values: &[vibesql_types::SqlValue],
collations: &[Option<String>],
) -> Vec<vibesql_types::SqlValue> {
use arcstr::ArcStr;
use vibesql_types::SqlValue;
values
.iter()
.enumerate()
.map(|(i, val)| {
let collation = collations.get(i).and_then(|c| c.as_ref());
match collation.map(|s| s.to_uppercase()).as_deref() {
Some("NOCASE") => {
match val {
SqlValue::Varchar(s) => SqlValue::Varchar(ArcStr::from(s.to_uppercase())),
SqlValue::Character(s) => {
SqlValue::Character(ArcStr::from(s.to_uppercase()))
}
_ => canonicalize_numeric(val),
}
}
Some("RTRIM") => {
match val {
SqlValue::Varchar(s) => {
SqlValue::Varchar(ArcStr::from(s.trim_end().to_string()))
}
SqlValue::Character(s) => {
SqlValue::Character(ArcStr::from(s.trim_end().to_string()))
}
_ => canonicalize_numeric(val),
}
}
_ => canonicalize_numeric(val),
}
})
.collect()
}
fn compare_normalized_rows(
a: &[vibesql_types::SqlValue],
b: &[vibesql_types::SqlValue],
) -> Ordering {
let len = a.len().min(b.len());
for i in 0..len {
let ord = a[i].cmp(&b[i]);
if ord != Ordering::Equal {
return ord;
}
}
a.len().cmp(&b.len())
}
pub(super) fn apply_set_operation(
left: Vec<vibesql_storage::Row>,
right: Vec<vibesql_storage::Row>,
set_op: &vibesql_ast::SetOperation,
collations: &[Option<String>],
) -> Result<Vec<vibesql_storage::Row>, ExecutorError> {
if !left.is_empty() && !right.is_empty() {
let left_cols = left[0].values.len();
let right_cols = right[0].values.len();
if left_cols != right_cols {
let operator = match (&set_op.op, set_op.all) {
(vibesql_ast::SetOperator::Union, true) => "UNION ALL",
(vibesql_ast::SetOperator::Union, false) => "UNION",
(vibesql_ast::SetOperator::Intersect, true) => "INTERSECT ALL",
(vibesql_ast::SetOperator::Intersect, false) => "INTERSECT",
(vibesql_ast::SetOperator::Except, true) => "EXCEPT ALL",
(vibesql_ast::SetOperator::Except, false) => "EXCEPT",
};
return Err(ExecutorError::SetOperationColumnMismatch {
operator: operator.to_string(),
});
}
}
match set_op.op {
vibesql_ast::SetOperator::Union => {
if set_op.all {
let mut result = left;
result.extend(right);
Ok(result)
} else {
let mut combined = left;
combined.extend(right);
let mut index_by_key: HashMap<Vec<vibesql_types::SqlValue>, usize> =
HashMap::new();
let mut result: Vec<vibesql_storage::Row> = Vec::with_capacity(combined.len());
for row in combined {
let key = normalize_row_for_comparison(&row.values, collations);
if let Some(&idx) = index_by_key.get(&key) {
result[idx] = row;
} else {
index_by_key.insert(key, result.len());
result.push(row);
}
}
result.sort_by(|a, b| {
let ka = normalize_row_for_comparison(&a.values, collations);
let kb = normalize_row_for_comparison(&b.values, collations);
compare_normalized_rows(&ka, &kb)
});
Ok(result)
}
}
vibesql_ast::SetOperator::Intersect => {
if set_op.all {
let mut right_counts = HashMap::new();
for row in &right {
let key = normalize_row_for_comparison(&row.values, collations);
*right_counts.entry(key).or_insert(0) += 1;
}
let mut result = Vec::new();
for row in left {
let key = normalize_row_for_comparison(&row.values, collations);
if let Some(count) = right_counts.get_mut(&key) {
if *count > 0 {
result.push(row);
*count -= 1;
}
}
}
Ok(result)
} else {
let right_set: HashSet<_> = right
.iter()
.map(|row| normalize_row_for_comparison(&row.values, collations))
.collect();
let mut result = Vec::new();
let mut seen = HashSet::new();
for row in left {
let key = normalize_row_for_comparison(&row.values, collations);
if right_set.contains(&key) && seen.insert(key) {
result.push(row);
}
}
Ok(result)
}
}
vibesql_ast::SetOperator::Except => {
if set_op.all {
let mut right_counts = HashMap::new();
for row in &right {
let key = normalize_row_for_comparison(&row.values, collations);
*right_counts.entry(key).or_insert(0) += 1;
}
let mut result = Vec::new();
for row in left {
let key = normalize_row_for_comparison(&row.values, collations);
match right_counts.get_mut(&key) {
None => {
result.push(row);
}
Some(count) if *count == 0 => {
result.push(row);
}
Some(count) => {
*count -= 1;
}
}
}
Ok(result)
} else {
let right_set: HashSet<_> = right
.iter()
.map(|row| normalize_row_for_comparison(&row.values, collations))
.collect();
let mut result = Vec::new();
let mut seen = HashSet::new();
for row in left {
let key = normalize_row_for_comparison(&row.values, collations);
if !right_set.contains(&key) && seen.insert(key) {
result.push(row);
}
}
Ok(result)
}
}
}
}