Skip to main content

fsqlite_func/
math.rs

1//! SQLite 3.35+ math functions (§13.2).
2//!
3//! All 30 math functions: trig, hyperbolic, rounding, log/exp, and misc.
4//! Always included in FrankenSQLite (no compile flag needed).
5//!
6//! # NaN / Inf semantics
7//! - NaN results are normalized to NULL.
8//! - +Inf / -Inf are valid REAL values and propagate.
9//! - Division by zero (mod) returns NULL.
10#![allow(
11    clippy::unnecessary_literal_bound,
12    clippy::too_many_lines,
13    clippy::cast_possible_truncation,
14    clippy::cast_possible_wrap,
15    clippy::suboptimal_flops,
16    clippy::unnecessary_wraps,
17    clippy::match_same_arms,
18    clippy::items_after_statements,
19    clippy::float_cmp
20)]
21
22use fsqlite_error::Result;
23use fsqlite_types::SqliteValue;
24
25use crate::{FunctionRegistry, ScalarFunction};
26
27// ── Helpers ───────────────────────────────────────────────────────────────
28
29/// Coerce a `SqliteValue` to `f64`. Returns `None` for `Null`, `Blob`, and for
30/// non-numeric text (SQLite math functions return NULL for these cases,
31/// per `sqlite3_value_numeric_type()` semantics in C SQLite).
32fn to_f64(v: &SqliteValue) -> Option<f64> {
33    match v {
34        SqliteValue::Null => None,
35        SqliteValue::Integer(i) => Some(*i as f64),
36        SqliteValue::Float(f) => Some(*f),
37        // SQLite trims leading/trailing whitespace via sqlite3AtoF before
38        // numeric conversion.  Non-numeric text produces NULL (not 0.0).
39        SqliteValue::Text(s) => {
40            let trimmed = s.trim();
41            // Reject non-finite (NaN/Inf) — sqlite3AtoF doesn't recognize them.
42            trimmed.parse::<f64>().ok().filter(|f| f.is_finite())
43        }
44        SqliteValue::Blob(_) => None,
45    }
46}
47
48/// Wrap an `f64` result: NaN → NULL, otherwise Float.
49fn wrap(v: f64) -> SqliteValue {
50    if v.is_nan() {
51        SqliteValue::Null
52    } else {
53        SqliteValue::Float(v)
54    }
55}
56
57/// One-arg math function that returns NULL on domain error (NaN).
58fn unary_math(args: &[SqliteValue], f: fn(f64) -> f64) -> Result<SqliteValue> {
59    let Some(x) = to_f64(&args[0]) else {
60        return Ok(SqliteValue::Null);
61    };
62    Ok(wrap(f(x)))
63}
64
65/// One-arg math with explicit domain check.  Returns NULL if `domain` is false.
66fn unary_domain(
67    args: &[SqliteValue],
68    domain: fn(f64) -> bool,
69    f: fn(f64) -> f64,
70) -> Result<SqliteValue> {
71    let Some(x) = to_f64(&args[0]) else {
72        return Ok(SqliteValue::Null);
73    };
74    if !domain(x) {
75        return Ok(SqliteValue::Null);
76    }
77    Ok(wrap(f(x)))
78}
79
80// ── Trigonometric ─────────────────────────────────────────────────────────
81
82pub struct AcosFunc;
83
84impl ScalarFunction for AcosFunc {
85    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
86        unary_domain(args, |x| (-1.0..=1.0).contains(&x), f64::acos)
87    }
88
89    fn num_args(&self) -> i32 {
90        1
91    }
92
93    fn name(&self) -> &str {
94        "acos"
95    }
96}
97
98pub struct AsinFunc;
99
100impl ScalarFunction for AsinFunc {
101    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
102        unary_domain(args, |x| (-1.0..=1.0).contains(&x), f64::asin)
103    }
104
105    fn num_args(&self) -> i32 {
106        1
107    }
108
109    fn name(&self) -> &str {
110        "asin"
111    }
112}
113
114pub struct AtanFunc;
115
116impl ScalarFunction for AtanFunc {
117    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
118        unary_math(args, f64::atan)
119    }
120
121    fn num_args(&self) -> i32 {
122        1
123    }
124
125    fn name(&self) -> &str {
126        "atan"
127    }
128}
129
130pub struct Atan2Func;
131
132impl ScalarFunction for Atan2Func {
133    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
134        let Some(y) = to_f64(&args[0]) else {
135            return Ok(SqliteValue::Null);
136        };
137        let Some(x) = to_f64(&args[1]) else {
138            return Ok(SqliteValue::Null);
139        };
140        Ok(wrap(y.atan2(x)))
141    }
142
143    fn num_args(&self) -> i32 {
144        2
145    }
146
147    fn name(&self) -> &str {
148        "atan2"
149    }
150}
151
152pub struct CosFunc;
153
154impl ScalarFunction for CosFunc {
155    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
156        unary_math(args, f64::cos)
157    }
158
159    fn num_args(&self) -> i32 {
160        1
161    }
162
163    fn name(&self) -> &str {
164        "cos"
165    }
166}
167
168pub struct SinFunc;
169
170impl ScalarFunction for SinFunc {
171    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
172        unary_math(args, f64::sin)
173    }
174
175    fn num_args(&self) -> i32 {
176        1
177    }
178
179    fn name(&self) -> &str {
180        "sin"
181    }
182}
183
184pub struct TanFunc;
185
186impl ScalarFunction for TanFunc {
187    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
188        unary_math(args, f64::tan)
189    }
190
191    fn num_args(&self) -> i32 {
192        1
193    }
194
195    fn name(&self) -> &str {
196        "tan"
197    }
198}
199
200// ── Hyperbolic ────────────────────────────────────────────────────────────
201
202pub struct AcoshFunc;
203
204impl ScalarFunction for AcoshFunc {
205    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
206        unary_domain(args, |x| x >= 1.0, f64::acosh)
207    }
208
209    fn num_args(&self) -> i32 {
210        1
211    }
212
213    fn name(&self) -> &str {
214        "acosh"
215    }
216}
217
218pub struct AsinhFunc;
219
220impl ScalarFunction for AsinhFunc {
221    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
222        unary_math(args, f64::asinh)
223    }
224
225    fn num_args(&self) -> i32 {
226        1
227    }
228
229    fn name(&self) -> &str {
230        "asinh"
231    }
232}
233
234pub struct AtanhFunc;
235
236impl ScalarFunction for AtanhFunc {
237    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
238        // Domain: (-1, 1) — open interval, atanh(1) and atanh(-1) are ±Inf
239        // but C sqlite returns NULL for these edge cases.
240        unary_domain(args, |x| x > -1.0 && x < 1.0, f64::atanh)
241    }
242
243    fn num_args(&self) -> i32 {
244        1
245    }
246
247    fn name(&self) -> &str {
248        "atanh"
249    }
250}
251
252pub struct CoshFunc;
253
254impl ScalarFunction for CoshFunc {
255    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
256        unary_math(args, f64::cosh)
257    }
258
259    fn num_args(&self) -> i32 {
260        1
261    }
262
263    fn name(&self) -> &str {
264        "cosh"
265    }
266}
267
268pub struct SinhFunc;
269
270impl ScalarFunction for SinhFunc {
271    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
272        unary_math(args, f64::sinh)
273    }
274
275    fn num_args(&self) -> i32 {
276        1
277    }
278
279    fn name(&self) -> &str {
280        "sinh"
281    }
282}
283
284pub struct TanhFunc;
285
286impl ScalarFunction for TanhFunc {
287    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
288        unary_math(args, f64::tanh)
289    }
290
291    fn num_args(&self) -> i32 {
292        1
293    }
294
295    fn name(&self) -> &str {
296        "tanh"
297    }
298}
299
300// ── Rounding ──────────────────────────────────────────────────────────────
301//
302// ceil/floor/trunc preserve INTEGER type for INTEGER input.
303
304pub struct CeilFunc;
305
306impl ScalarFunction for CeilFunc {
307    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
308        match &args[0] {
309            SqliteValue::Null => Ok(SqliteValue::Null),
310            SqliteValue::Integer(i) => Ok(SqliteValue::Integer(*i)),
311            other => {
312                let Some(x) = to_f64(other) else {
313                    return Ok(SqliteValue::Null);
314                };
315                Ok(wrap(x.ceil()))
316            }
317        }
318    }
319
320    fn num_args(&self) -> i32 {
321        1
322    }
323
324    fn name(&self) -> &str {
325        "ceil"
326    }
327}
328
329pub struct FloorFunc;
330
331impl ScalarFunction for FloorFunc {
332    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
333        match &args[0] {
334            SqliteValue::Null => Ok(SqliteValue::Null),
335            SqliteValue::Integer(i) => Ok(SqliteValue::Integer(*i)),
336            other => {
337                let Some(x) = to_f64(other) else {
338                    return Ok(SqliteValue::Null);
339                };
340                Ok(wrap(x.floor()))
341            }
342        }
343    }
344
345    fn num_args(&self) -> i32 {
346        1
347    }
348
349    fn name(&self) -> &str {
350        "floor"
351    }
352}
353
354pub struct TruncFunc;
355
356impl ScalarFunction for TruncFunc {
357    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
358        match &args[0] {
359            SqliteValue::Null => Ok(SqliteValue::Null),
360            SqliteValue::Integer(i) => Ok(SqliteValue::Integer(*i)),
361            other => {
362                let Some(x) = to_f64(other) else {
363                    return Ok(SqliteValue::Null);
364                };
365                Ok(wrap(x.trunc()))
366            }
367        }
368    }
369
370    fn num_args(&self) -> i32 {
371        1
372    }
373
374    fn name(&self) -> &str {
375        "trunc"
376    }
377}
378
379// ── Logarithmic / Exponential ─────────────────────────────────────────────
380
381pub struct LnFunc;
382
383impl ScalarFunction for LnFunc {
384    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
385        unary_domain(args, |x| x > 0.0, f64::ln)
386    }
387
388    fn num_args(&self) -> i32 {
389        1
390    }
391
392    fn name(&self) -> &str {
393        "ln"
394    }
395}
396
397/// `log(X)` — base-10 logarithm (single arg).
398pub struct Log10Func;
399
400impl ScalarFunction for Log10Func {
401    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
402        unary_domain(args, |x| x > 0.0, f64::log10)
403    }
404
405    fn num_args(&self) -> i32 {
406        1
407    }
408
409    fn name(&self) -> &str {
410        "log10"
411    }
412}
413
414/// `log(X)` with 1 arg = base-10.  `log(B, X)` with 2 args = base-B.
415pub struct LogFunc;
416
417impl ScalarFunction for LogFunc {
418    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
419        if args.len() == 1 {
420            // log(X) = log10(X)
421            return Log10Func.invoke(args);
422        }
423        // log(B, X) = ln(X) / ln(B)
424        let Some(b) = to_f64(&args[0]) else {
425            return Ok(SqliteValue::Null);
426        };
427        let Some(x) = to_f64(&args[1]) else {
428            return Ok(SqliteValue::Null);
429        };
430        if b <= 0.0 || b == 1.0 || x <= 0.0 {
431            return Ok(SqliteValue::Null);
432        }
433        Ok(wrap(x.ln() / b.ln()))
434    }
435
436    fn num_args(&self) -> i32 {
437        -1 // variadic: 1 or 2 args
438    }
439
440    fn name(&self) -> &str {
441        "log"
442    }
443}
444
445pub struct Log2Func;
446
447impl ScalarFunction for Log2Func {
448    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
449        unary_domain(args, |x| x > 0.0, f64::log2)
450    }
451
452    fn num_args(&self) -> i32 {
453        1
454    }
455
456    fn name(&self) -> &str {
457        "log2"
458    }
459}
460
461pub struct ExpFunc;
462
463impl ScalarFunction for ExpFunc {
464    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
465        // exp overflow produces +Inf (valid REAL), so no domain check needed.
466        unary_math(args, f64::exp)
467    }
468
469    fn num_args(&self) -> i32 {
470        1
471    }
472
473    fn name(&self) -> &str {
474        "exp"
475    }
476}
477
478pub struct PowFunc;
479
480impl ScalarFunction for PowFunc {
481    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
482        let Some(x) = to_f64(&args[0]) else {
483            return Ok(SqliteValue::Null);
484        };
485        let Some(y) = to_f64(&args[1]) else {
486            return Ok(SqliteValue::Null);
487        };
488        Ok(wrap(x.powf(y)))
489    }
490
491    fn num_args(&self) -> i32 {
492        2
493    }
494
495    fn name(&self) -> &str {
496        "pow"
497    }
498}
499
500pub struct SqrtFunc;
501
502impl ScalarFunction for SqrtFunc {
503    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
504        unary_domain(args, |x| x >= 0.0, f64::sqrt)
505    }
506
507    fn num_args(&self) -> i32 {
508        1
509    }
510
511    fn name(&self) -> &str {
512        "sqrt"
513    }
514}
515
516// ── Other ─────────────────────────────────────────────────────────────────
517
518pub struct DegreesFunc;
519
520impl ScalarFunction for DegreesFunc {
521    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
522        unary_math(args, f64::to_degrees)
523    }
524
525    fn num_args(&self) -> i32 {
526        1
527    }
528
529    fn name(&self) -> &str {
530        "degrees"
531    }
532}
533
534pub struct RadiansFunc;
535
536impl ScalarFunction for RadiansFunc {
537    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
538        unary_math(args, f64::to_radians)
539    }
540
541    fn num_args(&self) -> i32 {
542        1
543    }
544
545    fn name(&self) -> &str {
546        "radians"
547    }
548}
549
550pub struct ModFunc;
551
552impl ScalarFunction for ModFunc {
553    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
554        let Some(x) = to_f64(&args[0]) else {
555            return Ok(SqliteValue::Null);
556        };
557        let Some(y) = to_f64(&args[1]) else {
558            return Ok(SqliteValue::Null);
559        };
560        if y == 0.0 {
561            return Ok(SqliteValue::Null);
562        }
563        Ok(wrap(x % y))
564    }
565
566    fn num_args(&self) -> i32 {
567        2
568    }
569
570    fn name(&self) -> &str {
571        "mod"
572    }
573}
574
575pub struct PiFunc;
576
577impl ScalarFunction for PiFunc {
578    fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
579        Ok(SqliteValue::Float(std::f64::consts::PI))
580    }
581
582    fn num_args(&self) -> i32 {
583        0
584    }
585
586    fn name(&self) -> &str {
587        "pi"
588    }
589}
590
591// ── Registration ──────────────────────────────────────────────────────────
592
593/// Register all §13.2 math functions into the given registry.
594pub fn register_math_builtins(registry: &mut FunctionRegistry) {
595    // Trigonometric
596    registry.register_scalar(AcosFunc);
597    registry.register_scalar(AsinFunc);
598    registry.register_scalar(AtanFunc);
599    registry.register_scalar(Atan2Func);
600    registry.register_scalar(CosFunc);
601    registry.register_scalar(SinFunc);
602    registry.register_scalar(TanFunc);
603
604    // Hyperbolic
605    registry.register_scalar(AcoshFunc);
606    registry.register_scalar(AsinhFunc);
607    registry.register_scalar(AtanhFunc);
608    registry.register_scalar(CoshFunc);
609    registry.register_scalar(SinhFunc);
610    registry.register_scalar(TanhFunc);
611
612    // Rounding
613    registry.register_scalar(CeilFunc);
614    registry.register_scalar(FloorFunc);
615    registry.register_scalar(TruncFunc);
616
617    // Logarithmic / Exponential
618    registry.register_scalar(LnFunc);
619    registry.register_scalar(LogFunc);
620    registry.register_scalar(Log10Func);
621    registry.register_scalar(Log2Func);
622    registry.register_scalar(ExpFunc);
623    registry.register_scalar(PowFunc);
624    registry.register_scalar(SqrtFunc);
625
626    // Other
627    registry.register_scalar(DegreesFunc);
628    registry.register_scalar(RadiansFunc);
629    registry.register_scalar(ModFunc);
630    registry.register_scalar(PiFunc);
631
632    // Aliases
633    // "ceiling" → same as "ceil"
634    struct CeilingFunc;
635    impl ScalarFunction for CeilingFunc {
636        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
637            CeilFunc.invoke(args)
638        }
639
640        fn num_args(&self) -> i32 {
641            1
642        }
643
644        fn name(&self) -> &str {
645            "ceiling"
646        }
647    }
648    registry.register_scalar(CeilingFunc);
649
650    // "power" → same as "pow"
651    struct PowerFunc;
652    impl ScalarFunction for PowerFunc {
653        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
654            PowFunc.invoke(args)
655        }
656
657        fn num_args(&self) -> i32 {
658            2
659        }
660
661        fn name(&self) -> &str {
662            "power"
663        }
664    }
665    registry.register_scalar(PowerFunc);
666}
667
668// ── Tests ─────────────────────────────────────────────────────────────────
669
670#[cfg(test)]
671mod tests {
672    use super::*;
673
674    // Tolerance for floating-point comparisons.
675    const EPS: f64 = 1e-12;
676
677    fn assert_float_eq(result: &SqliteValue, expected: f64) {
678        match result {
679            SqliteValue::Float(v) => {
680                assert!((v - expected).abs() < EPS, "expected {expected}, got {v}");
681            }
682            other => panic!("expected Float({expected}), got {other:?}"),
683        }
684    }
685
686    fn assert_null(result: &SqliteValue) {
687        assert_eq!(result, &SqliteValue::Null, "expected NULL");
688    }
689
690    fn float(v: f64) -> SqliteValue {
691        SqliteValue::Float(v)
692    }
693
694    fn int(v: i64) -> SqliteValue {
695        SqliteValue::Integer(v)
696    }
697
698    fn null() -> SqliteValue {
699        SqliteValue::Null
700    }
701
702    // ── Trigonometric ─────────────────────────────────────────────────
703
704    #[test]
705    fn test_acos_valid() {
706        let r = AcosFunc.invoke(&[float(0.5)]).unwrap();
707        assert_float_eq(&r, 0.5_f64.acos());
708    }
709
710    #[test]
711    fn test_acos_domain_error() {
712        assert_null(&AcosFunc.invoke(&[float(2.0)]).unwrap());
713    }
714
715    #[test]
716    fn test_acos_null() {
717        assert_null(&AcosFunc.invoke(&[null()]).unwrap());
718    }
719
720    #[test]
721    fn test_acosh_valid() {
722        let r = AcoshFunc.invoke(&[float(2.0)]).unwrap();
723        assert_float_eq(&r, 2.0_f64.acosh());
724    }
725
726    #[test]
727    fn test_acosh_domain_error() {
728        assert_null(&AcoshFunc.invoke(&[float(0.5)]).unwrap());
729    }
730
731    #[test]
732    fn test_asin_valid() {
733        let r = AsinFunc.invoke(&[float(0.5)]).unwrap();
734        assert_float_eq(&r, 0.5_f64.asin());
735    }
736
737    #[test]
738    fn test_asin_domain_error() {
739        assert_null(&AsinFunc.invoke(&[float(2.0)]).unwrap());
740    }
741
742    #[test]
743    fn test_asinh_all_reals() {
744        let r = AsinhFunc.invoke(&[float(1.0)]).unwrap();
745        assert_float_eq(&r, 1.0_f64.asinh());
746    }
747
748    #[test]
749    fn test_atan_basic() {
750        let r = AtanFunc.invoke(&[float(1.0)]).unwrap();
751        assert_float_eq(&r, std::f64::consts::FRAC_PI_4);
752    }
753
754    #[test]
755    fn test_atan2_quadrants() {
756        // Q1: atan2(1, 1) ≈ π/4
757        let r = Atan2Func.invoke(&[float(1.0), float(1.0)]).unwrap();
758        assert_float_eq(&r, std::f64::consts::FRAC_PI_4);
759
760        // Q2: atan2(1, -1) ≈ 3π/4
761        let r = Atan2Func.invoke(&[float(1.0), float(-1.0)]).unwrap();
762        assert_float_eq(&r, 3.0 * std::f64::consts::FRAC_PI_4);
763
764        // Q3: atan2(-1, -1) ≈ -3π/4
765        let r = Atan2Func.invoke(&[float(-1.0), float(-1.0)]).unwrap();
766        assert_float_eq(&r, -3.0 * std::f64::consts::FRAC_PI_4);
767
768        // Q4: atan2(-1, 1) ≈ -π/4
769        let r = Atan2Func.invoke(&[float(-1.0), float(1.0)]).unwrap();
770        assert_float_eq(&r, -std::f64::consts::FRAC_PI_4);
771    }
772
773    #[test]
774    fn test_atanh_valid() {
775        let r = AtanhFunc.invoke(&[float(0.5)]).unwrap();
776        assert_float_eq(&r, 0.5_f64.atanh());
777    }
778
779    #[test]
780    fn test_atanh_domain_error() {
781        // atanh(1.0) is outside the open interval (-1, 1)
782        assert_null(&AtanhFunc.invoke(&[float(1.0)]).unwrap());
783        assert_null(&AtanhFunc.invoke(&[float(-1.0)]).unwrap());
784    }
785
786    #[test]
787    fn test_cos_zero() {
788        let r = CosFunc.invoke(&[float(0.0)]).unwrap();
789        assert_float_eq(&r, 1.0);
790    }
791
792    #[test]
793    fn test_cosh_zero() {
794        let r = CoshFunc.invoke(&[float(0.0)]).unwrap();
795        assert_float_eq(&r, 1.0);
796    }
797
798    #[test]
799    fn test_sin_zero() {
800        let r = SinFunc.invoke(&[float(0.0)]).unwrap();
801        assert_float_eq(&r, 0.0);
802    }
803
804    #[test]
805    fn test_sinh_zero() {
806        let r = SinhFunc.invoke(&[float(0.0)]).unwrap();
807        assert_float_eq(&r, 0.0);
808    }
809
810    #[test]
811    fn test_tan_zero() {
812        let r = TanFunc.invoke(&[float(0.0)]).unwrap();
813        assert_float_eq(&r, 0.0);
814    }
815
816    #[test]
817    fn test_tanh_zero() {
818        let r = TanhFunc.invoke(&[float(0.0)]).unwrap();
819        assert_float_eq(&r, 0.0);
820    }
821
822    // ── Rounding ──────────────────────────────────────────────────────
823
824    #[test]
825    fn test_ceil_real() {
826        let r = CeilFunc.invoke(&[float(1.2)]).unwrap();
827        assert_float_eq(&r, 2.0);
828    }
829
830    #[test]
831    fn test_ceil_integer_type() {
832        let r = CeilFunc.invoke(&[int(5)]).unwrap();
833        assert_eq!(r, SqliteValue::Integer(5));
834    }
835
836    #[test]
837    fn test_ceil_negative() {
838        let r = CeilFunc.invoke(&[float(-1.2)]).unwrap();
839        assert_float_eq(&r, -1.0);
840    }
841
842    #[test]
843    fn test_floor_real() {
844        let r = FloorFunc.invoke(&[float(1.7)]).unwrap();
845        assert_float_eq(&r, 1.0);
846    }
847
848    #[test]
849    fn test_floor_integer_type() {
850        let r = FloorFunc.invoke(&[int(5)]).unwrap();
851        assert_eq!(r, SqliteValue::Integer(5));
852    }
853
854    #[test]
855    fn test_floor_negative() {
856        let r = FloorFunc.invoke(&[float(-1.2)]).unwrap();
857        assert_float_eq(&r, -2.0);
858    }
859
860    #[test]
861    fn test_trunc_positive() {
862        let r = TruncFunc.invoke(&[float(2.9)]).unwrap();
863        assert_float_eq(&r, 2.0);
864    }
865
866    #[test]
867    fn test_trunc_negative() {
868        let r = TruncFunc.invoke(&[float(-2.9)]).unwrap();
869        assert_float_eq(&r, -2.0);
870    }
871
872    #[test]
873    fn test_trunc_integer_type() {
874        let r = TruncFunc.invoke(&[int(5)]).unwrap();
875        assert_eq!(r, SqliteValue::Integer(5));
876    }
877
878    // ── Logarithmic / Exponential ─────────────────────────────────────
879
880    #[test]
881    fn test_ln_positive() {
882        let r = LnFunc.invoke(&[float(std::f64::consts::E)]).unwrap();
883        assert_float_eq(&r, 1.0);
884    }
885
886    #[test]
887    fn test_ln_zero() {
888        assert_null(&LnFunc.invoke(&[float(0.0)]).unwrap());
889    }
890
891    #[test]
892    fn test_ln_negative() {
893        assert_null(&LnFunc.invoke(&[float(-1.0)]).unwrap());
894    }
895
896    #[test]
897    fn test_log_single_arg_base10() {
898        let r = LogFunc.invoke(&[float(100.0)]).unwrap();
899        assert_float_eq(&r, 2.0);
900    }
901
902    #[test]
903    fn test_log_two_arg_base() {
904        let r = LogFunc.invoke(&[float(2.0), float(8.0)]).unwrap();
905        assert_float_eq(&r, 3.0);
906    }
907
908    #[test]
909    fn test_log10_alias() {
910        let r = Log10Func.invoke(&[float(1000.0)]).unwrap();
911        assert_float_eq(&r, 3.0);
912    }
913
914    #[test]
915    fn test_log2_basic() {
916        let r = Log2Func.invoke(&[float(8.0)]).unwrap();
917        assert_float_eq(&r, 3.0);
918    }
919
920    #[test]
921    fn test_exp_one() {
922        let r = ExpFunc.invoke(&[float(1.0)]).unwrap();
923        assert_float_eq(&r, std::f64::consts::E);
924    }
925
926    #[test]
927    fn test_exp_overflow() {
928        let r = ExpFunc.invoke(&[float(1000.0)]).unwrap();
929        match r {
930            SqliteValue::Float(v) => assert!(v.is_infinite() && v > 0.0, "+Inf expected"),
931            other => panic!("expected +Inf Float, got {other:?}"),
932        }
933    }
934
935    #[test]
936    fn test_pow_basic() {
937        let r = PowFunc.invoke(&[float(2.0), float(10.0)]).unwrap();
938        assert_float_eq(&r, 1024.0);
939    }
940
941    #[test]
942    fn test_pow_zero_zero() {
943        let r = PowFunc.invoke(&[float(0.0), float(0.0)]).unwrap();
944        assert_float_eq(&r, 1.0);
945    }
946
947    #[test]
948    fn test_power_alias() {
949        // power is registered via register_math_builtins as an alias.
950        // Test the underlying PowFunc directly.
951        let r = PowFunc.invoke(&[float(3.0), float(2.0)]).unwrap();
952        assert_float_eq(&r, 9.0);
953    }
954
955    #[test]
956    fn test_sqrt_positive() {
957        let r = SqrtFunc.invoke(&[float(144.0)]).unwrap();
958        assert_float_eq(&r, 12.0);
959    }
960
961    #[test]
962    fn test_sqrt_negative() {
963        assert_null(&SqrtFunc.invoke(&[float(-1.0)]).unwrap());
964    }
965
966    // ── Other ─────────────────────────────────────────────────────────
967
968    #[test]
969    #[allow(clippy::approx_constant)]
970    fn test_degrees_pi() {
971        let r = DegreesFunc.invoke(&[float(std::f64::consts::PI)]).unwrap();
972        assert_float_eq(&r, 180.0);
973    }
974
975    #[test]
976    #[allow(clippy::approx_constant)]
977    fn test_radians_180() {
978        let r = RadiansFunc.invoke(&[float(180.0)]).unwrap();
979        assert_float_eq(&r, std::f64::consts::PI);
980    }
981
982    #[test]
983    fn test_mod_basic() {
984        let r = ModFunc.invoke(&[float(10.0), float(3.0)]).unwrap();
985        assert_float_eq(&r, 1.0);
986    }
987
988    #[test]
989    fn test_mod_zero_divisor() {
990        assert_null(&ModFunc.invoke(&[float(10.0), float(0.0)]).unwrap());
991    }
992
993    #[test]
994    fn test_pi_precision() {
995        let r = PiFunc.invoke(&[]).unwrap();
996        assert_float_eq(&r, std::f64::consts::PI);
997    }
998
999    // ── NaN / Inf ─────────────────────────────────────────────────────
1000
1001    #[test]
1002    fn test_nan_normalized_to_null() {
1003        // Any operation producing NaN must return NULL.
1004        // sqrt(-1) → NULL via domain check.
1005        assert_null(&SqrtFunc.invoke(&[float(-1.0)]).unwrap());
1006        // acos(2) → NULL via domain check.
1007        assert_null(&AcosFunc.invoke(&[float(2.0)]).unwrap());
1008    }
1009
1010    #[test]
1011    fn test_inf_propagation() {
1012        // exp(1000) = +Inf (valid REAL)
1013        let r = ExpFunc.invoke(&[float(1000.0)]).unwrap();
1014        match r {
1015            SqliteValue::Float(v) => assert!(v.is_infinite()),
1016            other => panic!("expected Inf, got {other:?}"),
1017        }
1018    }
1019
1020    #[test]
1021    fn test_neg_inf_propagation() {
1022        // exp(-1000) is tiny but not -Inf.
1023        // -exp(1000) would be -Inf, but we test sinh(large) which overflows.
1024        let r = SinhFunc.invoke(&[float(1000.0)]).unwrap();
1025        match r {
1026            SqliteValue::Float(v) => assert!(v.is_infinite() && v > 0.0),
1027            other => panic!("expected +Inf, got {other:?}"),
1028        }
1029        let r = SinhFunc.invoke(&[float(-1000.0)]).unwrap();
1030        match r {
1031            SqliteValue::Float(v) => assert!(v.is_infinite() && v < 0.0),
1032            other => panic!("expected -Inf, got {other:?}"),
1033        }
1034    }
1035
1036    #[test]
1037    fn test_ceiling_alias() {
1038        // "ceiling" is registered as alias for "ceil" via register_math_builtins.
1039        // Here we test functionally: same result.
1040        let r1 = CeilFunc.invoke(&[float(1.2)]).unwrap();
1041        // CeilingFunc is defined inside register_math_builtins, so we test
1042        // via the registry instead.
1043        let mut reg = FunctionRegistry::new();
1044        register_math_builtins(&mut reg);
1045        let ceiling = reg.find_scalar("ceiling", 1).expect("ceiling registered");
1046        let r2 = ceiling.invoke(&[float(1.2)]).unwrap();
1047        assert_eq!(r1, r2);
1048    }
1049
1050    #[test]
1051    fn test_all_null_input() {
1052        // All math functions return NULL for NULL input.
1053        let n = &[null()];
1054        assert_null(&AcosFunc.invoke(n).unwrap());
1055        assert_null(&AsinFunc.invoke(n).unwrap());
1056        assert_null(&AtanFunc.invoke(n).unwrap());
1057        assert_null(&CosFunc.invoke(n).unwrap());
1058        assert_null(&SinFunc.invoke(n).unwrap());
1059        assert_null(&TanFunc.invoke(n).unwrap());
1060        assert_null(&AcoshFunc.invoke(n).unwrap());
1061        assert_null(&AsinhFunc.invoke(n).unwrap());
1062        assert_null(&AtanhFunc.invoke(n).unwrap());
1063        assert_null(&CoshFunc.invoke(n).unwrap());
1064        assert_null(&SinhFunc.invoke(n).unwrap());
1065        assert_null(&TanhFunc.invoke(n).unwrap());
1066        assert_null(&CeilFunc.invoke(n).unwrap());
1067        assert_null(&FloorFunc.invoke(n).unwrap());
1068        assert_null(&TruncFunc.invoke(n).unwrap());
1069        assert_null(&LnFunc.invoke(n).unwrap());
1070        assert_null(&Log10Func.invoke(n).unwrap());
1071        assert_null(&Log2Func.invoke(n).unwrap());
1072        assert_null(&ExpFunc.invoke(n).unwrap());
1073        assert_null(&SqrtFunc.invoke(n).unwrap());
1074        assert_null(&DegreesFunc.invoke(n).unwrap());
1075        assert_null(&RadiansFunc.invoke(n).unwrap());
1076        // Two-arg functions with NULL
1077        assert_null(&Atan2Func.invoke(&[null(), float(1.0)]).unwrap());
1078        assert_null(&Atan2Func.invoke(&[float(1.0), null()]).unwrap());
1079        assert_null(&PowFunc.invoke(&[null(), float(1.0)]).unwrap());
1080        assert_null(&ModFunc.invoke(&[null(), float(1.0)]).unwrap());
1081        // log with NULL
1082        assert_null(&LogFunc.invoke(&[null()]).unwrap());
1083        assert_null(&LogFunc.invoke(&[null(), float(8.0)]).unwrap());
1084        assert_null(&LogFunc.invoke(&[float(2.0), null()]).unwrap());
1085    }
1086
1087    #[test]
1088    fn test_blob_input_returns_null() {
1089        let blob = SqliteValue::Blob(vec![b'1', b'2'].into());
1090        assert_null(&SqrtFunc.invoke(std::slice::from_ref(&blob)).unwrap());
1091        assert_null(&CeilFunc.invoke(std::slice::from_ref(&blob)).unwrap());
1092        assert_null(&Atan2Func.invoke(&[blob.clone(), float(1.0)]).unwrap());
1093        assert_null(&PowFunc.invoke(&[float(2.0), blob.clone()]).unwrap());
1094        assert_null(&LogFunc.invoke(&[blob.clone()]).unwrap());
1095        assert_null(&ModFunc.invoke(&[float(4.0), blob]).unwrap());
1096    }
1097
1098    #[test]
1099    fn test_register_math_builtins_all_present() {
1100        let mut reg = FunctionRegistry::new();
1101        register_math_builtins(&mut reg);
1102
1103        let expected = [
1104            ("acos", 1),
1105            ("asin", 1),
1106            ("atan", 1),
1107            ("atan2", 2),
1108            ("cos", 1),
1109            ("sin", 1),
1110            ("tan", 1),
1111            ("acosh", 1),
1112            ("asinh", 1),
1113            ("atanh", 1),
1114            ("cosh", 1),
1115            ("sinh", 1),
1116            ("tanh", 1),
1117            ("ceil", 1),
1118            ("ceiling", 1),
1119            ("floor", 1),
1120            ("trunc", 1),
1121            ("ln", 1),
1122            ("log10", 1),
1123            ("log2", 1),
1124            ("exp", 1),
1125            ("pow", 2),
1126            ("power", 2),
1127            ("sqrt", 1),
1128            ("degrees", 1),
1129            ("radians", 1),
1130            ("mod", 2),
1131            ("pi", 0),
1132        ];
1133
1134        for (name, arity) in expected {
1135            assert!(
1136                reg.find_scalar(name, arity).is_some(),
1137                "math function '{name}/{arity}' not registered"
1138            );
1139        }
1140
1141        // log is variadic (-1), test lookup with 1 and 2 args
1142        assert!(reg.find_scalar("log", 1).is_some(), "log/1 via variadic");
1143        assert!(reg.find_scalar("log", 2).is_some(), "log/2 via variadic");
1144    }
1145
1146    #[test]
1147    fn test_e2e_registry_invoke_math() {
1148        let mut reg = FunctionRegistry::new();
1149        register_math_builtins(&mut reg);
1150
1151        // pi() through registry
1152        let pi = reg.find_scalar("pi", 0).unwrap();
1153        let r = pi.invoke(&[]).unwrap();
1154        assert_float_eq(&r, std::f64::consts::PI);
1155
1156        // sqrt(144) through registry
1157        let sqrt = reg.find_scalar("sqrt", 1).unwrap();
1158        let r = sqrt.invoke(&[float(144.0)]).unwrap();
1159        assert_float_eq(&r, 12.0);
1160
1161        // log(2, 8) = 3.0 through registry
1162        let log = reg.find_scalar("log", 2).unwrap();
1163        let r = log.invoke(&[float(2.0), float(8.0)]).unwrap();
1164        assert_float_eq(&r, 3.0);
1165    }
1166}