#[cfg(test)]
mod tests;
use crate::{error::InternalError, types::Decimal, value::Value};
use std::cmp::Ordering;
use thiserror::Error as ThisError;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum NumericArithmeticOp {
Add,
Sub,
Mul,
Div,
Rem,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ThisError)]
pub(crate) enum NumericEvalError {
#[error("numeric overflow")]
Overflow,
#[error("numeric result is not representable")]
NotRepresentable,
}
impl NumericEvalError {
pub(in crate::db) fn into_internal_error(self) -> InternalError {
match self {
Self::Overflow => InternalError::query_numeric_overflow(),
Self::NotRepresentable => InternalError::query_numeric_not_representable(),
}
}
}
pub(in crate::db) fn apply_decimal_arithmetic_checked(
op: NumericArithmeticOp,
left: Decimal,
right: Decimal,
) -> Result<Decimal, NumericEvalError> {
match op {
NumericArithmeticOp::Add => left.checked_add(right).ok_or(NumericEvalError::Overflow),
NumericArithmeticOp::Sub => left.checked_sub(right).ok_or(NumericEvalError::Overflow),
NumericArithmeticOp::Mul => left.checked_mul(right).ok_or(NumericEvalError::Overflow),
NumericArithmeticOp::Div => {
if right.is_zero() {
return Err(NumericEvalError::NotRepresentable);
}
left.checked_div(right).ok_or(NumericEvalError::Overflow)
}
NumericArithmeticOp::Rem => {
if right.is_zero() {
return Err(NumericEvalError::NotRepresentable);
}
left.checked_rem(right).ok_or(NumericEvalError::Overflow)
}
}
}
pub(in crate::db) fn add_decimal_terms_checked(
left: Decimal,
right: Decimal,
) -> Result<Decimal, NumericEvalError> {
apply_decimal_arithmetic_checked(NumericArithmeticOp::Add, left, right)
}
pub(in crate::db) fn divide_decimal_terms_checked(
left: Decimal,
right: Decimal,
) -> Result<Decimal, NumericEvalError> {
apply_decimal_arithmetic_checked(NumericArithmeticOp::Div, left, right)
}
pub(in crate::db) fn average_decimal_terms_checked(
sum: Decimal,
count: u64,
) -> Result<Decimal, NumericEvalError> {
let divisor = Decimal::from_num(count).ok_or(NumericEvalError::NotRepresentable)?;
divide_decimal_terms_checked(sum, divisor)
}
#[must_use]
pub(in crate::db) fn coerce_numeric_decimal(value: &Value) -> Option<Decimal> {
if !value.supports_numeric_coercion() {
return None;
}
value.to_numeric_decimal()
}
#[must_use]
pub(in crate::db) fn decimal_sign(decimal: Decimal) -> Decimal {
let sign = match decimal.cmp(&Decimal::ZERO) {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
};
Decimal::from_i64(sign).expect("small sign values must fit decimal")
}
pub(in crate::db) fn decimal_sqrt_checked(decimal: Decimal) -> Result<Decimal, NumericEvalError> {
if decimal.is_sign_negative() {
return Err(NumericEvalError::NotRepresentable);
}
Decimal::from_f64_lossy(
decimal
.to_f64()
.ok_or(NumericEvalError::NotRepresentable)?
.sqrt(),
)
.ok_or(NumericEvalError::NotRepresentable)
}
pub(in crate::db) fn decimal_cbrt_checked(decimal: Decimal) -> Result<Decimal, NumericEvalError> {
Decimal::from_f64_lossy(
decimal
.to_f64()
.ok_or(NumericEvalError::NotRepresentable)?
.cbrt(),
)
.ok_or(NumericEvalError::NotRepresentable)
}
pub(in crate::db) fn decimal_exp_checked(decimal: Decimal) -> Result<Decimal, NumericEvalError> {
Decimal::from_f64_lossy(
decimal
.to_f64()
.ok_or(NumericEvalError::NotRepresentable)?
.exp(),
)
.ok_or(NumericEvalError::NotRepresentable)
}
pub(in crate::db) fn decimal_ln_checked(decimal: Decimal) -> Result<Decimal, NumericEvalError> {
if decimal <= Decimal::ZERO {
return Err(NumericEvalError::NotRepresentable);
}
Decimal::from_f64_lossy(
decimal
.to_f64()
.ok_or(NumericEvalError::NotRepresentable)?
.ln(),
)
.ok_or(NumericEvalError::NotRepresentable)
}
pub(in crate::db) fn decimal_log2_checked(decimal: Decimal) -> Result<Decimal, NumericEvalError> {
if decimal <= Decimal::ZERO {
return Err(NumericEvalError::NotRepresentable);
}
Decimal::from_f64_lossy(
decimal
.to_f64()
.ok_or(NumericEvalError::NotRepresentable)?
.log2(),
)
.ok_or(NumericEvalError::NotRepresentable)
}
pub(in crate::db) fn decimal_log10_checked(decimal: Decimal) -> Result<Decimal, NumericEvalError> {
if decimal <= Decimal::ZERO {
return Err(NumericEvalError::NotRepresentable);
}
Decimal::from_f64_lossy(
decimal
.to_f64()
.ok_or(NumericEvalError::NotRepresentable)?
.log10(),
)
.ok_or(NumericEvalError::NotRepresentable)
}
pub(in crate::db) fn decimal_log_base_checked(
base: Decimal,
value: Decimal,
) -> Result<Decimal, NumericEvalError> {
if base <= Decimal::ZERO || base == Decimal::from_i64(1).expect("one fits decimal") {
return Err(NumericEvalError::NotRepresentable);
}
if value <= Decimal::ZERO {
return Err(NumericEvalError::NotRepresentable);
}
Decimal::from_f64_lossy(
value
.to_f64()
.ok_or(NumericEvalError::NotRepresentable)?
.log(base.to_f64().ok_or(NumericEvalError::NotRepresentable)?),
)
.ok_or(NumericEvalError::NotRepresentable)
}
pub(in crate::db) fn decimal_power_checked(
base: Decimal,
exponent: Decimal,
) -> Result<Decimal, NumericEvalError> {
if let Some(power) = exponent.to_u64() {
return base.checked_powu(power).ok_or(NumericEvalError::Overflow);
}
Decimal::from_f64_lossy(
base.to_f64()
.ok_or(NumericEvalError::NotRepresentable)?
.powf(
exponent
.to_f64()
.ok_or(NumericEvalError::NotRepresentable)?,
),
)
.ok_or(NumericEvalError::NotRepresentable)
}
pub(in crate::db) fn apply_numeric_arithmetic_checked(
op: NumericArithmeticOp,
left: &Value,
right: &Value,
) -> Result<Option<Decimal>, NumericEvalError> {
let Some(left) = coerce_numeric_decimal(left) else {
return Ok(None);
};
let Some(right) = coerce_numeric_decimal(right) else {
return Ok(None);
};
apply_decimal_arithmetic_checked(op, left, right).map(Some)
}
#[must_use]
pub(in crate::db) fn compare_numeric_order(left: &Value, right: &Value) -> Option<Ordering> {
let left = coerce_numeric_decimal(left)?;
let right = coerce_numeric_decimal(right)?;
left.partial_cmp(&right)
}
#[must_use]
pub(in crate::db) fn compare_numeric_or_strict_order(
left: &Value,
right: &Value,
) -> Option<Ordering> {
compare_numeric_order(left, right).or_else(|| Value::strict_order_cmp(left, right))
}
trait OrderingSemantics<T: ?Sized> {
fn compare(left: &T, right: &T) -> Ordering;
}
struct CanonicalValueOrderingSemantics;
impl OrderingSemantics<Value> for CanonicalValueOrderingSemantics {
fn compare(left: &Value, right: &Value) -> Ordering {
if let Some(ordering) = compare_numeric_or_strict_order(left, right) {
return ordering;
}
Value::canonical_cmp(left, right)
}
}
#[must_use]
pub(in crate::db) fn canonical_value_compare(left: &Value, right: &Value) -> Ordering {
CanonicalValueOrderingSemantics::compare(left, right)
}
#[must_use]
pub(in crate::db) fn compare_numeric_eq(left: &Value, right: &Value) -> Option<bool> {
compare_numeric_order(left, right).map(|ordering| ordering == Ordering::Equal)
}