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::sql::{SQL, Token};
13use crate::traits::SQLParam;
14use crate::types::{Double, Numeric};
15
16use super::{Expr, NullOr, Nullability, SQLExpr, Scalar};
17
18// =============================================================================
19// ABSOLUTE VALUE
20// =============================================================================
21
22/// ABS - returns the absolute value of a number.
23///
24/// Preserves the SQL type and nullability of the input expression.
25///
26/// # Type Safety
27///
28/// ```ignore
29/// // ✅ OK: Int column
30/// abs(users.balance);
31///
32/// // ❌ Compile error: Text is not Numeric
33/// abs(users.name);
34/// ```
35pub fn abs<'a, V, E>(expr: E) -> SQLExpr<'a, V, E::SQLType, E::Nullable, Scalar>
36where
37    V: SQLParam + 'a,
38    E: Expr<'a, V>,
39    E::SQLType: Numeric,
40{
41    SQLExpr::new(SQL::func("ABS", expr.into_sql()))
42}
43
44// =============================================================================
45// ROUNDING FUNCTIONS
46// =============================================================================
47
48/// ROUND - rounds a number to the nearest integer (or specified precision).
49///
50/// Returns Double type, preserves nullability.
51///
52/// # Example
53///
54/// ```ignore
55/// use drizzle_core::expr::round;
56///
57/// // SELECT ROUND(users.price)
58/// let rounded = round(users.price);
59/// ```
60pub fn round<'a, V, E>(expr: E) -> SQLExpr<'a, V, Double, E::Nullable, Scalar>
61where
62    V: SQLParam + 'a,
63    E: Expr<'a, V>,
64    E::SQLType: Numeric,
65{
66    SQLExpr::new(SQL::func("ROUND", expr.into_sql()))
67}
68
69/// ROUND with precision - rounds a number to specified decimal places.
70///
71/// Returns Double type, preserves nullability of the input expression.
72///
73/// # Example
74///
75/// ```ignore
76/// use drizzle_core::expr::round_to;
77///
78/// // SELECT ROUND(users.price, 2)
79/// let rounded = round_to(users.price, 2);
80/// ```
81pub fn round_to<'a, V, E, P>(expr: E, precision: P) -> SQLExpr<'a, V, Double, E::Nullable, Scalar>
82where
83    V: SQLParam + 'a,
84    E: Expr<'a, V>,
85    E::SQLType: Numeric,
86    P: Expr<'a, V>,
87{
88    SQLExpr::new(SQL::func(
89        "ROUND",
90        expr.into_sql()
91            .push(Token::COMMA)
92            .append(precision.into_sql()),
93    ))
94}
95
96/// CEIL / CEILING - rounds a number up to the nearest integer.
97///
98/// Returns Double type, preserves nullability.
99///
100/// # Example
101///
102/// ```ignore
103/// use drizzle_core::expr::ceil;
104///
105/// // SELECT CEIL(users.price)
106/// let ceiling = ceil(users.price);
107/// ```
108pub fn ceil<'a, V, E>(expr: E) -> SQLExpr<'a, V, Double, E::Nullable, Scalar>
109where
110    V: SQLParam + 'a,
111    E: Expr<'a, V>,
112    E::SQLType: Numeric,
113{
114    SQLExpr::new(SQL::func("CEIL", expr.into_sql()))
115}
116
117/// FLOOR - rounds a number down to the nearest integer.
118///
119/// Returns Double type, preserves nullability.
120///
121/// # Example
122///
123/// ```ignore
124/// use drizzle_core::expr::floor;
125///
126/// // SELECT FLOOR(users.price)
127/// let floored = floor(users.price);
128/// ```
129pub fn floor<'a, V, E>(expr: E) -> SQLExpr<'a, V, Double, E::Nullable, Scalar>
130where
131    V: SQLParam + 'a,
132    E: Expr<'a, V>,
133    E::SQLType: Numeric,
134{
135    SQLExpr::new(SQL::func("FLOOR", expr.into_sql()))
136}
137
138/// TRUNC - truncates a number towards zero.
139///
140/// Returns Double type, preserves nullability.
141///
142/// # Example
143///
144/// ```ignore
145/// use drizzle_core::expr::trunc;
146///
147/// // SELECT TRUNC(users.price)
148/// let truncated = trunc(users.price);
149/// ```
150pub fn trunc<'a, V, E>(expr: E) -> SQLExpr<'a, V, Double, E::Nullable, Scalar>
151where
152    V: SQLParam + 'a,
153    E: Expr<'a, V>,
154    E::SQLType: Numeric,
155{
156    SQLExpr::new(SQL::func("TRUNC", expr.into_sql()))
157}
158
159// =============================================================================
160// POWER AND ROOT FUNCTIONS
161// =============================================================================
162
163/// SQRT - returns the square root of a number.
164///
165/// Returns Double type, preserves nullability.
166///
167/// # Example
168///
169/// ```ignore
170/// use drizzle_core::expr::sqrt;
171///
172/// // SELECT SQRT(users.area)
173/// let root = sqrt(users.area);
174/// ```
175pub fn sqrt<'a, V, E>(expr: E) -> SQLExpr<'a, V, Double, E::Nullable, Scalar>
176where
177    V: SQLParam + 'a,
178    E: Expr<'a, V>,
179    E::SQLType: Numeric,
180{
181    SQLExpr::new(SQL::func("SQRT", expr.into_sql()))
182}
183
184/// POWER - raises a number to a power.
185///
186/// Returns Double type. The result is nullable if either input is nullable.
187///
188/// # Example
189///
190/// ```ignore
191/// use drizzle_core::expr::power;
192///
193/// // SELECT POWER(users.base, 2)
194/// let squared = power(users.base, 2);
195/// ```
196pub fn power<'a, V, E1, E2>(
197    base: E1,
198    exponent: E2,
199) -> SQLExpr<'a, V, Double, <E1::Nullable as NullOr<E2::Nullable>>::Output, Scalar>
200where
201    V: SQLParam + 'a,
202    E1: Expr<'a, V>,
203    E1::SQLType: Numeric,
204    E2: Expr<'a, V>,
205    E2::SQLType: Numeric,
206    E1::Nullable: NullOr<E2::Nullable>,
207    E2::Nullable: Nullability,
208{
209    SQLExpr::new(SQL::func(
210        "POWER",
211        base.into_sql()
212            .push(Token::COMMA)
213            .append(exponent.into_sql()),
214    ))
215}
216
217// =============================================================================
218// LOGARITHMIC AND EXPONENTIAL FUNCTIONS
219// =============================================================================
220
221/// EXP - returns e raised to the power of the argument.
222///
223/// Returns Double type, preserves nullability.
224///
225/// # Example
226///
227/// ```ignore
228/// use drizzle_core::expr::exp;
229///
230/// // SELECT EXP(users.rate)
231/// let exponential = exp(users.rate);
232/// ```
233pub fn exp<'a, V, E>(expr: E) -> SQLExpr<'a, V, Double, E::Nullable, Scalar>
234where
235    V: SQLParam + 'a,
236    E: Expr<'a, V>,
237    E::SQLType: Numeric,
238{
239    SQLExpr::new(SQL::func("EXP", expr.into_sql()))
240}
241
242/// LN - returns the natural logarithm of a number.
243///
244/// Returns Double type, preserves nullability.
245///
246/// # Example
247///
248/// ```ignore
249/// use drizzle_core::expr::ln;
250///
251/// // SELECT LN(users.value)
252/// let natural_log = ln(users.value);
253/// ```
254pub fn ln<'a, V, E>(expr: E) -> SQLExpr<'a, V, Double, E::Nullable, Scalar>
255where
256    V: SQLParam + 'a,
257    E: Expr<'a, V>,
258    E::SQLType: Numeric,
259{
260    SQLExpr::new(SQL::func("LN", expr.into_sql()))
261}
262
263/// LOG10 - returns the base-10 logarithm of a number.
264///
265/// Returns Double type, preserves nullability.
266///
267/// # Example
268///
269/// ```ignore
270/// use drizzle_core::expr::log10;
271///
272/// // SELECT LOG10(users.value)
273/// let log_base_10 = log10(users.value);
274/// ```
275pub fn log10<'a, V, E>(expr: E) -> SQLExpr<'a, V, Double, E::Nullable, Scalar>
276where
277    V: SQLParam + 'a,
278    E: Expr<'a, V>,
279    E::SQLType: Numeric,
280{
281    SQLExpr::new(SQL::func("LOG10", expr.into_sql()))
282}
283
284/// LOG - returns the logarithm of a number with a specified base.
285///
286/// Returns Double type. The result is nullable if either input is nullable.
287///
288/// # Example
289///
290/// ```ignore
291/// use drizzle_core::expr::log;
292///
293/// // SELECT LOG(2, users.value)
294/// let log_base_2 = log(2, users.value);
295/// ```
296pub fn log<'a, V, E1, E2>(
297    base: E1,
298    value: E2,
299) -> SQLExpr<'a, V, Double, <E1::Nullable as NullOr<E2::Nullable>>::Output, Scalar>
300where
301    V: SQLParam + 'a,
302    E1: Expr<'a, V>,
303    E1::SQLType: Numeric,
304    E2: Expr<'a, V>,
305    E2::SQLType: Numeric,
306    E1::Nullable: NullOr<E2::Nullable>,
307    E2::Nullable: Nullability,
308{
309    SQLExpr::new(SQL::func(
310        "LOG",
311        base.into_sql().push(Token::COMMA).append(value.into_sql()),
312    ))
313}
314
315// =============================================================================
316// SIGN AND MODULO
317// =============================================================================
318
319/// SIGN - returns the sign of a number (-1, 0, or 1).
320///
321/// Returns the same numeric type, preserves nullability.
322///
323/// # Example
324///
325/// ```ignore
326/// use drizzle_core::expr::sign;
327///
328/// // SELECT SIGN(users.balance)
329/// let balance_sign = sign(users.balance);
330/// ```
331pub fn sign<'a, V, E>(expr: E) -> SQLExpr<'a, V, E::SQLType, E::Nullable, Scalar>
332where
333    V: SQLParam + 'a,
334    E: Expr<'a, V>,
335    E::SQLType: Numeric,
336{
337    SQLExpr::new(SQL::func("SIGN", expr.into_sql()))
338}
339
340/// MOD - returns the remainder of division (using % operator).
341///
342/// Returns the same type as the dividend. The result is nullable if either input is nullable.
343/// Named `mod_` to avoid conflict with Rust's `mod` keyword.
344///
345/// Note: Uses the `%` operator which works on both SQLite and PostgreSQL.
346///
347/// # Example
348///
349/// ```ignore
350/// use drizzle_core::expr::mod_;
351///
352/// // SELECT users.value % 3
353/// let remainder = mod_(users.value, 3);
354/// ```
355#[allow(clippy::type_complexity)]
356pub fn mod_<'a, V, E1, E2>(
357    dividend: E1,
358    divisor: E2,
359) -> SQLExpr<'a, V, E1::SQLType, <E1::Nullable as NullOr<E2::Nullable>>::Output, Scalar>
360where
361    V: SQLParam + 'a,
362    E1: Expr<'a, V>,
363    E1::SQLType: Numeric,
364    E2: Expr<'a, V>,
365    E2::SQLType: Numeric,
366    E1::Nullable: NullOr<E2::Nullable>,
367    E2::Nullable: Nullability,
368{
369    SQLExpr::new(
370        dividend
371            .into_sql()
372            .push(Token::REM)
373            .append(divisor.into_sql()),
374    )
375}