use super::Numeric;
#[doc(hidden)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct ArithmeticOp;
#[doc(hidden)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct AddOp;
#[doc(hidden)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct SubOp;
#[doc(hidden)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct MulOp;
#[doc(hidden)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct DivOp;
#[doc(hidden)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct RemOp;
#[doc(hidden)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct PropagateNullability;
#[doc(hidden)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct AlwaysNullable;
#[doc(hidden)]
pub trait ArithmeticNullability: super::private::Sealed + Copy + 'static {}
impl super::private::Sealed for PropagateNullability {}
impl super::private::Sealed for AlwaysNullable {}
impl ArithmeticNullability for PropagateNullability {}
impl ArithmeticNullability for AlwaysNullable {}
#[diagnostic::on_unimplemented(
message = "arithmetic between `{Self}` and `{Rhs}` is not supported",
label = "both operands must be Numeric (Int, BigInt, Float, Double, etc.)"
)]
pub trait ArithmeticOutput<Rhs: Numeric = Self, Op = ArithmeticOp>: Numeric {
type Output: Numeric;
type Nullability: ArithmeticNullability;
}
#[diagnostic::on_unimplemented(
message = "unary negation of `{Self}` is not supported",
label = "the dialect has no numeric result mapping for this operand"
)]
pub trait NegOutput: Numeric {
type Output: Numeric;
}
macro_rules! neg_output {
($input:ty => $out:ty) => {
impl NegOutput for $input {
type Output = $out;
}
};
}
macro_rules! arithmetic_output {
($lhs:ty, $rhs:ty => $out:ty) => {
arithmetic_output!($lhs, $rhs => $out; zero_divisor: PropagateNullability);
};
($lhs:ty, $rhs:ty => $out:ty; zero_divisor: $zero_divisor:ty) => {
impl ArithmeticOutput<$rhs> for $lhs {
type Output = $out;
type Nullability = PropagateNullability;
}
impl ArithmeticOutput<$rhs, AddOp> for $lhs {
type Output = $out;
type Nullability = PropagateNullability;
}
impl ArithmeticOutput<$rhs, SubOp> for $lhs {
type Output = $out;
type Nullability = PropagateNullability;
}
impl ArithmeticOutput<$rhs, MulOp> for $lhs {
type Output = $out;
type Nullability = PropagateNullability;
}
impl ArithmeticOutput<$rhs, DivOp> for $lhs {
type Output = $out;
type Nullability = $zero_divisor;
}
impl ArithmeticOutput<$rhs, RemOp> for $lhs {
type Output = $out;
type Nullability = $zero_divisor;
}
};
}
use crate::sqlite::types::{Integer, Numeric as SqliteNumeric, Real};
arithmetic_output!(Integer, Integer => Integer; zero_divisor: AlwaysNullable);
arithmetic_output!(Integer, Real => Real; zero_divisor: AlwaysNullable);
arithmetic_output!(Integer, SqliteNumeric => SqliteNumeric; zero_divisor: AlwaysNullable);
arithmetic_output!(Real, Integer => Real; zero_divisor: AlwaysNullable);
arithmetic_output!(Real, Real => Real; zero_divisor: AlwaysNullable);
arithmetic_output!(Real, SqliteNumeric => Real; zero_divisor: AlwaysNullable);
arithmetic_output!(SqliteNumeric, Integer => SqliteNumeric; zero_divisor: AlwaysNullable);
arithmetic_output!(SqliteNumeric, Real => Real; zero_divisor: AlwaysNullable);
arithmetic_output!(SqliteNumeric, SqliteNumeric => SqliteNumeric; zero_divisor: AlwaysNullable);
use crate::sqlite::types::Any as SqliteAny;
arithmetic_output!(SqliteAny, SqliteAny => SqliteAny);
arithmetic_output!(SqliteAny, Integer => SqliteAny);
arithmetic_output!(SqliteAny, Real => SqliteAny);
arithmetic_output!(SqliteAny, SqliteNumeric => SqliteAny);
arithmetic_output!(Integer, SqliteAny => SqliteAny);
arithmetic_output!(Real, SqliteAny => SqliteAny);
arithmetic_output!(SqliteNumeric, SqliteAny => SqliteAny);
neg_output!(Integer => Integer);
neg_output!(Real => Real);
neg_output!(SqliteNumeric => SqliteNumeric);
neg_output!(SqliteAny => SqliteAny);
use crate::postgres::types::{Float4, Float8, Int2, Int4, Int8, Numeric as PgNumeric};
arithmetic_output!(Int2, Int2 => Int2);
arithmetic_output!(Int2, Int4 => Int4); arithmetic_output!(Int2, Int8 => Int8); arithmetic_output!(Int2, Float4 => Float4); arithmetic_output!(Int2, Float8 => Float8); arithmetic_output!(Int2, PgNumeric => PgNumeric);
arithmetic_output!(Int4, Int2 => Int4); arithmetic_output!(Int4, Int4 => Int4);
arithmetic_output!(Int4, Int8 => Int8); arithmetic_output!(Int4, Float4 => Float8); arithmetic_output!(Int4, Float8 => Float8); arithmetic_output!(Int4, PgNumeric => PgNumeric);
arithmetic_output!(Int8, Int2 => Int8); arithmetic_output!(Int8, Int4 => Int8); arithmetic_output!(Int8, Int8 => Int8);
arithmetic_output!(Int8, Float4 => Float8); arithmetic_output!(Int8, Float8 => Float8); arithmetic_output!(Int8, PgNumeric => PgNumeric);
arithmetic_output!(Float4, Int2 => Float4); arithmetic_output!(Float4, Int4 => Float8); arithmetic_output!(Float4, Int8 => Float8); arithmetic_output!(Float4, Float4 => Float4);
arithmetic_output!(Float4, Float8 => Float8); arithmetic_output!(Float4, PgNumeric => Float8);
arithmetic_output!(Float8, Int2 => Float8);
arithmetic_output!(Float8, Int4 => Float8);
arithmetic_output!(Float8, Int8 => Float8);
arithmetic_output!(Float8, Float4 => Float8); arithmetic_output!(Float8, Float8 => Float8);
arithmetic_output!(Float8, PgNumeric => Float8);
arithmetic_output!(PgNumeric, Int2 => PgNumeric);
arithmetic_output!(PgNumeric, Int4 => PgNumeric);
arithmetic_output!(PgNumeric, Int8 => PgNumeric);
arithmetic_output!(PgNumeric, Float4 => Float8); arithmetic_output!(PgNumeric, Float8 => Float8);
arithmetic_output!(PgNumeric, PgNumeric => PgNumeric);
neg_output!(Int2 => Int2);
neg_output!(Int4 => Int4);
neg_output!(Int8 => Int8);
neg_output!(Float4 => Float4);
neg_output!(Float8 => Float8);
neg_output!(PgNumeric => PgNumeric);
use crate::mysql::types::{
BigInt as MyBigInt, BigIntUnsigned as MyBigIntUnsigned, Decimal as MyDecimal,
Double as MyDouble,
};
macro_rules! mysql_arithmetic {
(
signed: [$($signed:ty),+ $(,)?],
unsigned: [$($unsigned:ty),+ $(,)?],
decimal: $decimal:ty,
approximate: [$($approximate:ty),+ $(,)?],
) => {
mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
[$($signed),+], [$($signed),+] => MyBigInt);
mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
[$($signed),+], [$($unsigned),+] => MyBigIntUnsigned);
mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
[$($unsigned),+], [$($signed),+, $($unsigned),+] => MyBigIntUnsigned);
mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
[$($signed),+, $($unsigned),+], [$decimal] => MyDecimal);
mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
[$decimal], [$($signed),+, $($unsigned),+, $decimal] => MyDecimal);
mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
[$($signed),+, $($unsigned),+, $decimal], [$($approximate),+] => MyDouble);
mysql_arithmetic!(@matrix [AddOp, SubOp, MulOp], PropagateNullability;
[$($approximate),+],
[$($signed),+, $($unsigned),+, $decimal, $($approximate),+] => MyDouble);
mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
[$($signed),+], [$($signed),+, $($unsigned),+] => MyBigInt);
mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
[$($unsigned),+], [$($signed),+, $($unsigned),+] => MyBigIntUnsigned);
mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
[$($signed),+, $($unsigned),+], [$decimal] => MyDecimal);
mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
[$decimal], [$($signed),+, $($unsigned),+, $decimal] => MyDecimal);
mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
[$($signed),+, $($unsigned),+, $decimal], [$($approximate),+] => MyDouble);
mysql_arithmetic!(@matrix [RemOp], AlwaysNullable;
[$($approximate),+],
[$($signed),+, $($unsigned),+, $decimal, $($approximate),+] => MyDouble);
mysql_arithmetic!(@matrix [DivOp], AlwaysNullable;
[$($signed),+, $($unsigned),+, $decimal],
[$($signed),+, $($unsigned),+, $decimal] => MyDecimal);
mysql_arithmetic!(@matrix [DivOp], AlwaysNullable;
[$($signed),+, $($unsigned),+, $decimal], [$($approximate),+] => MyDouble);
mysql_arithmetic!(@matrix [DivOp], AlwaysNullable;
[$($approximate),+],
[$($signed),+, $($unsigned),+, $decimal, $($approximate),+] => MyDouble);
$(neg_output!($signed => MyBigInt);)+
$(neg_output!($unsigned => MyBigInt);)+
neg_output!($decimal => MyDecimal);
$(neg_output!($approximate => MyDouble);)+
};
(@matrix $ops:tt, $nullability:ty;
[$($lhs:ty),+], $rhs:tt => $out:ty
) => {
$(mysql_arithmetic!(@row $ops, $nullability; $lhs, $rhs => $out);)+
};
(@row [$op:ty $(, $remaining:ty)*], $nullability:ty;
$lhs:ty, [$($rhs:ty),+] => $out:ty
) => {
$(
impl ArithmeticOutput<$rhs, $op> for $lhs {
type Output = $out;
type Nullability = $nullability;
}
)+
mysql_arithmetic!(@row [$($remaining),*], $nullability;
$lhs, [$($rhs),+] => $out);
};
(@row [], $nullability:ty; $lhs:ty, $rhs:tt => $out:ty) => {};
}
mysql_arithmetic! {
signed: [
crate::mysql::types::TinyInt,
crate::mysql::types::SmallInt,
crate::mysql::types::MediumInt,
crate::mysql::types::Int,
crate::mysql::types::BigInt,
],
unsigned: [
crate::mysql::types::TinyIntUnsigned,
crate::mysql::types::SmallIntUnsigned,
crate::mysql::types::MediumIntUnsigned,
crate::mysql::types::IntUnsigned,
crate::mysql::types::BigIntUnsigned,
crate::mysql::types::Year,
],
decimal: crate::mysql::types::Decimal,
approximate: [crate::mysql::types::Float, crate::mysql::types::Double],
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mysql::types as my;
use crate::postgres::types as pg;
use crate::sqlite::types as sqlite;
trait Same<T> {}
impl<T> Same<T> for T {}
fn assert_output<Lhs, Rhs, Op, Output, Nullability>()
where
Lhs: ArithmeticOutput<Rhs, Op, Output = Output>,
Rhs: Numeric,
Output: Numeric,
<Lhs as ArithmeticOutput<Rhs, Op>>::Nullability: Same<Nullability>,
{
}
fn assert_neg_output<Input, Output>()
where
Input: NegOutput<Output = Output>,
Output: Numeric,
{
}
#[test]
fn mysql_operator_result_types_follow_server_categories() {
assert_output::<my::Int, my::SmallInt, AddOp, my::BigInt, PropagateNullability>();
assert_output::<my::Int, my::IntUnsigned, SubOp, my::BigIntUnsigned, PropagateNullability>(
);
assert_output::<my::BigIntUnsigned, my::Int, MulOp, my::BigIntUnsigned, PropagateNullability>(
);
assert_output::<my::Int, my::Int, DivOp, my::Decimal, AlwaysNullable>();
assert_output::<my::Int, my::IntUnsigned, RemOp, my::BigInt, AlwaysNullable>();
assert_output::<my::IntUnsigned, my::Int, RemOp, my::BigIntUnsigned, AlwaysNullable>();
assert_output::<my::Decimal, my::Int, AddOp, my::Decimal, PropagateNullability>();
assert_output::<my::Float, my::Int, AddOp, my::Double, PropagateNullability>();
assert_output::<my::Int, my::Double, DivOp, my::Double, AlwaysNullable>();
}
#[test]
fn every_mysql_numeric_marker_has_operator_and_negation_policy() {
macro_rules! assert_numeric_policy {
($($ty:ty),+ $(,)?) => {
$(
assert_output::<$ty, $ty, AddOp, _, PropagateNullability>();
assert_output::<$ty, $ty, DivOp, _, AlwaysNullable>();
assert_output::<$ty, $ty, RemOp, _, AlwaysNullable>();
assert_neg_output::<$ty, _>();
)+
};
}
assert_numeric_policy!(
my::TinyInt,
my::TinyIntUnsigned,
my::SmallInt,
my::SmallIntUnsigned,
my::MediumInt,
my::MediumIntUnsigned,
my::Int,
my::IntUnsigned,
my::BigInt,
my::BigIntUnsigned,
my::Year,
my::Decimal,
my::Float,
my::Double,
);
}
#[test]
fn legacy_operator_independent_projection_remains_available() {
fn assert_legacy<Lhs, Rhs, Output>()
where
Lhs: ArithmeticOutput<Rhs, Output = Output>,
Rhs: Numeric,
Output: Numeric,
{
}
assert_legacy::<sqlite::Integer, sqlite::Real, sqlite::Real>();
assert_legacy::<pg::Int4, pg::Float8, pg::Float8>();
}
#[test]
fn sqlite_zero_divisor_operators_are_nullable() {
assert_output::<sqlite::Integer, sqlite::Integer, DivOp, sqlite::Integer, AlwaysNullable>();
assert_output::<sqlite::Integer, sqlite::Integer, RemOp, sqlite::Integer, AlwaysNullable>();
assert_output::<sqlite::Real, sqlite::Integer, DivOp, sqlite::Real, AlwaysNullable>();
}
#[test]
fn mysql_unary_negation_widens_to_a_signed_result() {
assert_neg_output::<my::TinyInt, my::BigInt>();
assert_neg_output::<my::BigIntUnsigned, my::BigInt>();
assert_neg_output::<my::Decimal, my::Decimal>();
assert_neg_output::<my::Float, my::Double>();
}
}