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}