1#![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
27fn 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 SqliteValue::Text(s) => {
40 let trimmed = s.trim();
41 trimmed.parse::<f64>().ok().filter(|f| f.is_finite())
43 }
44 SqliteValue::Blob(_) => None,
45 }
46}
47
48fn wrap(v: f64) -> SqliteValue {
50 if v.is_nan() {
51 SqliteValue::Null
52 } else {
53 SqliteValue::Float(v)
54 }
55}
56
57fn 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
65fn 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
80pub 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
200pub 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 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
300pub 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
379pub 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
397pub 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
414pub struct LogFunc;
416
417impl ScalarFunction for LogFunc {
418 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
419 if args.len() == 1 {
420 return Log10Func.invoke(args);
422 }
423 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 }
439
440 fn min_args(&self) -> i32 {
441 1
442 }
443
444 fn max_args(&self) -> Option<i32> {
445 Some(2)
446 }
447
448 fn name(&self) -> &str {
449 "log"
450 }
451}
452
453pub struct Log2Func;
454
455impl ScalarFunction for Log2Func {
456 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
457 unary_domain(args, |x| x > 0.0, f64::log2)
458 }
459
460 fn num_args(&self) -> i32 {
461 1
462 }
463
464 fn name(&self) -> &str {
465 "log2"
466 }
467}
468
469pub struct ExpFunc;
470
471impl ScalarFunction for ExpFunc {
472 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
473 unary_math(args, f64::exp)
475 }
476
477 fn num_args(&self) -> i32 {
478 1
479 }
480
481 fn name(&self) -> &str {
482 "exp"
483 }
484}
485
486pub struct PowFunc;
487
488impl ScalarFunction for PowFunc {
489 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
490 let Some(x) = to_f64(&args[0]) else {
491 return Ok(SqliteValue::Null);
492 };
493 let Some(y) = to_f64(&args[1]) else {
494 return Ok(SqliteValue::Null);
495 };
496 Ok(wrap(x.powf(y)))
497 }
498
499 fn num_args(&self) -> i32 {
500 2
501 }
502
503 fn name(&self) -> &str {
504 "pow"
505 }
506}
507
508pub struct SqrtFunc;
509
510impl ScalarFunction for SqrtFunc {
511 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
512 unary_domain(args, |x| x >= 0.0, f64::sqrt)
513 }
514
515 fn num_args(&self) -> i32 {
516 1
517 }
518
519 fn name(&self) -> &str {
520 "sqrt"
521 }
522}
523
524pub struct DegreesFunc;
527
528impl ScalarFunction for DegreesFunc {
529 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
530 unary_math(args, f64::to_degrees)
531 }
532
533 fn num_args(&self) -> i32 {
534 1
535 }
536
537 fn name(&self) -> &str {
538 "degrees"
539 }
540}
541
542pub struct RadiansFunc;
543
544impl ScalarFunction for RadiansFunc {
545 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
546 unary_math(args, f64::to_radians)
547 }
548
549 fn num_args(&self) -> i32 {
550 1
551 }
552
553 fn name(&self) -> &str {
554 "radians"
555 }
556}
557
558pub struct ModFunc;
559
560impl ScalarFunction for ModFunc {
561 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
562 let Some(x) = to_f64(&args[0]) else {
563 return Ok(SqliteValue::Null);
564 };
565 let Some(y) = to_f64(&args[1]) else {
566 return Ok(SqliteValue::Null);
567 };
568 if y == 0.0 {
569 return Ok(SqliteValue::Null);
570 }
571 Ok(wrap(x % y))
572 }
573
574 fn num_args(&self) -> i32 {
575 2
576 }
577
578 fn name(&self) -> &str {
579 "mod"
580 }
581}
582
583pub struct PiFunc;
584
585impl ScalarFunction for PiFunc {
586 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
587 Ok(SqliteValue::Float(std::f64::consts::PI))
588 }
589
590 fn num_args(&self) -> i32 {
591 0
592 }
593
594 fn name(&self) -> &str {
595 "pi"
596 }
597}
598
599pub fn register_math_builtins(registry: &mut FunctionRegistry) {
603 registry.register_scalar(AcosFunc);
605 registry.register_scalar(AsinFunc);
606 registry.register_scalar(AtanFunc);
607 registry.register_scalar(Atan2Func);
608 registry.register_scalar(CosFunc);
609 registry.register_scalar(SinFunc);
610 registry.register_scalar(TanFunc);
611
612 registry.register_scalar(AcoshFunc);
614 registry.register_scalar(AsinhFunc);
615 registry.register_scalar(AtanhFunc);
616 registry.register_scalar(CoshFunc);
617 registry.register_scalar(SinhFunc);
618 registry.register_scalar(TanhFunc);
619
620 registry.register_scalar(CeilFunc);
622 registry.register_scalar(FloorFunc);
623 registry.register_scalar(TruncFunc);
624
625 registry.register_scalar(LnFunc);
627 registry.register_scalar(LogFunc);
628 registry.register_scalar(Log10Func);
629 registry.register_scalar(Log2Func);
630 registry.register_scalar(ExpFunc);
631 registry.register_scalar(PowFunc);
632 registry.register_scalar(SqrtFunc);
633
634 registry.register_scalar(DegreesFunc);
636 registry.register_scalar(RadiansFunc);
637 registry.register_scalar(ModFunc);
638 registry.register_scalar(PiFunc);
639
640 struct CeilingFunc;
643 impl ScalarFunction for CeilingFunc {
644 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
645 CeilFunc.invoke(args)
646 }
647
648 fn num_args(&self) -> i32 {
649 1
650 }
651
652 fn name(&self) -> &str {
653 "ceiling"
654 }
655 }
656 registry.register_scalar(CeilingFunc);
657
658 struct PowerFunc;
660 impl ScalarFunction for PowerFunc {
661 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
662 PowFunc.invoke(args)
663 }
664
665 fn num_args(&self) -> i32 {
666 2
667 }
668
669 fn name(&self) -> &str {
670 "power"
671 }
672 }
673 registry.register_scalar(PowerFunc);
674}
675
676#[cfg(test)]
679mod tests {
680 use fsqlite_error::FrankenError;
681
682 use super::*;
683
684 const EPS: f64 = 1e-12;
686
687 fn assert_float_eq(result: &SqliteValue, expected: f64) {
688 match result {
689 SqliteValue::Float(v) => {
690 assert!((v - expected).abs() < EPS, "expected {expected}, got {v}");
691 }
692 other => unreachable!("expected Float({expected}), got {other:?}"),
693 }
694 }
695
696 fn assert_null(result: &SqliteValue) {
697 assert_eq!(result, &SqliteValue::Null, "expected NULL");
698 }
699
700 fn float(v: f64) -> SqliteValue {
701 SqliteValue::Float(v)
702 }
703
704 fn int(v: i64) -> SqliteValue {
705 SqliteValue::Integer(v)
706 }
707
708 fn assert_wrong_arg_count(function: &dyn ScalarFunction, args: &[SqliteValue], name: &str) {
709 let err = function
710 .invoke(args)
711 .expect_err("wrong arity should return function error");
712 let expected = format!("wrong number of arguments to function {name}()");
713 assert!(
714 matches!(&err, FrankenError::FunctionError(message) if message == &expected),
715 "expected {expected:?}, got {err:?}"
716 );
717 }
718
719 fn null() -> SqliteValue {
720 SqliteValue::Null
721 }
722
723 #[test]
726 fn test_acos_valid() {
727 let r = AcosFunc.invoke(&[float(0.5)]).unwrap();
728 assert_float_eq(&r, 0.5_f64.acos());
729 }
730
731 #[test]
732 fn test_acos_domain_error() {
733 assert_null(&AcosFunc.invoke(&[float(2.0)]).unwrap());
734 }
735
736 #[test]
737 fn test_acos_null() {
738 assert_null(&AcosFunc.invoke(&[null()]).unwrap());
739 }
740
741 #[test]
742 fn test_acosh_valid() {
743 let r = AcoshFunc.invoke(&[float(2.0)]).unwrap();
744 assert_float_eq(&r, 2.0_f64.acosh());
745 }
746
747 #[test]
748 fn test_acosh_domain_error() {
749 assert_null(&AcoshFunc.invoke(&[float(0.5)]).unwrap());
750 }
751
752 #[test]
753 fn test_asin_valid() {
754 let r = AsinFunc.invoke(&[float(0.5)]).unwrap();
755 assert_float_eq(&r, 0.5_f64.asin());
756 }
757
758 #[test]
759 fn test_asin_domain_error() {
760 assert_null(&AsinFunc.invoke(&[float(2.0)]).unwrap());
761 }
762
763 #[test]
764 fn test_asinh_all_reals() {
765 let r = AsinhFunc.invoke(&[float(1.0)]).unwrap();
766 assert_float_eq(&r, 1.0_f64.asinh());
767 }
768
769 #[test]
770 fn test_atan_basic() {
771 let r = AtanFunc.invoke(&[float(1.0)]).unwrap();
772 assert_float_eq(&r, std::f64::consts::FRAC_PI_4);
773 }
774
775 #[test]
776 fn test_atan2_quadrants() {
777 let r = Atan2Func.invoke(&[float(1.0), float(1.0)]).unwrap();
779 assert_float_eq(&r, std::f64::consts::FRAC_PI_4);
780
781 let r = Atan2Func.invoke(&[float(1.0), float(-1.0)]).unwrap();
783 assert_float_eq(&r, 3.0 * std::f64::consts::FRAC_PI_4);
784
785 let r = Atan2Func.invoke(&[float(-1.0), float(-1.0)]).unwrap();
787 assert_float_eq(&r, -3.0 * std::f64::consts::FRAC_PI_4);
788
789 let r = Atan2Func.invoke(&[float(-1.0), float(1.0)]).unwrap();
791 assert_float_eq(&r, -std::f64::consts::FRAC_PI_4);
792 }
793
794 #[test]
795 fn test_atanh_valid() {
796 let r = AtanhFunc.invoke(&[float(0.5)]).unwrap();
797 assert_float_eq(&r, 0.5_f64.atanh());
798 }
799
800 #[test]
801 fn test_atanh_domain_error() {
802 assert_null(&AtanhFunc.invoke(&[float(1.0)]).unwrap());
804 assert_null(&AtanhFunc.invoke(&[float(-1.0)]).unwrap());
805 }
806
807 #[test]
808 fn test_cos_zero() {
809 let r = CosFunc.invoke(&[float(0.0)]).unwrap();
810 assert_float_eq(&r, 1.0);
811 }
812
813 #[test]
814 fn test_cosh_zero() {
815 let r = CoshFunc.invoke(&[float(0.0)]).unwrap();
816 assert_float_eq(&r, 1.0);
817 }
818
819 #[test]
820 fn test_sin_zero() {
821 let r = SinFunc.invoke(&[float(0.0)]).unwrap();
822 assert_float_eq(&r, 0.0);
823 }
824
825 #[test]
826 fn test_sinh_zero() {
827 let r = SinhFunc.invoke(&[float(0.0)]).unwrap();
828 assert_float_eq(&r, 0.0);
829 }
830
831 #[test]
832 fn test_tan_zero() {
833 let r = TanFunc.invoke(&[float(0.0)]).unwrap();
834 assert_float_eq(&r, 0.0);
835 }
836
837 #[test]
838 fn test_tanh_zero() {
839 let r = TanhFunc.invoke(&[float(0.0)]).unwrap();
840 assert_float_eq(&r, 0.0);
841 }
842
843 #[test]
846 fn test_ceil_real() {
847 let r = CeilFunc.invoke(&[float(1.2)]).unwrap();
848 assert_float_eq(&r, 2.0);
849 }
850
851 #[test]
852 fn test_ceil_integer_type() {
853 let r = CeilFunc.invoke(&[int(5)]).unwrap();
854 assert_eq!(r, SqliteValue::Integer(5));
855 }
856
857 #[test]
858 fn test_ceil_negative() {
859 let r = CeilFunc.invoke(&[float(-1.2)]).unwrap();
860 assert_float_eq(&r, -1.0);
861 }
862
863 #[test]
864 fn test_floor_real() {
865 let r = FloorFunc.invoke(&[float(1.7)]).unwrap();
866 assert_float_eq(&r, 1.0);
867 }
868
869 #[test]
870 fn test_floor_integer_type() {
871 let r = FloorFunc.invoke(&[int(5)]).unwrap();
872 assert_eq!(r, SqliteValue::Integer(5));
873 }
874
875 #[test]
876 fn test_floor_negative() {
877 let r = FloorFunc.invoke(&[float(-1.2)]).unwrap();
878 assert_float_eq(&r, -2.0);
879 }
880
881 #[test]
882 fn test_trunc_positive() {
883 let r = TruncFunc.invoke(&[float(2.9)]).unwrap();
884 assert_float_eq(&r, 2.0);
885 }
886
887 #[test]
888 fn test_trunc_negative() {
889 let r = TruncFunc.invoke(&[float(-2.9)]).unwrap();
890 assert_float_eq(&r, -2.0);
891 }
892
893 #[test]
894 fn test_trunc_integer_type() {
895 let r = TruncFunc.invoke(&[int(5)]).unwrap();
896 assert_eq!(r, SqliteValue::Integer(5));
897 }
898
899 #[test]
902 fn test_ln_positive() {
903 let r = LnFunc.invoke(&[float(std::f64::consts::E)]).unwrap();
904 assert_float_eq(&r, 1.0);
905 }
906
907 #[test]
908 fn test_ln_zero() {
909 assert_null(&LnFunc.invoke(&[float(0.0)]).unwrap());
910 }
911
912 #[test]
913 fn test_ln_negative() {
914 assert_null(&LnFunc.invoke(&[float(-1.0)]).unwrap());
915 }
916
917 #[test]
918 fn test_log_single_arg_base10() {
919 let r = LogFunc.invoke(&[float(100.0)]).unwrap();
920 assert_float_eq(&r, 2.0);
921 }
922
923 #[test]
924 fn test_log_two_arg_base() {
925 let r = LogFunc.invoke(&[float(2.0), float(8.0)]).unwrap();
926 assert_float_eq(&r, 3.0);
927 }
928
929 #[test]
930 fn test_log10_alias() {
931 let r = Log10Func.invoke(&[float(1000.0)]).unwrap();
932 assert_float_eq(&r, 3.0);
933 }
934
935 #[test]
936 fn test_log2_basic() {
937 let r = Log2Func.invoke(&[float(8.0)]).unwrap();
938 assert_float_eq(&r, 3.0);
939 }
940
941 #[test]
942 fn test_exp_one() {
943 let r = ExpFunc.invoke(&[float(1.0)]).unwrap();
944 assert_float_eq(&r, std::f64::consts::E);
945 }
946
947 #[test]
948 fn test_exp_overflow() {
949 let r = ExpFunc.invoke(&[float(1000.0)]).unwrap();
950 match r {
951 SqliteValue::Float(v) => assert!(v.is_infinite() && v > 0.0, "+Inf expected"),
952 other => unreachable!("expected +Inf Float, got {other:?}"),
953 }
954 }
955
956 #[test]
957 fn test_pow_basic() {
958 let r = PowFunc.invoke(&[float(2.0), float(10.0)]).unwrap();
959 assert_float_eq(&r, 1024.0);
960 }
961
962 #[test]
963 fn test_pow_zero_zero() {
964 let r = PowFunc.invoke(&[float(0.0), float(0.0)]).unwrap();
965 assert_float_eq(&r, 1.0);
966 }
967
968 #[test]
969 fn test_power_alias() {
970 let r = PowFunc.invoke(&[float(3.0), float(2.0)]).unwrap();
973 assert_float_eq(&r, 9.0);
974 }
975
976 #[test]
977 fn test_sqrt_positive() {
978 let r = SqrtFunc.invoke(&[float(144.0)]).unwrap();
979 assert_float_eq(&r, 12.0);
980 }
981
982 #[test]
983 fn test_sqrt_negative() {
984 assert_null(&SqrtFunc.invoke(&[float(-1.0)]).unwrap());
985 }
986
987 #[test]
990 #[allow(clippy::approx_constant)]
991 fn test_degrees_pi() {
992 let r = DegreesFunc.invoke(&[float(std::f64::consts::PI)]).unwrap();
993 assert_float_eq(&r, 180.0);
994 }
995
996 #[test]
997 #[allow(clippy::approx_constant)]
998 fn test_radians_180() {
999 let r = RadiansFunc.invoke(&[float(180.0)]).unwrap();
1000 assert_float_eq(&r, std::f64::consts::PI);
1001 }
1002
1003 #[test]
1004 fn test_mod_basic() {
1005 let r = ModFunc.invoke(&[float(10.0), float(3.0)]).unwrap();
1006 assert_float_eq(&r, 1.0);
1007 }
1008
1009 #[test]
1010 fn test_mod_zero_divisor() {
1011 assert_null(&ModFunc.invoke(&[float(10.0), float(0.0)]).unwrap());
1012 }
1013
1014 #[test]
1015 fn test_pi_precision() {
1016 let r = PiFunc.invoke(&[]).unwrap();
1017 assert_float_eq(&r, std::f64::consts::PI);
1018 }
1019
1020 #[test]
1023 fn test_nan_normalized_to_null() {
1024 assert_null(&SqrtFunc.invoke(&[float(-1.0)]).unwrap());
1027 assert_null(&AcosFunc.invoke(&[float(2.0)]).unwrap());
1029 }
1030
1031 #[test]
1032 fn test_inf_propagation() {
1033 let r = ExpFunc.invoke(&[float(1000.0)]).unwrap();
1035 match r {
1036 SqliteValue::Float(v) => assert!(v.is_infinite()),
1037 other => unreachable!("expected Inf, got {other:?}"),
1038 }
1039 }
1040
1041 #[test]
1042 fn test_neg_inf_propagation() {
1043 let r = SinhFunc.invoke(&[float(1000.0)]).unwrap();
1046 match r {
1047 SqliteValue::Float(v) => assert!(v.is_infinite() && v > 0.0),
1048 other => unreachable!("expected +Inf, got {other:?}"),
1049 }
1050 let r = SinhFunc.invoke(&[float(-1000.0)]).unwrap();
1051 match r {
1052 SqliteValue::Float(v) => assert!(v.is_infinite() && v < 0.0),
1053 other => unreachable!("expected -Inf, got {other:?}"),
1054 }
1055 }
1056
1057 #[test]
1058 fn test_ceiling_alias() {
1059 let r1 = CeilFunc.invoke(&[float(1.2)]).unwrap();
1062 let mut reg = FunctionRegistry::new();
1065 register_math_builtins(&mut reg);
1066 let ceiling = reg.find_scalar("ceiling", 1).expect("ceiling registered");
1067 let r2 = ceiling.invoke(&[float(1.2)]).unwrap();
1068 assert_eq!(r1, r2);
1069 }
1070
1071 #[test]
1072 fn test_all_null_input() {
1073 let n = &[null()];
1075 assert_null(&AcosFunc.invoke(n).unwrap());
1076 assert_null(&AsinFunc.invoke(n).unwrap());
1077 assert_null(&AtanFunc.invoke(n).unwrap());
1078 assert_null(&CosFunc.invoke(n).unwrap());
1079 assert_null(&SinFunc.invoke(n).unwrap());
1080 assert_null(&TanFunc.invoke(n).unwrap());
1081 assert_null(&AcoshFunc.invoke(n).unwrap());
1082 assert_null(&AsinhFunc.invoke(n).unwrap());
1083 assert_null(&AtanhFunc.invoke(n).unwrap());
1084 assert_null(&CoshFunc.invoke(n).unwrap());
1085 assert_null(&SinhFunc.invoke(n).unwrap());
1086 assert_null(&TanhFunc.invoke(n).unwrap());
1087 assert_null(&CeilFunc.invoke(n).unwrap());
1088 assert_null(&FloorFunc.invoke(n).unwrap());
1089 assert_null(&TruncFunc.invoke(n).unwrap());
1090 assert_null(&LnFunc.invoke(n).unwrap());
1091 assert_null(&Log10Func.invoke(n).unwrap());
1092 assert_null(&Log2Func.invoke(n).unwrap());
1093 assert_null(&ExpFunc.invoke(n).unwrap());
1094 assert_null(&SqrtFunc.invoke(n).unwrap());
1095 assert_null(&DegreesFunc.invoke(n).unwrap());
1096 assert_null(&RadiansFunc.invoke(n).unwrap());
1097 assert_null(&Atan2Func.invoke(&[null(), float(1.0)]).unwrap());
1099 assert_null(&Atan2Func.invoke(&[float(1.0), null()]).unwrap());
1100 assert_null(&PowFunc.invoke(&[null(), float(1.0)]).unwrap());
1101 assert_null(&ModFunc.invoke(&[null(), float(1.0)]).unwrap());
1102 assert_null(&LogFunc.invoke(&[null()]).unwrap());
1104 assert_null(&LogFunc.invoke(&[null(), float(8.0)]).unwrap());
1105 assert_null(&LogFunc.invoke(&[float(2.0), null()]).unwrap());
1106 }
1107
1108 #[test]
1109 fn test_blob_input_returns_null() {
1110 let blob = SqliteValue::Blob(vec![b'1', b'2'].into());
1111 assert_null(&SqrtFunc.invoke(std::slice::from_ref(&blob)).unwrap());
1112 assert_null(&CeilFunc.invoke(std::slice::from_ref(&blob)).unwrap());
1113 assert_null(&Atan2Func.invoke(&[blob.clone(), float(1.0)]).unwrap());
1114 assert_null(&PowFunc.invoke(&[float(2.0), blob.clone()]).unwrap());
1115 assert_null(&LogFunc.invoke(&[blob.clone()]).unwrap());
1116 assert_null(&ModFunc.invoke(&[float(4.0), blob]).unwrap());
1117 }
1118
1119 #[test]
1120 fn test_register_math_builtins_all_present() {
1121 let mut reg = FunctionRegistry::new();
1122 register_math_builtins(&mut reg);
1123
1124 let expected = [
1125 ("acos", 1),
1126 ("asin", 1),
1127 ("atan", 1),
1128 ("atan2", 2),
1129 ("cos", 1),
1130 ("sin", 1),
1131 ("tan", 1),
1132 ("acosh", 1),
1133 ("asinh", 1),
1134 ("atanh", 1),
1135 ("cosh", 1),
1136 ("sinh", 1),
1137 ("tanh", 1),
1138 ("ceil", 1),
1139 ("ceiling", 1),
1140 ("floor", 1),
1141 ("trunc", 1),
1142 ("ln", 1),
1143 ("log10", 1),
1144 ("log2", 1),
1145 ("exp", 1),
1146 ("pow", 2),
1147 ("power", 2),
1148 ("sqrt", 1),
1149 ("degrees", 1),
1150 ("radians", 1),
1151 ("mod", 2),
1152 ("pi", 0),
1153 ];
1154
1155 for (name, arity) in expected {
1156 assert!(
1157 reg.find_scalar(name, arity).is_some(),
1158 "math function '{name}/{arity}' not registered"
1159 );
1160 }
1161
1162 let log0 = reg
1164 .find_scalar("log", 0)
1165 .expect("log/0 returns erroring scalar");
1166 assert_wrong_arg_count(log0.as_ref(), &[], "log");
1167 assert!(reg.find_scalar("log", 1).is_some(), "log/1 via variadic");
1168 assert!(reg.find_scalar("log", 2).is_some(), "log/2 via variadic");
1169 let log3 = reg
1170 .find_scalar("log", 3)
1171 .expect("log/3 returns erroring scalar");
1172 assert_wrong_arg_count(
1173 log3.as_ref(),
1174 &[SqliteValue::Null, SqliteValue::Null, SqliteValue::Null],
1175 "log",
1176 );
1177 }
1178
1179 #[test]
1180 fn test_e2e_registry_invoke_math() {
1181 let mut reg = FunctionRegistry::new();
1182 register_math_builtins(&mut reg);
1183
1184 let pi = reg.find_scalar("pi", 0).unwrap();
1186 let r = pi.invoke(&[]).unwrap();
1187 assert_float_eq(&r, std::f64::consts::PI);
1188
1189 let sqrt = reg.find_scalar("sqrt", 1).unwrap();
1191 let r = sqrt.invoke(&[float(144.0)]).unwrap();
1192 assert_float_eq(&r, 12.0);
1193
1194 let log = reg.find_scalar("log", 2).unwrap();
1196 let r = log.invoke(&[float(2.0), float(8.0)]).unwrap();
1197 assert_float_eq(&r, 3.0);
1198 }
1199}