use crate::dialect::DialectTypes;
use crate::sql::{SQL, Token};
use crate::traits::SQLParam;
use crate::types::{DataType, Integral, Numeric};
use crate::{Dialect, MySQLDialect, PostgresDialect, SQLiteDialect};
use drizzle_types::mysql::types::{
BigInt as MyBigInt, BigIntUnsigned as MyBigIntUnsigned, Decimal as MyDecimal,
Double as MyDouble, Float as MyFloat, Int as MyInt, IntUnsigned as MyIntUnsigned,
MediumInt as MyMediumInt, MediumIntUnsigned as MyMediumIntUnsigned, SmallInt as MySmallInt,
SmallIntUnsigned as MySmallIntUnsigned, TinyInt as MyTinyInt,
TinyIntUnsigned as MyTinyIntUnsigned, Year as MyYear,
};
use drizzle_types::postgres::types::{Float4, Float8, Int2, Int4, Int8, Numeric as PgNumeric};
use drizzle_types::sqlite::types::{
Integer as SqliteInteger, Numeric as SqliteNumeric, Real as SqliteReal,
};
use super::{AggOr, Expr, NullOr, Nullability, SQLExpr, Scalar};
#[diagnostic::on_unimplemented(
message = "`{Self}` does not provide this math function",
label = "SQLite only has CEIL/FLOOR/TRUNC/SQRT/POWER/EXP/LN/LOG*/PI with SQLITE_ENABLE_MATH_FUNCTIONS",
note = "enable drizzle's `math` feature and build SQLite with the math functions, e.g. `LIBSQLITE3_FLAGS=\"-DSQLITE_ENABLE_MATH_FUNCTIONS\"` for bundled rusqlite"
)]
pub trait MathExt {}
impl MathExt for PostgresDialect {}
impl MathExt for MySQLDialect {}
#[cfg(feature = "math")]
impl MathExt for SQLiteDialect {}
#[diagnostic::on_unimplemented(
message = "this math function is not available for this dialect",
label = "use a dialect-specific alternative"
)]
pub trait Log2Policy {
type Nullable: Nullability;
}
#[doc(hidden)]
pub trait DomainMathPolicy<Input: Nullability> {
type Nullable: Nullability;
}
#[diagnostic::on_unimplemented(
message = "this math function is not available for this dialect",
label = "use a dialect-specific alternative"
)]
pub trait PiSupport {}
impl Log2Policy for SQLiteDialect {
type Nullable = super::Null;
}
impl Log2Policy for MySQLDialect {
type Nullable = super::Null;
}
impl<Input: Nullability> DomainMathPolicy<Input> for SQLiteDialect {
type Nullable = super::Null;
}
impl<Input: Nullability> DomainMathPolicy<Input> for MySQLDialect {
type Nullable = super::Null;
}
impl<Input: Nullability> DomainMathPolicy<Input> for PostgresDialect {
type Nullable = Input;
}
impl PiSupport for PostgresDialect {}
impl PiSupport for MySQLDialect {}
#[cfg(feature = "math")]
impl PiSupport for SQLiteDialect {}
#[diagnostic::on_unimplemented(
message = "no RANDOM return type defined for this dialect",
label = "RANDOM result type is not configured for this dialect marker"
)]
pub trait RandomPolicy {
type Random: DataType;
}
impl RandomPolicy for SQLiteDialect {
type Random = SqliteInteger;
}
impl RandomPolicy for PostgresDialect {
type Random = drizzle_types::postgres::types::Float8;
}
impl RandomPolicy for MySQLDialect {
type Random = drizzle_types::mysql::types::Double;
}
#[diagnostic::on_unimplemented(
message = "no rounding policy for `{Self}` on this dialect",
label = "round/ceil/floor/trunc return type is not defined for this SQL type/dialect"
)]
pub trait RoundingPolicy<D>: Numeric {
type Output: DataType;
fn precision_operand<'a, V: SQLParam + 'a>(expr: SQL<'a, V>) -> SQL<'a, V> {
expr
}
fn coerce_result<'a, V: SQLParam + 'a>(sql: SQL<'a, V>) -> SQL<'a, V> {
sql
}
}
pub(super) fn pg_double<'a, V: SQLParam + 'a>(sql: SQL<'a, V>) -> SQL<'a, V> {
match V::DIALECT {
Dialect::PostgreSQL => pg_cast(sql, "DOUBLE PRECISION"),
Dialect::SQLite | Dialect::MySQL => sql,
}
}
pub(super) fn pg_cast<'a, V: SQLParam + 'a>(
expr: SQL<'a, V>,
type_name: &'static str,
) -> SQL<'a, V> {
SQL::func("CAST", expr.push(Token::AS).append(SQL::raw(type_name)))
}
impl RoundingPolicy<SQLiteDialect> for SqliteInteger {
type Output = SqliteReal;
}
impl RoundingPolicy<SQLiteDialect> for SqliteReal {
type Output = Self;
}
impl RoundingPolicy<SQLiteDialect> for SqliteNumeric {
type Output = SqliteReal;
}
macro_rules! postgres_numeric_rounding_policy {
($($ty:ty),+ $(,)?) => {
$(
impl RoundingPolicy<PostgresDialect> for $ty {
type Output = Float8;
fn coerce_result<'a, V: SQLParam + 'a>(sql: SQL<'a, V>) -> SQL<'a, V> {
pg_cast(sql, "DOUBLE PRECISION")
}
}
)+
};
}
postgres_numeric_rounding_policy!(Int2, Int4, Int8, PgNumeric);
macro_rules! postgres_float_rounding_policy {
($($ty:ty),+ $(,)?) => {
$(
impl RoundingPolicy<PostgresDialect> for $ty {
type Output = Float8;
fn precision_operand<'a, V: SQLParam + 'a>(expr: SQL<'a, V>) -> SQL<'a, V> {
pg_cast(expr, "NUMERIC")
}
fn coerce_result<'a, V: SQLParam + 'a>(sql: SQL<'a, V>) -> SQL<'a, V> {
pg_cast(sql, "DOUBLE PRECISION")
}
}
)+
};
}
postgres_float_rounding_policy!(Float4, Float8);
macro_rules! mysql_rounding_policy {
($output:ty; $($ty:ty),+ $(,)?) => {
$(
impl RoundingPolicy<MySQLDialect> for $ty {
type Output = $output;
}
)+
};
}
mysql_rounding_policy!(MyBigInt; MyTinyInt, MySmallInt, MyMediumInt, MyInt, MyBigInt,);
mysql_rounding_policy!(MyBigIntUnsigned;
MyTinyIntUnsigned,
MySmallIntUnsigned,
MyMediumIntUnsigned,
MyIntUnsigned,
MyBigIntUnsigned,
MyYear,
);
impl RoundingPolicy<MySQLDialect> for MyFloat {
type Output = MyDouble;
}
impl RoundingPolicy<MySQLDialect> for MyDouble {
type Output = Self;
}
impl RoundingPolicy<MySQLDialect> for MyDecimal {
type Output = Self;
}
pub fn abs<'a, V, E>(expr: E) -> SQLExpr<'a, V, E::SQLType, E::Nullable, E::Aggregate>
where
V: SQLParam + 'a,
E: Expr<'a, V>,
E::SQLType: Numeric,
{
SQLExpr::new(SQL::func("ABS", expr.into_sql()))
}
#[allow(clippy::type_complexity)]
pub fn round<'a, V, E>(
expr: E,
) -> SQLExpr<
'a,
V,
<E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
E::Nullable,
E::Aggregate,
>
where
V: SQLParam + 'a,
E: Expr<'a, V>,
E::SQLType: RoundingPolicy<V::DialectMarker>,
{
SQLExpr::new(
<E::SQLType as RoundingPolicy<V::DialectMarker>>::coerce_result(SQL::func(
"ROUND",
expr.into_sql(),
)),
)
}
#[allow(clippy::type_complexity)]
pub fn round_to<'a, V, E, P>(
expr: E,
precision: P,
) -> SQLExpr<
'a,
V,
<E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
<E::Nullable as NullOr<P::Nullable>>::Output,
<E::Aggregate as AggOr<P::Aggregate>>::Output,
>
where
V: SQLParam + 'a,
E: Expr<'a, V>,
E::SQLType: RoundingPolicy<V::DialectMarker>,
P: Expr<'a, V>,
P::SQLType: Integral,
E::Nullable: NullOr<P::Nullable>,
P::Nullable: Nullability,
E::Aggregate: AggOr<P::Aggregate>,
{
SQLExpr::new(
<E::SQLType as RoundingPolicy<V::DialectMarker>>::coerce_result(SQL::func(
"ROUND",
<E::SQLType as RoundingPolicy<V::DialectMarker>>::precision_operand(expr.into_sql())
.push(Token::COMMA)
.append(precision.into_sql()),
)),
)
}
#[allow(clippy::type_complexity)]
pub fn ceil<'a, V, E>(
expr: E,
) -> SQLExpr<
'a,
V,
<E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
E::Nullable,
E::Aggregate,
>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
E: Expr<'a, V>,
E::SQLType: RoundingPolicy<V::DialectMarker>,
{
SQLExpr::new(
<E::SQLType as RoundingPolicy<V::DialectMarker>>::coerce_result(SQL::func(
"CEIL",
expr.into_sql(),
)),
)
}
#[allow(clippy::type_complexity)]
pub fn floor<'a, V, E>(
expr: E,
) -> SQLExpr<
'a,
V,
<E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
E::Nullable,
E::Aggregate,
>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
E: Expr<'a, V>,
E::SQLType: RoundingPolicy<V::DialectMarker>,
{
SQLExpr::new(
<E::SQLType as RoundingPolicy<V::DialectMarker>>::coerce_result(SQL::func(
"FLOOR",
expr.into_sql(),
)),
)
}
#[allow(clippy::type_complexity)]
pub fn trunc<'a, V, E>(
expr: E,
) -> SQLExpr<
'a,
V,
<E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
E::Nullable,
E::Aggregate,
>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
E: Expr<'a, V>,
E::SQLType: RoundingPolicy<V::DialectMarker>,
{
let expr = expr.into_sql();
let truncated = match V::DIALECT {
Dialect::MySQL => SQL::func("TRUNCATE", expr.push(Token::COMMA).append(SQL::raw("0"))),
Dialect::SQLite | Dialect::PostgreSQL => SQL::func("TRUNC", expr),
};
SQLExpr::new(<E::SQLType as RoundingPolicy<V::DialectMarker>>::coerce_result(truncated))
}
#[allow(clippy::type_complexity)]
pub fn sqrt<'a, V, E>(
expr: E,
) -> SQLExpr<
'a,
V,
<V::DialectMarker as DialectTypes>::Double,
<V::DialectMarker as DomainMathPolicy<E::Nullable>>::Nullable,
E::Aggregate,
>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
V::DialectMarker: DomainMathPolicy<E::Nullable>,
E: Expr<'a, V>,
E::SQLType: Numeric,
{
SQLExpr::new(pg_double(SQL::func("SQRT", expr.into_sql())))
}
#[allow(clippy::type_complexity)]
pub fn power<'a, V, E1, E2>(
base: E1,
exponent: E2,
) -> SQLExpr<
'a,
V,
<V::DialectMarker as DialectTypes>::Double,
<E1::Nullable as NullOr<E2::Nullable>>::Output,
<E1::Aggregate as AggOr<E2::Aggregate>>::Output,
>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
E1: Expr<'a, V>,
E1::SQLType: Numeric,
E2: Expr<'a, V>,
E2::SQLType: Numeric,
E1::Nullable: NullOr<E2::Nullable>,
E2::Nullable: Nullability,
E1::Aggregate: AggOr<E2::Aggregate>,
{
SQLExpr::new(pg_double(SQL::func(
"POWER",
base.into_sql()
.push(Token::COMMA)
.append(exponent.into_sql()),
)))
}
pub fn exp<'a, V, E>(
expr: E,
) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, E::Nullable, E::Aggregate>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
E: Expr<'a, V>,
E::SQLType: Numeric,
{
SQLExpr::new(pg_double(SQL::func("EXP", expr.into_sql())))
}
#[allow(clippy::type_complexity)]
pub fn ln<'a, V, E>(
expr: E,
) -> SQLExpr<
'a,
V,
<V::DialectMarker as DialectTypes>::Double,
<V::DialectMarker as DomainMathPolicy<E::Nullable>>::Nullable,
E::Aggregate,
>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
V::DialectMarker: DomainMathPolicy<E::Nullable>,
E: Expr<'a, V>,
E::SQLType: Numeric,
{
SQLExpr::new(pg_double(SQL::func("LN", expr.into_sql())))
}
#[allow(clippy::type_complexity)]
pub fn log10<'a, V, E>(
expr: E,
) -> SQLExpr<
'a,
V,
<V::DialectMarker as DialectTypes>::Double,
<V::DialectMarker as DomainMathPolicy<E::Nullable>>::Nullable,
E::Aggregate,
>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
V::DialectMarker: DomainMathPolicy<E::Nullable>,
E: Expr<'a, V>,
E::SQLType: Numeric,
{
SQLExpr::new(pg_double(SQL::func("LOG10", expr.into_sql())))
}
#[allow(clippy::type_complexity)]
pub fn log<'a, V, E1, E2>(
base: E1,
value: E2,
) -> SQLExpr<
'a,
V,
<V::DialectMarker as DialectTypes>::Double,
<V::DialectMarker as DomainMathPolicy<
<E1::Nullable as NullOr<E2::Nullable>>::Output,
>>::Nullable,
<E1::Aggregate as AggOr<E2::Aggregate>>::Output,
>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
V::DialectMarker:
DomainMathPolicy<<E1::Nullable as NullOr<E2::Nullable>>::Output>,
E1: Expr<'a, V>,
E1::SQLType: Numeric,
E2: Expr<'a, V>,
E2::SQLType: Numeric,
E1::Nullable: NullOr<E2::Nullable>,
E2::Nullable: Nullability,
E1::Aggregate: AggOr<E2::Aggregate>,
{
let (base, value) = (base.into_sql(), value.into_sql());
let (base, value) = match V::DIALECT {
Dialect::PostgreSQL => (pg_cast(base, "NUMERIC"), pg_cast(value, "NUMERIC")),
Dialect::SQLite | Dialect::MySQL => (base, value),
};
SQLExpr::new(pg_double(SQL::func(
"LOG",
base.push(Token::COMMA).append(value),
)))
}
pub trait SignPolicy {
type Sign: DataType;
}
impl SignPolicy for SQLiteDialect {
type Sign = SqliteInteger;
}
impl SignPolicy for PostgresDialect {
type Sign = Float8;
}
impl SignPolicy for MySQLDialect {
type Sign = MyBigInt;
}
pub fn sign<'a, V, E>(
expr: E,
) -> SQLExpr<'a, V, <V::DialectMarker as SignPolicy>::Sign, E::Nullable, E::Aggregate>
where
V: SQLParam + 'a,
V::DialectMarker: SignPolicy,
E: Expr<'a, V>,
E::SQLType: Numeric,
{
SQLExpr::new(pg_double(SQL::func("SIGN", expr.into_sql())))
}
#[allow(clippy::type_complexity)]
pub fn mod_<'a, V, E1, E2>(
dividend: E1,
divisor: E2,
) -> SQLExpr<
'a,
V,
E1::SQLType,
<E1::Nullable as NullOr<E2::Nullable>>::Output,
<E1::Aggregate as AggOr<E2::Aggregate>>::Output,
>
where
V: SQLParam + 'a,
E1: Expr<'a, V>,
E1::SQLType: Numeric,
E2: Expr<'a, V>,
E2::SQLType: Numeric,
E1::Nullable: NullOr<E2::Nullable>,
E2::Nullable: Nullability,
E1::Aggregate: AggOr<E2::Aggregate>,
{
SQLExpr::new(super::ops::binary_operator_sql(
dividend.into_expr_sql(),
Token::REM,
divisor.into_expr_sql(),
))
}
#[must_use]
pub fn pi<'a, V>()
-> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, super::NonNull, Scalar>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
V::DialectMarker: PiSupport,
{
SQLExpr::new(SQL::raw("PI()"))
}
#[must_use]
pub fn random<'a, V>()
-> SQLExpr<'a, V, <V::DialectMarker as RandomPolicy>::Random, super::NonNull, Scalar>
where
V: SQLParam + 'a,
V::DialectMarker: RandomPolicy,
{
SQLExpr::new(SQL::raw(match V::DIALECT {
Dialect::MySQL => "RAND()",
Dialect::SQLite | Dialect::PostgreSQL => "RANDOM()",
}))
}
#[allow(clippy::type_complexity)]
pub fn log2<'a, V, E>(
expr: E,
) -> SQLExpr<
'a,
V,
<V::DialectMarker as DialectTypes>::Double,
<V::DialectMarker as Log2Policy>::Nullable,
E::Aggregate,
>
where
V: SQLParam + 'a,
V::DialectMarker: MathExt,
V::DialectMarker: Log2Policy,
E: Expr<'a, V>,
E::SQLType: Numeric,
{
SQLExpr::new(SQL::func("LOG2", expr.into_sql()))
}