Skip to main content

drizzle_core/expr/
math.rs

1//! Type-safe math functions.
2//!
3//! These functions require `Numeric` types (`SmallInt`, Int, `BigInt`, Float, Double)
4//! and provide compile-time enforcement of mathematical operations.
5//!
6//! # Type Safety
7//!
8//! - `abs`, `round`, `ceil`, `floor`: Require `Numeric` types
9//! - `sqrt`, `power`, `log`, `exp`: Require `Numeric` types, return Double
10//! - `mod_`: Modulo operation requiring `Numeric` types
11
12use crate::dialect::DialectTypes;
13use crate::sql::{SQL, Token};
14use crate::traits::SQLParam;
15use crate::types::{DataType, Integral, Numeric};
16use crate::{PostgresDialect, SQLiteDialect};
17use drizzle_types::postgres::types::{Float4, Float8, Int2, Int4, Int8, Numeric as PgNumeric};
18use drizzle_types::sqlite::types::{
19    Integer as SqliteInteger, Numeric as SqliteNumeric, Real as SqliteReal,
20};
21
22use super::{AggOr, Expr, NullOr, Nullability, SQLExpr, Scalar};
23
24#[diagnostic::on_unimplemented(
25    message = "this math function is not available for this dialect",
26    label = "use a dialect-specific alternative"
27)]
28pub trait SQLiteMathSupport {}
29
30#[diagnostic::on_unimplemented(
31    message = "this math function is not available for this dialect",
32    label = "use a dialect-specific alternative"
33)]
34pub trait PostgresMathSupport {}
35
36impl SQLiteMathSupport for SQLiteDialect {}
37impl PostgresMathSupport for PostgresDialect {}
38
39/// Dialect-specific return type for `RANDOM()`.
40///
41/// `SQLite` `RANDOM()` returns an integer in [-2^63, 2^63).
42/// `PostgreSQL` `RANDOM()` returns a float in [0, 1).
43#[diagnostic::on_unimplemented(
44    message = "no RANDOM return type defined for this dialect",
45    label = "RANDOM result type is not configured for this dialect marker"
46)]
47pub trait RandomPolicy {
48    type Random: DataType;
49}
50
51impl RandomPolicy for SQLiteDialect {
52    type Random = SqliteInteger;
53}
54
55impl RandomPolicy for PostgresDialect {
56    type Random = drizzle_types::postgres::types::Float8;
57}
58
59#[diagnostic::on_unimplemented(
60    message = "no rounding policy for `{Self}` on this dialect",
61    label = "round/ceil/floor/trunc return type is not defined for this SQL type/dialect"
62)]
63pub trait RoundingPolicy<D>: Numeric {
64    type Output: DataType;
65}
66
67impl RoundingPolicy<SQLiteDialect> for SqliteInteger {
68    type Output = SqliteReal;
69}
70impl RoundingPolicy<SQLiteDialect> for SqliteReal {
71    type Output = Self;
72}
73impl RoundingPolicy<SQLiteDialect> for SqliteNumeric {
74    type Output = SqliteReal;
75}
76
77impl RoundingPolicy<PostgresDialect> for Int2 {
78    type Output = Float8;
79}
80impl RoundingPolicy<PostgresDialect> for Int4 {
81    type Output = Float8;
82}
83impl RoundingPolicy<PostgresDialect> for Int8 {
84    type Output = Float8;
85}
86impl RoundingPolicy<PostgresDialect> for Float4 {
87    type Output = Float8;
88}
89impl RoundingPolicy<PostgresDialect> for Float8 {
90    type Output = Self;
91}
92impl RoundingPolicy<PostgresDialect> for PgNumeric {
93    type Output = Float8;
94}
95
96// =============================================================================
97// ABSOLUTE VALUE
98// =============================================================================
99
100/// ABS - returns the absolute value of a number.
101///
102/// Preserves the SQL type and nullability of the input expression.
103///
104/// # Type Safety
105///
106/// ```rust
107/// # let _ = r####"
108/// // ✅ OK: Int column
109/// abs(users.balance);
110///
111/// // ❌ Compile error: Text is not Numeric
112/// abs(users.name);
113/// # "####;
114/// ```
115pub fn abs<'a, V, E>(expr: E) -> SQLExpr<'a, V, E::SQLType, E::Nullable, E::Aggregate>
116where
117    V: SQLParam + 'a,
118    E: Expr<'a, V>,
119    E::SQLType: Numeric,
120{
121    SQLExpr::new(SQL::func("ABS", expr.into_sql()))
122}
123
124// =============================================================================
125// ROUNDING FUNCTIONS
126// =============================================================================
127
128/// ROUND - rounds a number to the nearest integer (or specified precision).
129///
130/// Returns a dialect-aware float type, preserves nullability.
131///
132/// # Example
133///
134/// ```rust
135/// # let _ = r####"
136/// use drizzle_core::expr::round;
137///
138/// // SELECT ROUND(users.price)
139/// let rounded = round(users.price);
140/// # "####;
141/// ```
142#[allow(clippy::type_complexity)]
143pub fn round<'a, V, E>(
144    expr: E,
145) -> SQLExpr<
146    'a,
147    V,
148    <E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
149    E::Nullable,
150    E::Aggregate,
151>
152where
153    V: SQLParam + 'a,
154    E: Expr<'a, V>,
155    E::SQLType: RoundingPolicy<V::DialectMarker>,
156{
157    SQLExpr::new(SQL::func("ROUND", expr.into_sql()))
158}
159
160/// ROUND with precision - rounds a number to specified decimal places.
161///
162/// Returns a dialect-aware float type, preserves nullability of the input expression.
163///
164/// # Example
165///
166/// ```rust
167/// # let _ = r####"
168/// use drizzle_core::expr::round_to;
169///
170/// // SELECT ROUND(users.price, 2)
171/// let rounded = round_to(users.price, 2);
172/// # "####;
173/// ```
174#[allow(clippy::type_complexity)]
175pub fn round_to<'a, V, E, P>(
176    expr: E,
177    precision: P,
178) -> SQLExpr<
179    'a,
180    V,
181    <E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
182    <E::Nullable as NullOr<P::Nullable>>::Output,
183    <E::Aggregate as AggOr<P::Aggregate>>::Output,
184>
185where
186    V: SQLParam + 'a,
187    E: Expr<'a, V>,
188    E::SQLType: RoundingPolicy<V::DialectMarker>,
189    P: Expr<'a, V>,
190    P::SQLType: Integral,
191    E::Nullable: NullOr<P::Nullable>,
192    P::Nullable: Nullability,
193    E::Aggregate: AggOr<P::Aggregate>,
194{
195    SQLExpr::new(SQL::func(
196        "ROUND",
197        expr.into_sql()
198            .push(Token::COMMA)
199            .append(precision.into_sql()),
200    ))
201}
202
203/// CEIL / CEILING - rounds a number up to the nearest integer.
204///
205/// Returns a dialect-aware float type, preserves nullability.
206///
207/// # Example
208///
209/// ```rust
210/// # let _ = r####"
211/// use drizzle_core::expr::ceil;
212///
213/// // SELECT CEIL(users.price)
214/// let ceiling = ceil(users.price);
215/// # "####;
216/// ```
217#[allow(clippy::type_complexity)]
218pub fn ceil<'a, V, E>(
219    expr: E,
220) -> SQLExpr<
221    'a,
222    V,
223    <E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
224    E::Nullable,
225    E::Aggregate,
226>
227where
228    V: SQLParam + 'a,
229    E: Expr<'a, V>,
230    E::SQLType: RoundingPolicy<V::DialectMarker>,
231{
232    SQLExpr::new(SQL::func("CEIL", expr.into_sql()))
233}
234
235/// FLOOR - rounds a number down to the nearest integer.
236///
237/// Returns a dialect-aware float type, preserves nullability.
238///
239/// # Example
240///
241/// ```rust
242/// # let _ = r####"
243/// use drizzle_core::expr::floor;
244///
245/// // SELECT FLOOR(users.price)
246/// let floored = floor(users.price);
247/// # "####;
248/// ```
249#[allow(clippy::type_complexity)]
250pub fn floor<'a, V, E>(
251    expr: E,
252) -> SQLExpr<
253    'a,
254    V,
255    <E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
256    E::Nullable,
257    E::Aggregate,
258>
259where
260    V: SQLParam + 'a,
261    E: Expr<'a, V>,
262    E::SQLType: RoundingPolicy<V::DialectMarker>,
263{
264    SQLExpr::new(SQL::func("FLOOR", expr.into_sql()))
265}
266
267/// TRUNC - truncates a number towards zero.
268///
269/// Returns a dialect-aware float type, preserves nullability.
270///
271/// # Example
272///
273/// ```rust
274/// # let _ = r####"
275/// use drizzle_core::expr::trunc;
276///
277/// // SELECT TRUNC(users.price)
278/// let truncated = trunc(users.price);
279/// # "####;
280/// ```
281#[allow(clippy::type_complexity)]
282pub fn trunc<'a, V, E>(
283    expr: E,
284) -> SQLExpr<
285    'a,
286    V,
287    <E::SQLType as RoundingPolicy<V::DialectMarker>>::Output,
288    E::Nullable,
289    E::Aggregate,
290>
291where
292    V: SQLParam + 'a,
293    E: Expr<'a, V>,
294    E::SQLType: RoundingPolicy<V::DialectMarker>,
295{
296    SQLExpr::new(SQL::func("TRUNC", expr.into_sql()))
297}
298
299// =============================================================================
300// POWER AND ROOT FUNCTIONS
301// =============================================================================
302
303/// SQRT - returns the square root of a number.
304///
305/// Returns a dialect-aware double type, preserves nullability.
306///
307/// # Example
308///
309/// ```rust
310/// # let _ = r####"
311/// use drizzle_core::expr::sqrt;
312///
313/// // SELECT SQRT(users.area)
314/// let root = sqrt(users.area);
315/// # "####;
316/// ```
317pub fn sqrt<'a, V, E>(
318    expr: E,
319) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, E::Nullable, E::Aggregate>
320where
321    V: SQLParam + 'a,
322    E: Expr<'a, V>,
323    E::SQLType: Numeric,
324{
325    SQLExpr::new(SQL::func("SQRT", expr.into_sql()))
326}
327
328/// POWER - raises a number to a power.
329///
330/// Returns a dialect-aware double type. The result is nullable if either input is nullable.
331///
332/// # Example
333///
334/// ```rust
335/// # let _ = r####"
336/// use drizzle_core::expr::power;
337///
338/// // SELECT POWER(users.base, 2)
339/// let squared = power(users.base, 2);
340/// # "####;
341/// ```
342#[allow(clippy::type_complexity)]
343pub fn power<'a, V, E1, E2>(
344    base: E1,
345    exponent: E2,
346) -> SQLExpr<
347    'a,
348    V,
349    <V::DialectMarker as DialectTypes>::Double,
350    <E1::Nullable as NullOr<E2::Nullable>>::Output,
351    <E1::Aggregate as AggOr<E2::Aggregate>>::Output,
352>
353where
354    V: SQLParam + 'a,
355    E1: Expr<'a, V>,
356    E1::SQLType: Numeric,
357    E2: Expr<'a, V>,
358    E2::SQLType: Numeric,
359    E1::Nullable: NullOr<E2::Nullable>,
360    E2::Nullable: Nullability,
361    E1::Aggregate: AggOr<E2::Aggregate>,
362{
363    SQLExpr::new(SQL::func(
364        "POWER",
365        base.into_sql()
366            .push(Token::COMMA)
367            .append(exponent.into_sql()),
368    ))
369}
370
371// =============================================================================
372// LOGARITHMIC AND EXPONENTIAL FUNCTIONS
373// =============================================================================
374
375/// EXP - returns e raised to the power of the argument.
376///
377/// Returns a dialect-aware double type, preserves nullability.
378///
379/// # Example
380///
381/// ```rust
382/// # let _ = r####"
383/// use drizzle_core::expr::exp;
384///
385/// // SELECT EXP(users.rate)
386/// let exponential = exp(users.rate);
387/// # "####;
388/// ```
389pub fn exp<'a, V, E>(
390    expr: E,
391) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, E::Nullable, E::Aggregate>
392where
393    V: SQLParam + 'a,
394    E: Expr<'a, V>,
395    E::SQLType: Numeric,
396{
397    SQLExpr::new(SQL::func("EXP", expr.into_sql()))
398}
399
400/// LN - returns the natural logarithm of a number.
401///
402/// Returns a dialect-aware double type, preserves nullability.
403///
404/// # Example
405///
406/// ```rust
407/// # let _ = r####"
408/// use drizzle_core::expr::ln;
409///
410/// // SELECT LN(users.value)
411/// let natural_log = ln(users.value);
412/// # "####;
413/// ```
414pub fn ln<'a, V, E>(
415    expr: E,
416) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, E::Nullable, E::Aggregate>
417where
418    V: SQLParam + 'a,
419    E: Expr<'a, V>,
420    E::SQLType: Numeric,
421{
422    SQLExpr::new(SQL::func("LN", expr.into_sql()))
423}
424
425/// LOG10 - returns the base-10 logarithm of a number.
426///
427/// Returns a dialect-aware double type, preserves nullability.
428///
429/// # Example
430///
431/// ```rust
432/// # let _ = r####"
433/// use drizzle_core::expr::log10;
434///
435/// // SELECT LOG10(users.value)
436/// let log_base_10 = log10(users.value);
437/// # "####;
438/// ```
439pub fn log10<'a, V, E>(
440    expr: E,
441) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, E::Nullable, E::Aggregate>
442where
443    V: SQLParam + 'a,
444    E: Expr<'a, V>,
445    E::SQLType: Numeric,
446{
447    SQLExpr::new(SQL::func("LOG10", expr.into_sql()))
448}
449
450/// LOG - returns the logarithm of a number with a specified base.
451///
452/// Returns a dialect-aware double type. The result is nullable if either input is nullable.
453///
454/// # Example
455///
456/// ```rust
457/// # let _ = r####"
458/// use drizzle_core::expr::log;
459///
460/// // SELECT LOG(2, users.value)
461/// let log_base_2 = log(2, users.value);
462/// # "####;
463/// ```
464#[allow(clippy::type_complexity)]
465pub fn log<'a, V, E1, E2>(
466    base: E1,
467    value: E2,
468) -> SQLExpr<
469    'a,
470    V,
471    <V::DialectMarker as DialectTypes>::Double,
472    <E1::Nullable as NullOr<E2::Nullable>>::Output,
473    <E1::Aggregate as AggOr<E2::Aggregate>>::Output,
474>
475where
476    V: SQLParam + 'a,
477    E1: Expr<'a, V>,
478    E1::SQLType: Numeric,
479    E2: Expr<'a, V>,
480    E2::SQLType: Numeric,
481    E1::Nullable: NullOr<E2::Nullable>,
482    E2::Nullable: Nullability,
483    E1::Aggregate: AggOr<E2::Aggregate>,
484{
485    SQLExpr::new(SQL::func(
486        "LOG",
487        base.into_sql().push(Token::COMMA).append(value.into_sql()),
488    ))
489}
490
491// =============================================================================
492// SIGN AND MODULO
493// =============================================================================
494
495/// SIGN - returns the sign of a number (-1, 0, or 1).
496///
497/// Returns a Double, preserves nullability.
498///
499/// # Example
500///
501/// ```rust
502/// # let _ = r####"
503/// use drizzle_core::expr::sign;
504///
505/// // SELECT SIGN(users.balance)
506/// let balance_sign = sign(users.balance);
507/// # "####;
508/// ```
509pub fn sign<'a, V, E>(
510    expr: E,
511) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, E::Nullable, E::Aggregate>
512where
513    V: SQLParam + 'a,
514    E: Expr<'a, V>,
515    E::SQLType: Numeric,
516{
517    SQLExpr::new(SQL::func("SIGN", expr.into_sql()))
518}
519
520/// MOD - returns the remainder of division (using % operator).
521///
522/// Returns the same type as the dividend. The result is nullable if either input is nullable.
523/// Named `mod_` to avoid conflict with Rust's `mod` keyword.
524///
525/// Note: Uses the `%` operator which works on both `SQLite` and `PostgreSQL`.
526///
527/// # Example
528///
529/// ```rust
530/// # let _ = r####"
531/// use drizzle_core::expr::mod_;
532///
533/// // SELECT users.value % 3
534/// let remainder = mod_(users.value, 3);
535/// # "####;
536/// ```
537#[allow(clippy::type_complexity)]
538pub fn mod_<'a, V, E1, E2>(
539    dividend: E1,
540    divisor: E2,
541) -> SQLExpr<
542    'a,
543    V,
544    E1::SQLType,
545    <E1::Nullable as NullOr<E2::Nullable>>::Output,
546    <E1::Aggregate as AggOr<E2::Aggregate>>::Output,
547>
548where
549    V: SQLParam + 'a,
550    E1: Expr<'a, V>,
551    E1::SQLType: Numeric,
552    E2: Expr<'a, V>,
553    E2::SQLType: Numeric,
554    E1::Nullable: NullOr<E2::Nullable>,
555    E2::Nullable: Nullability,
556    E1::Aggregate: AggOr<E2::Aggregate>,
557{
558    SQLExpr::new(
559        dividend
560            .into_sql()
561            .push(Token::REM)
562            .append(divisor.into_sql()),
563    )
564}
565
566// =============================================================================
567// CONSTANTS AND RANDOM
568// =============================================================================
569
570/// PI - returns the mathematical constant pi (`PostgreSQL`).
571///
572/// # Example
573///
574/// ```rust
575/// # let _ = r####"
576/// use drizzle_core::expr::pi;
577///
578/// // SELECT PI()
579/// let pi_val = pi::<PostgresValue>();
580/// # "####;
581/// ```
582#[must_use]
583pub fn pi<'a, V>()
584-> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, super::NonNull, Scalar>
585where
586    V: SQLParam + 'a,
587    V::DialectMarker: PostgresMathSupport,
588{
589    SQLExpr::new(SQL::raw("PI()"))
590}
591
592/// RANDOM - returns a random value.
593///
594/// Return type is dialect-aware:
595/// - `SQLite`: integer in [-2^63, 2^63)
596/// - `PostgreSQL`: float in [0, 1)
597///
598/// # Example
599///
600/// ```rust
601/// # let _ = r####"
602/// use drizzle_core::expr::random;
603///
604/// // SELECT RANDOM()
605/// let rnd = random::<SQLiteValue>();
606/// # "####;
607/// ```
608#[must_use]
609pub fn random<'a, V>()
610-> SQLExpr<'a, V, <V::DialectMarker as RandomPolicy>::Random, super::NonNull, Scalar>
611where
612    V: SQLParam + 'a,
613    V::DialectMarker: RandomPolicy,
614{
615    SQLExpr::new(SQL::raw("RANDOM()"))
616}
617
618// =============================================================================
619// SQLite-only Math Functions
620// =============================================================================
621
622/// LOG2 - returns the base-2 logarithm of a number.
623///
624/// Available in `SQLite` when compiled with `SQLITE_ENABLE_MATH_FUNCTIONS`.
625/// Returns a dialect-aware double type, preserves nullability.
626///
627/// # Example
628///
629/// ```rust
630/// # let _ = r####"
631/// use drizzle_core::expr::log2;
632///
633/// // SELECT LOG2(users.value)
634/// let log_base_2 = log2(users.value);
635/// # "####;
636/// ```
637pub fn log2<'a, V, E>(
638    expr: E,
639) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Double, E::Nullable, E::Aggregate>
640where
641    V: SQLParam + 'a,
642    V::DialectMarker: SQLiteMathSupport,
643    E: Expr<'a, V>,
644    E::SQLType: Numeric,
645{
646    SQLExpr::new(SQL::func("LOG2", expr.into_sql()))
647}