1use crate::dialect::DialectTypes;
13use crate::sql::{SQL, Token};
14use crate::traits::SQLParam;
15use crate::types::{DataType, Integral, Numeric};
16use crate::{Dialect, MySQLDialect, PostgresDialect, SQLiteDialect};
17use drizzle_types::mysql::types::{
18 BigInt as MyBigInt, BigIntUnsigned as MyBigIntUnsigned, Decimal as MyDecimal,
19 Double as MyDouble, Float as MyFloat, Int as MyInt, IntUnsigned as MyIntUnsigned,
20 MediumInt as MyMediumInt, MediumIntUnsigned as MyMediumIntUnsigned, SmallInt as MySmallInt,
21 SmallIntUnsigned as MySmallIntUnsigned, TinyInt as MyTinyInt,
22 TinyIntUnsigned as MyTinyIntUnsigned, Year as MyYear,
23};
24use drizzle_types::postgres::types::{Float4, Float8, Int2, Int4, Int8, Numeric as PgNumeric};
25use drizzle_types::sqlite::types::{
26 Integer as SqliteInteger, Numeric as SqliteNumeric, Real as SqliteReal,
27};
28
29use super::{AggOr, Expr, NullOr, Nullability, SQLExpr, Scalar};
30
31#[diagnostic::on_unimplemented(
41 message = "`{Self}` does not provide this math function",
42 label = "SQLite only has CEIL/FLOOR/TRUNC/SQRT/POWER/EXP/LN/LOG*/PI with SQLITE_ENABLE_MATH_FUNCTIONS",
43 note = "enable drizzle's `math` feature and build SQLite with the math functions, e.g. `LIBSQLITE3_FLAGS=\"-DSQLITE_ENABLE_MATH_FUNCTIONS\"` for bundled rusqlite"
44)]
45pub trait MathExt {}
46
47impl MathExt for PostgresDialect {}
48impl MathExt for MySQLDialect {}
49#[cfg(feature = "math")]
50impl MathExt for SQLiteDialect {}
51
52#[diagnostic::on_unimplemented(
53 message = "this math function is not available for this dialect",
54 label = "use a dialect-specific alternative"
55)]
56pub trait Log2Policy {
57 type Nullable: Nullability;
58}
59
60#[doc(hidden)]
63pub trait DomainMathPolicy<Input: Nullability> {
64 type Nullable: Nullability;
65}
66
67#[diagnostic::on_unimplemented(
68 message = "this math function is not available for this dialect",
69 label = "use a dialect-specific alternative"
70)]
71pub trait PiSupport {}
72
73impl Log2Policy for SQLiteDialect {
74 type Nullable = super::Null;
75}
76impl Log2Policy for MySQLDialect {
77 type Nullable = super::Null;
78}
79impl<Input: Nullability> DomainMathPolicy<Input> for SQLiteDialect {
80 type Nullable = super::Null;
81}
82impl<Input: Nullability> DomainMathPolicy<Input> for MySQLDialect {
83 type Nullable = super::Null;
84}
85impl<Input: Nullability> DomainMathPolicy<Input> for PostgresDialect {
86 type Nullable = Input;
87}
88impl PiSupport for PostgresDialect {}
89impl PiSupport for MySQLDialect {}
90#[cfg(feature = "math")]
91impl PiSupport for SQLiteDialect {}
92
93#[diagnostic::on_unimplemented(
98 message = "no RANDOM return type defined for this dialect",
99 label = "RANDOM result type is not configured for this dialect marker"
100)]
101pub trait RandomPolicy {
102 type Random: DataType;
103}
104
105impl RandomPolicy for SQLiteDialect {
106 type Random = SqliteInteger;
107}
108
109impl RandomPolicy for PostgresDialect {
110 type Random = drizzle_types::postgres::types::Float8;
111}
112
113impl RandomPolicy for MySQLDialect {
114 type Random = drizzle_types::mysql::types::Double;
115}
116
117#[diagnostic::on_unimplemented(
118 message = "no rounding policy for `{Self}` on this dialect",
119 label = "round/ceil/floor/trunc return type is not defined for this SQL type/dialect"
120)]
121pub trait RoundingPolicy<D>: Numeric {
122 type Output: DataType;
123
124 fn precision_operand<'a, V: SQLParam + 'a>(expr: SQL<'a, V>) -> SQL<'a, V> {
129 expr
130 }
131
132 fn coerce_result<'a, V: SQLParam + 'a>(sql: SQL<'a, V>) -> SQL<'a, V> {
137 sql
138 }
139}
140
141pub(super) fn pg_double<'a, V: SQLParam + 'a>(sql: SQL<'a, V>) -> SQL<'a, V> {
147 match V::DIALECT {
148 Dialect::PostgreSQL => pg_cast(sql, "DOUBLE PRECISION"),
149 Dialect::SQLite | Dialect::MySQL => sql,
150 }
151}
152
153pub(super) fn pg_cast<'a, V: SQLParam + 'a>(
155 expr: SQL<'a, V>,
156 type_name: &'static str,
157) -> SQL<'a, V> {
158 SQL::func("CAST", expr.push(Token::AS).append(SQL::raw(type_name)))
159}
160
161impl RoundingPolicy<SQLiteDialect> for SqliteInteger {
162 type Output = SqliteReal;
163}
164impl RoundingPolicy<SQLiteDialect> for SqliteReal {
165 type Output = Self;
166}
167impl RoundingPolicy<SQLiteDialect> for SqliteNumeric {
168 type Output = SqliteReal;
169}
170
171macro_rules! postgres_numeric_rounding_policy {
174 ($($ty:ty),+ $(,)?) => {
175 $(
176 impl RoundingPolicy<PostgresDialect> for $ty {
177 type Output = Float8;
178
179 fn coerce_result<'a, V: SQLParam + 'a>(sql: SQL<'a, V>) -> SQL<'a, V> {
180 pg_cast(sql, "DOUBLE PRECISION")
181 }
182 }
183 )+
184 };
185}
186postgres_numeric_rounding_policy!(Int2, Int4, Int8, PgNumeric);
187
188macro_rules! postgres_float_rounding_policy {
191 ($($ty:ty),+ $(,)?) => {
192 $(
193 impl RoundingPolicy<PostgresDialect> for $ty {
194 type Output = Float8;
195
196 fn precision_operand<'a, V: SQLParam + 'a>(expr: SQL<'a, V>) -> SQL<'a, V> {
197 pg_cast(expr, "NUMERIC")
198 }
199
200 fn coerce_result<'a, V: SQLParam + 'a>(sql: SQL<'a, V>) -> SQL<'a, V> {
201 pg_cast(sql, "DOUBLE PRECISION")
202 }
203 }
204 )+
205 };
206}
207postgres_float_rounding_policy!(Float4, Float8);
208
209macro_rules! mysql_rounding_policy {
210 ($output:ty; $($ty:ty),+ $(,)?) => {
211 $(
212 impl RoundingPolicy<MySQLDialect> for $ty {
213 type Output = $output;
214 }
215 )+
216 };
217}
218
219mysql_rounding_policy!(MyBigInt; MyTinyInt, MySmallInt, MyMediumInt, MyInt, MyBigInt,);
220mysql_rounding_policy!(MyBigIntUnsigned;
221 MyTinyIntUnsigned,
222 MySmallIntUnsigned,
223 MyMediumIntUnsigned,
224 MyIntUnsigned,
225 MyBigIntUnsigned,
226 MyYear,
227);
228
229impl RoundingPolicy<MySQLDialect> for MyFloat {
230 type Output = MyDouble;
231}
232impl RoundingPolicy<MySQLDialect> for MyDouble {
233 type Output = Self;
234}
235impl RoundingPolicy<MySQLDialect> for MyDecimal {
236 type Output = Self;
237}
238
239pub fn abs<'a, V, E>(expr: E) -> SQLExpr<'a, V, E::SQLType, E::Nullable, E::Aggregate>
259where
260 V: SQLParam + 'a,
261 E: Expr<'a, V>,
262 E::SQLType: Numeric,
263{
264 SQLExpr::new(SQL::func("ABS", expr.into_sql()))
265}
266
267#[allow(clippy::type_complexity)]
286pub fn round<'a, V, E>(
287 expr: E,
288) -> SQLExpr<
289 'a,
290 V,
291 <E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
292 E::Nullable,
293 E::Aggregate,
294>
295where
296 V: SQLParam + 'a,
297 E: Expr<'a, V>,
298 E::SQLType: RoundingPolicy<V::DialectMarker>,
299{
300 SQLExpr::new(
301 <E::SQLType as RoundingPolicy<V::DialectMarker>>::coerce_result(SQL::func(
302 "ROUND",
303 expr.into_sql(),
304 )),
305 )
306}
307
308#[allow(clippy::type_complexity)]
323pub fn round_to<'a, V, E, P>(
324 expr: E,
325 precision: P,
326) -> SQLExpr<
327 'a,
328 V,
329 <E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
330 <E::Nullable as NullOr<P::Nullable>>::Output,
331 <E::Aggregate as AggOr<P::Aggregate>>::Output,
332>
333where
334 V: SQLParam + 'a,
335 E: Expr<'a, V>,
336 E::SQLType: RoundingPolicy<V::DialectMarker>,
337 P: Expr<'a, V>,
338 P::SQLType: Integral,
339 E::Nullable: NullOr<P::Nullable>,
340 P::Nullable: Nullability,
341 E::Aggregate: AggOr<P::Aggregate>,
342{
343 SQLExpr::new(
344 <E::SQLType as RoundingPolicy<V::DialectMarker>>::coerce_result(SQL::func(
345 "ROUND",
346 <E::SQLType as RoundingPolicy<V::DialectMarker>>::precision_operand(expr.into_sql())
347 .push(Token::COMMA)
348 .append(precision.into_sql()),
349 )),
350 )
351}
352
353#[allow(clippy::type_complexity)]
368pub fn ceil<'a, V, E>(
369 expr: E,
370) -> SQLExpr<
371 'a,
372 V,
373 <E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
374 E::Nullable,
375 E::Aggregate,
376>
377where
378 V: SQLParam + 'a,
379 V::DialectMarker: MathExt,
380 E: Expr<'a, V>,
381 E::SQLType: RoundingPolicy<V::DialectMarker>,
382{
383 SQLExpr::new(
384 <E::SQLType as RoundingPolicy<V::DialectMarker>>::coerce_result(SQL::func(
385 "CEIL",
386 expr.into_sql(),
387 )),
388 )
389}
390
391#[allow(clippy::type_complexity)]
406pub fn floor<'a, V, E>(
407 expr: E,
408) -> SQLExpr<
409 'a,
410 V,
411 <E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
412 E::Nullable,
413 E::Aggregate,
414>
415where
416 V: SQLParam + 'a,
417 V::DialectMarker: MathExt,
418 E: Expr<'a, V>,
419 E::SQLType: RoundingPolicy<V::DialectMarker>,
420{
421 SQLExpr::new(
422 <E::SQLType as RoundingPolicy<V::DialectMarker>>::coerce_result(SQL::func(
423 "FLOOR",
424 expr.into_sql(),
425 )),
426 )
427}
428
429#[allow(clippy::type_complexity)]
444pub fn trunc<'a, V, E>(
445 expr: E,
446) -> SQLExpr<
447 'a,
448 V,
449 <E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
450 E::Nullable,
451 E::Aggregate,
452>
453where
454 V: SQLParam + 'a,
455 V::DialectMarker: MathExt,
456 E: Expr<'a, V>,
457 E::SQLType: RoundingPolicy<V::DialectMarker>,
458{
459 let expr = expr.into_sql();
460 let truncated = match V::DIALECT {
461 Dialect::MySQL => SQL::func("TRUNCATE", expr.push(Token::COMMA).append(SQL::raw("0"))),
462 Dialect::SQLite | Dialect::PostgreSQL => SQL::func("TRUNC", expr),
463 };
464 SQLExpr::new(<E::SQLType as RoundingPolicy<V::DialectMarker>>::coerce_result(truncated))
465}
466
467#[allow(clippy::type_complexity)]
487pub fn sqrt<'a, V, E>(
488 expr: E,
489) -> SQLExpr<
490 'a,
491 V,
492 <V::DialectMarker as DialectTypes>::Double,
493 <V::DialectMarker as DomainMathPolicy<E::Nullable>>::Nullable,
494 E::Aggregate,
495>
496where
497 V: SQLParam + 'a,
498 V::DialectMarker: MathExt,
499 V::DialectMarker: DomainMathPolicy<E::Nullable>,
500 E: Expr<'a, V>,
501 E::SQLType: Numeric,
502{
503 SQLExpr::new(pg_double(SQL::func("SQRT", expr.into_sql())))
504}
505
506#[allow(clippy::type_complexity)]
521pub fn power<'a, V, E1, E2>(
522 base: E1,
523 exponent: E2,
524) -> SQLExpr<
525 'a,
526 V,
527 <V::DialectMarker as DialectTypes>::Double,
528 <E1::Nullable as NullOr<E2::Nullable>>::Output,
529 <E1::Aggregate as AggOr<E2::Aggregate>>::Output,
530>
531where
532 V: SQLParam + 'a,
533 V::DialectMarker: MathExt,
534 E1: Expr<'a, V>,
535 E1::SQLType: Numeric,
536 E2: Expr<'a, V>,
537 E2::SQLType: Numeric,
538 E1::Nullable: NullOr<E2::Nullable>,
539 E2::Nullable: Nullability,
540 E1::Aggregate: AggOr<E2::Aggregate>,
541{
542 SQLExpr::new(pg_double(SQL::func(
543 "POWER",
544 base.into_sql()
545 .push(Token::COMMA)
546 .append(exponent.into_sql()),
547 )))
548}
549
550pub fn exp<'a, V, E>(
569 expr: E,
570) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, E::Nullable, E::Aggregate>
571where
572 V: SQLParam + 'a,
573 V::DialectMarker: MathExt,
574 E: Expr<'a, V>,
575 E::SQLType: Numeric,
576{
577 SQLExpr::new(pg_double(SQL::func("EXP", expr.into_sql())))
578}
579
580#[allow(clippy::type_complexity)]
596pub fn ln<'a, V, E>(
597 expr: E,
598) -> SQLExpr<
599 'a,
600 V,
601 <V::DialectMarker as DialectTypes>::Double,
602 <V::DialectMarker as DomainMathPolicy<E::Nullable>>::Nullable,
603 E::Aggregate,
604>
605where
606 V: SQLParam + 'a,
607 V::DialectMarker: MathExt,
608 V::DialectMarker: DomainMathPolicy<E::Nullable>,
609 E: Expr<'a, V>,
610 E::SQLType: Numeric,
611{
612 SQLExpr::new(pg_double(SQL::func("LN", expr.into_sql())))
613}
614
615#[allow(clippy::type_complexity)]
631pub fn log10<'a, V, E>(
632 expr: E,
633) -> SQLExpr<
634 'a,
635 V,
636 <V::DialectMarker as DialectTypes>::Double,
637 <V::DialectMarker as DomainMathPolicy<E::Nullable>>::Nullable,
638 E::Aggregate,
639>
640where
641 V: SQLParam + 'a,
642 V::DialectMarker: MathExt,
643 V::DialectMarker: DomainMathPolicy<E::Nullable>,
644 E: Expr<'a, V>,
645 E::SQLType: Numeric,
646{
647 SQLExpr::new(pg_double(SQL::func("LOG10", expr.into_sql())))
648}
649
650#[allow(clippy::type_complexity)]
665pub fn log<'a, V, E1, E2>(
666 base: E1,
667 value: E2,
668) -> SQLExpr<
669 'a,
670 V,
671 <V::DialectMarker as DialectTypes>::Double,
672 <V::DialectMarker as DomainMathPolicy<
673 <E1::Nullable as NullOr<E2::Nullable>>::Output,
674 >>::Nullable,
675 <E1::Aggregate as AggOr<E2::Aggregate>>::Output,
676>
677where
678 V: SQLParam + 'a,
679 V::DialectMarker: MathExt,
680 V::DialectMarker:
681 DomainMathPolicy<<E1::Nullable as NullOr<E2::Nullable>>::Output>,
682 E1: Expr<'a, V>,
683 E1::SQLType: Numeric,
684 E2: Expr<'a, V>,
685 E2::SQLType: Numeric,
686 E1::Nullable: NullOr<E2::Nullable>,
687 E2::Nullable: Nullability,
688 E1::Aggregate: AggOr<E2::Aggregate>,
689{
690 let (base, value) = (base.into_sql(), value.into_sql());
691 let (base, value) = match V::DIALECT {
693 Dialect::PostgreSQL => (pg_cast(base, "NUMERIC"), pg_cast(value, "NUMERIC")),
694 Dialect::SQLite | Dialect::MySQL => (base, value),
695 };
696 SQLExpr::new(pg_double(SQL::func(
697 "LOG",
698 base.push(Token::COMMA).append(value),
699 )))
700}
701
702pub trait SignPolicy {
712 type Sign: DataType;
714}
715
716impl SignPolicy for SQLiteDialect {
717 type Sign = SqliteInteger;
718}
719
720impl SignPolicy for PostgresDialect {
721 type Sign = Float8;
722}
723
724impl SignPolicy for MySQLDialect {
725 type Sign = MyBigInt;
726}
727
728pub fn sign<'a, V, E>(
744 expr: E,
745) -> SQLExpr<'a, V, <V::DialectMarker as SignPolicy>::Sign, E::Nullable, E::Aggregate>
746where
747 V: SQLParam + 'a,
748 V::DialectMarker: SignPolicy,
749 E: Expr<'a, V>,
750 E::SQLType: Numeric,
751{
752 SQLExpr::new(pg_double(SQL::func("SIGN", expr.into_sql())))
753}
754
755#[allow(clippy::type_complexity)]
773pub fn mod_<'a, V, E1, E2>(
774 dividend: E1,
775 divisor: E2,
776) -> SQLExpr<
777 'a,
778 V,
779 E1::SQLType,
780 <E1::Nullable as NullOr<E2::Nullable>>::Output,
781 <E1::Aggregate as AggOr<E2::Aggregate>>::Output,
782>
783where
784 V: SQLParam + 'a,
785 E1: Expr<'a, V>,
786 E1::SQLType: Numeric,
787 E2: Expr<'a, V>,
788 E2::SQLType: Numeric,
789 E1::Nullable: NullOr<E2::Nullable>,
790 E2::Nullable: Nullability,
791 E1::Aggregate: AggOr<E2::Aggregate>,
792{
793 SQLExpr::new(super::ops::binary_operator_sql(
794 dividend.into_expr_sql(),
795 Token::REM,
796 divisor.into_expr_sql(),
797 ))
798}
799
800#[must_use]
817pub fn pi<'a, V>()
818-> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, super::NonNull, Scalar>
819where
820 V: SQLParam + 'a,
821 V::DialectMarker: MathExt,
822 V::DialectMarker: PiSupport,
823{
824 SQLExpr::new(SQL::raw("PI()"))
825}
826
827#[must_use]
844pub fn random<'a, V>()
845-> SQLExpr<'a, V, <V::DialectMarker as RandomPolicy>::Random, super::NonNull, Scalar>
846where
847 V: SQLParam + 'a,
848 V::DialectMarker: RandomPolicy,
849{
850 SQLExpr::new(SQL::raw(match V::DIALECT {
851 Dialect::MySQL => "RAND()",
852 Dialect::SQLite | Dialect::PostgreSQL => "RANDOM()",
853 }))
854}
855
856#[allow(clippy::type_complexity)]
878pub fn log2<'a, V, E>(
879 expr: E,
880) -> SQLExpr<
881 'a,
882 V,
883 <V::DialectMarker as DialectTypes>::Double,
884 <V::DialectMarker as Log2Policy>::Nullable,
885 E::Aggregate,
886>
887where
888 V: SQLParam + 'a,
889 V::DialectMarker: MathExt,
890 V::DialectMarker: Log2Policy,
891 E: Expr<'a, V>,
892 E::SQLType: Numeric,
893{
894 SQLExpr::new(SQL::func("LOG2", expr.into_sql()))
895}