use std::cmp::{max, min};
use anyhow::{anyhow, bail};
use crate::func::def::{MatchTestFailure, ParameterBindingProvider};
use crate::types::decimal_type::Decimal;
use crate::types::{Type, DOUBLE, INT, UNKNOWN};
pub fn bindings_must_have_same_type(
bindings: &dyn ParameterBindingProvider,
) -> anyhow::Result<Type> {
must_be_same_type(bindings.iter().map(|binding| binding.typ()))
}
pub fn must_be_same_type<'a, I>(types: I) -> anyhow::Result<Type>
where
I: IntoIterator<Item = &'a Type>,
{
types.into_iter().try_fold(UNKNOWN, |existing, current| {
match (&existing, current) {
(left, right) if left == right => Ok(existing),
(Type::Unknown, right) => Ok(right.clone()),
(left, Type::Unknown) => Ok(left.clone()),
(Type::Struct(_), Type::Struct(_)) => Err(anyhow!(MatchTestFailure(
anyhow!("Struct types cannot be merged").into()
))),
(_, right) => existing
.clone()
.merge(right.clone())
.map_err(|e| anyhow!(MatchTestFailure::wrap(e))),
}
})
}
pub fn drop_decimal_scale(bindings: &dyn ParameterBindingProvider) -> anyhow::Result<Type> {
match bindings.get_by_index(0)?.typ() {
Type::Decimal(d) => Ok(Decimal::new(d.precision, 0)?.into()),
t => Ok(t.clone()),
}
}
pub fn pls_min_dec(dec: &Decimal) -> anyhow::Result<Decimal> {
Decimal::new(min(max(dec.precision + 1, 12), 38), dec.scale)
}
pub fn pls_min_dec_merge(left: &Decimal, right: &Decimal) -> anyhow::Result<Decimal> {
Decimal::new(
min(
38,
1 + max(left.scale, right.scale)
+ max(left.precision - left.scale, right.precision - right.scale),
),
max(left.scale, right.scale),
)
}
pub fn doubles_poison_and<C, M>(
convert: C,
merge: M,
) -> impl Fn(&dyn ParameterBindingProvider) -> anyhow::Result<Type>
where
C: Fn(&Decimal) -> anyhow::Result<Decimal>,
M: Fn(&Decimal, &Decimal) -> anyhow::Result<Decimal>,
{
move |bindings| {
let left_type = bindings.get_by_index(0)?.typ().clone();
let right_type = bindings.get_by_index(1)?.typ().clone();
match (left_type, right_type) {
(Type::Decimal(left), Type::Decimal(right)) => Ok(merge(&left, &right)?.into()),
(Type::Decimal(left), Type::Int) => Ok(convert(&left)?.into()),
(Type::Int, Type::Decimal(right)) => Ok(convert(&right)?.into()),
(Type::Double, _) | (_, Type::Double) => Ok(DOUBLE.into()),
(Type::Int, Type::Int) => Ok(INT.into()),
_ => {
bail!(MatchTestFailure(
anyhow!("arguments are not numeric").into()
));
}
}
}
}