use super::*;
use uqa_execution::{Batch, ExecError, ExecResult, PhysicalOperator, RowSchema};
use uqa_planner::UnifiedPlan;
struct CloseOperator {
schema: RowSchema,
close_error: Option<&'static str>,
}
impl PhysicalOperator for CloseOperator {
fn row_schema(&self) -> &RowSchema {
&self.schema
}
fn open(&mut self) -> ExecResult<()> {
unreachable!("the cleanup helper must not open the operator")
}
fn next(&mut self) -> ExecResult<Option<Batch>> {
unreachable!("the cleanup helper must not pull the operator")
}
fn close(&mut self) -> ExecResult<()> {
match self.close_error {
Some(message) => Err(ExecError::Other(message.into())),
None => Ok(()),
}
}
}
#[test]
fn physical_failure_preserves_the_original_error_when_close_succeeds() {
let mut operator = CloseOperator {
schema: RowSchema::new(Vec::new()),
close_error: None,
};
let original = SQLError::TypeMismatch("primary".into());
let original_message = original.to_string();
let error = close_after_physical_failure(&mut operator, ExecError::SQL(original), "execution");
assert_eq!(error.to_string(), original_message);
assert!(matches!(error, SQLError::TypeMismatch(_)));
}
#[test]
fn physical_failure_reports_both_execution_and_close_errors() {
let mut operator = CloseOperator {
schema: RowSchema::new(Vec::new()),
close_error: Some("cleanup"),
};
let error = close_after_physical_failure(
&mut operator,
ExecError::Other("primary".into()),
"spill buffering",
);
let message = error.to_string();
assert!(message.contains("primary"));
assert!(message.contains("spill buffering"));
assert!(message.contains("cleanup"));
}
#[test]
fn floating_limit_uses_postgresql_bigint_rounding_and_range_checks() {
assert_eq!(float_limit_offset(42.0, "LIMIT").unwrap(), 42);
assert_eq!(float_limit_offset(1.5, "LIMIT").unwrap(), 2);
assert_eq!(float_limit_offset(2.5, "LIMIT").unwrap(), 2);
assert_eq!(float_limit_offset(-0.5, "LIMIT").unwrap(), 0);
for value in [f64::NAN, f64::INFINITY, -1.0, 9_223_372_036_854_775_808.0] {
assert!(float_limit_offset(value, "LIMIT").is_err(), "{value}");
}
}
#[test]
fn scalar_subquery_scope_restores_the_parent_arena_after_unwind() {
let statement = uqa_sql::compile("SELECT 1")
.unwrap()
.into_iter()
.next()
.unwrap();
let UnifiedPlan::Query(query) = UnifiedPlan::lower(statement) else {
panic!("SELECT must lower to a query plan");
};
let query = *query;
let mut scope = CteScope::new();
scope.scalar_subqueries.push(query.clone());
let unwind = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _guard = scope.enter_scalar_subqueries(&[query.clone(), query]);
panic!("exercise scalar-subquery scope cleanup");
}));
assert!(unwind.is_err());
assert_eq!(scope.scalar_subqueries.len(), 1);
}