1#![allow(
13 clippy::unnecessary_literal_bound,
14 clippy::too_many_lines,
15 clippy::cast_possible_truncation,
16 clippy::cast_possible_wrap,
17 clippy::cast_precision_loss,
18 clippy::match_same_arms,
19 clippy::items_after_statements,
20 clippy::float_cmp,
21 clippy::cast_sign_loss,
22 clippy::suboptimal_flops
23)]
24
25use fsqlite_error::{FrankenError, Result};
26use fsqlite_types::SqliteValue;
27
28use crate::{AggregateFunction, FunctionRegistry};
29
30#[inline]
36fn kahan_add(sum: &mut f64, compensation: &mut f64, value: f64) {
37 let s = *sum;
38 let t = s + value;
39 if s.abs() > value.abs() {
40 *compensation += (s - t) + value;
41 } else {
42 *compensation += (value - t) + s;
43 }
44 *sum = t;
45}
46
47pub struct AvgState {
52 sum: f64,
53 compensation: f64,
54 count: i64,
55}
56
57pub struct AvgFunc;
58
59impl AggregateFunction for AvgFunc {
60 type State = AvgState;
61
62 fn initial_state(&self) -> Self::State {
63 AvgState {
64 sum: 0.0,
65 compensation: 0.0,
66 count: 0,
67 }
68 }
69
70 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
71 if !args[0].is_null() {
72 kahan_add(&mut state.sum, &mut state.compensation, args[0].to_float());
73 state.count += 1;
74 }
75 Ok(())
76 }
77
78 fn finalize(&self, state: Self::State) -> Result<SqliteValue> {
79 if state.count == 0 {
80 Ok(SqliteValue::Null)
81 } else {
82 Ok(SqliteValue::Float(
83 (state.sum + state.compensation) / state.count as f64,
84 ))
85 }
86 }
87
88 fn num_args(&self) -> i32 {
89 1
90 }
91
92 fn name(&self) -> &str {
93 "avg"
94 }
95}
96
97pub struct CountStarFunc;
103
104impl AggregateFunction for CountStarFunc {
105 type State = i64;
106
107 fn initial_state(&self) -> Self::State {
108 0
109 }
110
111 fn step(&self, state: &mut Self::State, _args: &[SqliteValue]) -> Result<()> {
112 *state += 1;
113 Ok(())
114 }
115
116 fn finalize(&self, state: Self::State) -> Result<SqliteValue> {
117 Ok(SqliteValue::Integer(state))
118 }
119
120 fn num_args(&self) -> i32 {
121 0 }
123
124 fn name(&self) -> &str {
125 "count"
126 }
127}
128
129pub struct CountFunc;
131
132impl AggregateFunction for CountFunc {
133 type State = i64;
134
135 fn initial_state(&self) -> Self::State {
136 0
137 }
138
139 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
140 if !args[0].is_null() {
141 *state += 1;
142 }
143 Ok(())
144 }
145
146 fn finalize(&self, state: Self::State) -> Result<SqliteValue> {
147 Ok(SqliteValue::Integer(state))
148 }
149
150 fn num_args(&self) -> i32 {
151 1
152 }
153
154 fn name(&self) -> &str {
155 "count"
156 }
157}
158
159pub struct GroupConcatState {
164 result: String,
168 has_value: bool,
169}
170
171pub struct GroupConcatFunc;
172
173#[inline]
174fn push_group_concat_text(result: &mut String, value: &SqliteValue) {
175 if let Some(text) = value.as_text_str() {
176 result.push_str(text);
177 } else {
178 result.push_str(&value.to_text());
179 }
180}
181
182impl AggregateFunction for GroupConcatFunc {
183 type State = GroupConcatState;
184
185 fn initial_state(&self) -> Self::State {
186 GroupConcatState {
187 result: String::new(),
188 has_value: false,
189 }
190 }
191
192 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
193 if args[0].is_null() {
194 return Ok(());
195 }
196 if state.has_value {
197 match args.get(1) {
198 Some(separator) if !separator.is_null() => {
199 push_group_concat_text(&mut state.result, separator);
200 }
201 Some(_) => {}
202 None => state.result.push(','),
203 }
204 }
205 push_group_concat_text(&mut state.result, &args[0]);
206 state.has_value = true;
207 Ok(())
208 }
209
210 fn finalize(&self, state: Self::State) -> Result<SqliteValue> {
211 if state.has_value {
212 Ok(SqliteValue::Text(state.result.into()))
213 } else {
214 Ok(SqliteValue::Null)
215 }
216 }
217
218 fn num_args(&self) -> i32 {
219 -1 }
221
222 fn min_args(&self) -> i32 {
223 1
224 }
225
226 fn max_args(&self) -> Option<i32> {
227 Some(2)
228 }
229
230 fn name(&self) -> &str {
231 "group_concat"
232 }
233}
234
235pub struct AggMaxFunc;
240
241impl AggregateFunction for AggMaxFunc {
242 type State = Option<SqliteValue>;
243
244 fn initial_state(&self) -> Self::State {
245 None
246 }
247
248 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
249 if args[0].is_null() {
250 return Ok(());
251 }
252 let candidate = &args[0];
253 match state {
254 None => *state = Some(candidate.clone()),
255 Some(current) => {
256 if candidate > current {
257 *state = Some(candidate.clone());
258 }
259 }
260 }
261 Ok(())
262 }
263
264 fn finalize(&self, state: Self::State) -> Result<SqliteValue> {
265 Ok(state.unwrap_or(SqliteValue::Null))
266 }
267
268 fn num_args(&self) -> i32 {
269 1
270 }
271
272 fn name(&self) -> &str {
273 "max"
274 }
275}
276
277pub struct AggMinFunc;
282
283impl AggregateFunction for AggMinFunc {
284 type State = Option<SqliteValue>;
285
286 fn initial_state(&self) -> Self::State {
287 None
288 }
289
290 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
291 if args[0].is_null() {
292 return Ok(());
293 }
294 let candidate = &args[0];
295 match state {
296 None => *state = Some(candidate.clone()),
297 Some(current) => {
298 if candidate < current {
299 *state = Some(candidate.clone());
300 }
301 }
302 }
303 Ok(())
304 }
305
306 fn finalize(&self, state: Self::State) -> Result<SqliteValue> {
307 Ok(state.unwrap_or(SqliteValue::Null))
308 }
309
310 fn num_args(&self) -> i32 {
311 1
312 }
313
314 fn name(&self) -> &str {
315 "min"
316 }
317}
318
319pub struct SumState {
327 int_sum: i64,
328 float_sum: f64,
329 float_compensation: f64,
330 all_integer: bool,
331 has_values: bool,
332 overflowed: bool,
333}
334
335pub struct SumFunc;
336
337impl AggregateFunction for SumFunc {
338 type State = SumState;
339
340 fn initial_state(&self) -> Self::State {
341 SumState {
342 int_sum: 0,
343 float_sum: 0.0,
344 float_compensation: 0.0,
345 all_integer: true,
346 has_values: false,
347 overflowed: false,
348 }
349 }
350
351 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
352 let value = args[0].to_sum_numeric_value();
353 if value.is_null() {
354 return Ok(());
355 }
356 state.has_values = true;
357 match value {
358 SqliteValue::Integer(i) => {
359 if state.all_integer && !state.overflowed {
360 match state.int_sum.checked_add(i) {
361 Some(s) => state.int_sum = s,
362 None => state.overflowed = true,
363 }
364 }
365 kahan_add(
366 &mut state.float_sum,
367 &mut state.float_compensation,
368 i as f64,
369 );
370 }
371 SqliteValue::Float(f) => {
372 state.all_integer = false;
373 kahan_add(&mut state.float_sum, &mut state.float_compensation, f);
374 }
375 SqliteValue::Null | SqliteValue::Text(_) | SqliteValue::Blob(_) => {}
376 }
377 Ok(())
378 }
379
380 fn finalize(&self, state: Self::State) -> Result<SqliteValue> {
381 if !state.has_values {
382 return Ok(SqliteValue::Null);
383 }
384 if state.all_integer && state.overflowed {
385 return Err(FrankenError::IntegerOverflow);
386 }
387 if state.all_integer {
388 Ok(SqliteValue::Integer(state.int_sum))
389 } else {
390 Ok(SqliteValue::Float(
391 state.float_sum + state.float_compensation,
392 ))
393 }
394 }
395
396 fn num_args(&self) -> i32 {
397 1
398 }
399
400 fn name(&self) -> &str {
401 "sum"
402 }
403}
404
405pub struct TotalFunc;
410
411pub struct TotalState {
413 sum: f64,
414 compensation: f64,
415}
416
417impl AggregateFunction for TotalFunc {
418 type State = TotalState;
419
420 fn initial_state(&self) -> Self::State {
421 TotalState {
422 sum: 0.0,
423 compensation: 0.0,
424 }
425 }
426
427 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
428 if !args[0].is_null() {
429 kahan_add(&mut state.sum, &mut state.compensation, args[0].to_float());
430 }
431 Ok(())
432 }
433
434 fn finalize(&self, state: Self::State) -> Result<SqliteValue> {
435 Ok(SqliteValue::Float(state.sum + state.compensation))
436 }
437
438 fn num_args(&self) -> i32 {
439 1
440 }
441
442 fn name(&self) -> &str {
443 "total"
444 }
445}
446
447pub struct MedianFunc;
452
453impl AggregateFunction for MedianFunc {
454 type State = Vec<f64>;
455
456 fn initial_state(&self) -> Self::State {
457 Vec::new()
458 }
459
460 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
461 if !args[0].is_null() {
462 state.push(args[0].to_float());
463 }
464 Ok(())
465 }
466
467 fn finalize(&self, mut state: Self::State) -> Result<SqliteValue> {
468 if state.is_empty() {
469 return Ok(SqliteValue::Null);
470 }
471 state.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
472 let result = percentile_cont_impl(&state, 0.5);
473 Ok(SqliteValue::Float(result))
474 }
475
476 fn num_args(&self) -> i32 {
477 1
478 }
479
480 fn name(&self) -> &str {
481 "median"
482 }
483}
484
485pub struct PercentileState {
490 values: Vec<f64>,
491 p: Option<f64>,
492}
493
494pub struct PercentileFunc;
495
496impl AggregateFunction for PercentileFunc {
497 type State = PercentileState;
498
499 fn initial_state(&self) -> Self::State {
500 PercentileState {
501 values: Vec::new(),
502 p: None,
503 }
504 }
505
506 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
507 if !args[0].is_null() {
508 state.values.push(args[0].to_float());
509 }
510 if state.p.is_none() && args.len() > 1 && !args[1].is_null() {
512 state.p = Some(args[1].to_float());
513 }
514 Ok(())
515 }
516
517 fn finalize(&self, mut state: Self::State) -> Result<SqliteValue> {
518 if state.values.is_empty() {
519 return Ok(SqliteValue::Null);
520 }
521 let p = state.p.unwrap_or(50.0);
522 state
523 .values
524 .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
525 let result = percentile_cont_impl(&state.values, p / 100.0);
527 Ok(SqliteValue::Float(result))
528 }
529
530 fn num_args(&self) -> i32 {
531 2
532 }
533
534 fn name(&self) -> &str {
535 "percentile"
536 }
537}
538
539pub struct PercentileContFunc;
544
545impl AggregateFunction for PercentileContFunc {
546 type State = PercentileState;
547
548 fn initial_state(&self) -> Self::State {
549 PercentileState {
550 values: Vec::new(),
551 p: None,
552 }
553 }
554
555 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
556 if !args[0].is_null() {
557 state.values.push(args[0].to_float());
558 }
559 if state.p.is_none() && args.len() > 1 && !args[1].is_null() {
560 state.p = Some(args[1].to_float());
561 }
562 Ok(())
563 }
564
565 fn finalize(&self, mut state: Self::State) -> Result<SqliteValue> {
566 if state.values.is_empty() {
567 return Ok(SqliteValue::Null);
568 }
569 let p = state.p.unwrap_or(0.5);
570 state
571 .values
572 .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
573 let result = percentile_cont_impl(&state.values, p);
574 Ok(SqliteValue::Float(result))
575 }
576
577 fn num_args(&self) -> i32 {
578 2
579 }
580
581 fn name(&self) -> &str {
582 "percentile_cont"
583 }
584}
585
586pub struct PercentileDiscFunc;
591
592impl AggregateFunction for PercentileDiscFunc {
593 type State = PercentileState;
594
595 fn initial_state(&self) -> Self::State {
596 PercentileState {
597 values: Vec::new(),
598 p: None,
599 }
600 }
601
602 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
603 if !args[0].is_null() {
604 state.values.push(args[0].to_float());
605 }
606 if state.p.is_none() && args.len() > 1 && !args[1].is_null() {
607 state.p = Some(args[1].to_float());
608 }
609 Ok(())
610 }
611
612 fn finalize(&self, mut state: Self::State) -> Result<SqliteValue> {
613 if state.values.is_empty() {
614 return Ok(SqliteValue::Null);
615 }
616 let p = state.p.unwrap_or(0.5);
617 let p = if p.is_nan() { 0.5 } else { p.clamp(0.0, 1.0) };
618 state
619 .values
620 .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
621 let n = state.values.len();
623 let idx = ((p * n as f64).ceil() as usize)
624 .saturating_sub(1)
625 .min(n - 1);
626 Ok(SqliteValue::Float(state.values[idx]))
627 }
628
629 fn num_args(&self) -> i32 {
630 2
631 }
632
633 fn name(&self) -> &str {
634 "percentile_disc"
635 }
636}
637
638fn percentile_cont_impl(sorted: &[f64], p: f64) -> f64 {
643 let n = sorted.len();
644 if n == 1 {
645 return sorted[0];
646 }
647 let p = if p.is_nan() { 0.5 } else { p.clamp(0.0, 1.0) };
648 let rank = p * (n - 1) as f64;
649 let lower = rank.floor() as usize;
650 let upper = rank.ceil() as usize;
651 if lower == upper {
652 sorted[lower]
653 } else {
654 let frac = rank - lower as f64;
655 sorted[lower] * (1.0 - frac) + sorted[upper] * frac
656 }
657}
658
659pub fn register_aggregate_builtins(registry: &mut FunctionRegistry) {
663 registry.register_aggregate(AvgFunc);
664 registry.register_aggregate(CountStarFunc);
665 registry.register_aggregate(CountFunc);
666 registry.register_aggregate(GroupConcatFunc);
667 registry.register_aggregate(AggMaxFunc);
668 registry.register_aggregate(AggMinFunc);
669 registry.register_aggregate(SumFunc);
670 registry.register_aggregate(TotalFunc);
671 registry.register_aggregate(MedianFunc);
672 registry.register_aggregate(PercentileFunc);
673 registry.register_aggregate(PercentileContFunc);
674 registry.register_aggregate(PercentileDiscFunc);
675
676 struct StringAggFunc;
678 impl AggregateFunction for StringAggFunc {
679 type State = GroupConcatState;
680
681 fn initial_state(&self) -> Self::State {
682 GroupConcatState {
683 result: String::new(),
684 has_value: false,
685 }
686 }
687
688 fn step(&self, state: &mut Self::State, args: &[SqliteValue]) -> Result<()> {
689 GroupConcatFunc.step(state, args)
690 }
691
692 fn finalize(&self, state: Self::State) -> Result<SqliteValue> {
693 GroupConcatFunc.finalize(state)
694 }
695
696 fn num_args(&self) -> i32 {
697 2 }
699
700 fn name(&self) -> &str {
701 "string_agg"
702 }
703 }
704 registry.register_aggregate(StringAggFunc);
705}
706
707#[cfg(test)]
710mod tests {
711 use super::*;
712
713 const EPS: f64 = 1e-12;
714
715 fn int(v: i64) -> SqliteValue {
716 SqliteValue::Integer(v)
717 }
718
719 fn float(v: f64) -> SqliteValue {
720 SqliteValue::Float(v)
721 }
722
723 fn null() -> SqliteValue {
724 SqliteValue::Null
725 }
726
727 fn text(s: &str) -> SqliteValue {
728 SqliteValue::Text(s.into())
729 }
730
731 fn assert_float_eq(result: &SqliteValue, expected: f64) {
732 match result {
733 SqliteValue::Float(v) => {
734 assert!((v - expected).abs() < EPS, "expected {expected}, got {v}");
735 }
736 other => {
737 assert!(
738 matches!(other, SqliteValue::Float(_)),
739 "expected Float({expected}), got {other:?}"
740 );
741 }
742 }
743 }
744
745 fn run_agg<F: AggregateFunction>(func: &F, rows: &[SqliteValue]) -> SqliteValue {
747 let mut state = func.initial_state();
748 for row in rows {
749 func.step(&mut state, std::slice::from_ref(row)).unwrap();
750 }
751 func.finalize(state).unwrap()
752 }
753
754 fn run_agg2<F: AggregateFunction>(
756 func: &F,
757 rows: &[(SqliteValue, SqliteValue)],
758 ) -> SqliteValue {
759 let mut state = func.initial_state();
760 for (a, b) in rows {
761 func.step(&mut state, &[a.clone(), b.clone()]).unwrap();
762 }
763 func.finalize(state).unwrap()
764 }
765
766 #[test]
767 fn test_aggregate_oracle_edges_2026_08() {
768 assert_eq!(run_agg(&SumFunc, &[int(1), int(2)]), int(3));
774 assert_eq!(run_agg(&SumFunc, &[null(), null()]), null());
775 assert_eq!(
776 run_agg(&TotalFunc, &[int(1), int(2)]),
777 SqliteValue::Float(3.0)
778 );
779 assert_eq!(
780 run_agg(&TotalFunc, &[null(), null()]),
781 SqliteValue::Float(0.0)
782 );
783 assert_float_eq(&run_agg(&AvgFunc, &[int(1), int(2)]), 1.5);
784 assert_eq!(run_agg(&CountFunc, &[int(1), null(), int(3)]), int(2));
785 assert_eq!(
786 run_agg(&AggMaxFunc, &[int(1), text("a"), null()]),
787 text("a")
788 );
789 assert_eq!(run_agg(&AggMinFunc, &[int(1), text("a"), null()]), int(1));
790 assert_eq!(
791 run_agg(&GroupConcatFunc, &[int(1), int(2), int(3)]),
792 text("1,2,3")
793 );
794
795 let sum = SumFunc;
799 let mut st = sum.initial_state();
800 sum.step(&mut st, &[int(i64::MAX)]).unwrap();
801 sum.step(&mut st, &[int(1)]).unwrap();
802 assert!(
803 sum.finalize(st).is_err(),
804 "sum() must error on i64 overflow"
805 );
806 let mut st2 = sum.initial_state();
809 sum.step(&mut st2, &[int(i64::MAX)]).unwrap();
810 sum.step(&mut st2, &[int(1)]).unwrap();
811 sum.step(&mut st2, &[float(0.5)]).unwrap();
812 assert!(
813 matches!(sum.finalize(st2), Ok(SqliteValue::Float(_))),
814 "sum() with a real present returns the float sum, not an overflow error"
815 );
816 }
817
818 #[test]
821 fn test_avg_basic() {
822 let r = run_agg(&AvgFunc, &[int(1), int(2), int(3), int(4), int(5)]);
823 assert_float_eq(&r, 3.0);
824 }
825
826 #[test]
827 fn test_avg_with_nulls() {
828 let r = run_agg(&AvgFunc, &[int(1), null(), int(3)]);
829 assert_float_eq(&r, 2.0);
830 }
831
832 #[test]
833 fn test_avg_empty() {
834 let r = run_agg(&AvgFunc, &[]);
835 assert_eq!(r, SqliteValue::Null);
836 }
837
838 #[test]
839 fn test_avg_returns_real() {
840 let r = run_agg(&AvgFunc, &[int(2), int(4)]);
841 assert!(matches!(r, SqliteValue::Float(_)));
842 }
843
844 #[test]
847 fn test_count_star() {
848 let mut state = CountStarFunc.initial_state();
850 CountStarFunc.step(&mut state, &[]).unwrap(); CountStarFunc.step(&mut state, &[]).unwrap(); CountStarFunc.step(&mut state, &[]).unwrap(); let r = CountStarFunc.finalize(state).unwrap();
854 assert_eq!(r, int(3));
855 }
856
857 #[test]
858 fn test_count_column() {
859 let r = run_agg(&CountFunc, &[int(1), null(), int(3), null(), int(5)]);
860 assert_eq!(r, int(3));
861 }
862
863 #[test]
864 fn test_count_empty() {
865 let r = run_agg(&CountFunc, &[]);
866 assert_eq!(r, int(0));
867 }
868
869 #[test]
872 fn test_group_concat_basic() {
873 let r = run_agg(&GroupConcatFunc, &[text("a"), text("b"), text("c")]);
874 assert_eq!(r, SqliteValue::Text("a,b,c".into()));
875 }
876
877 #[test]
878 fn test_group_concat_custom_sep() {
879 let rows = vec![
880 (text("a"), text("; ")),
881 (text("b"), text("; ")),
882 (text("c"), text("; ")),
883 ];
884 let r = run_agg2(&GroupConcatFunc, &rows);
885 assert_eq!(r, SqliteValue::Text("a; b; c".into()));
886 }
887
888 #[test]
889 fn test_group_concat_null_skipped() {
890 let r = run_agg(&GroupConcatFunc, &[text("a"), null(), text("c")]);
891 assert_eq!(r, SqliteValue::Text("a,c".into()));
892 }
893
894 #[test]
895 fn test_group_concat_empty() {
896 let r = run_agg(&GroupConcatFunc, &[]);
897 assert_eq!(r, SqliteValue::Null);
898 }
899
900 #[test]
901 fn test_group_concat_varying_separator() {
902 let rows = vec![
906 (text("a"), text("-")),
907 (text("b"), text("+")),
908 (text("c"), text("*")),
909 ];
910 let r = run_agg2(&GroupConcatFunc, &rows);
911 assert_eq!(r, SqliteValue::Text("a+b*c".into()));
912 }
913
914 #[test]
915 fn test_group_concat_single_value() {
916 let r = run_agg(&GroupConcatFunc, &[text("only")]);
917 assert_eq!(r, SqliteValue::Text("only".into()));
918 }
919
920 #[test]
921 fn test_group_concat_integer_values_coerced_to_text() {
922 let r = run_agg(&GroupConcatFunc, &[int(1), int(2), int(3)]);
923 assert_eq!(r, SqliteValue::Text("1,2,3".into()));
924 }
925
926 #[test]
927 #[ignore = "perf-only benchmark"]
928 fn perf_group_concat_text_rows() {
929 use std::hint::black_box;
930 use std::time::Instant;
931
932 const ROWS: usize = 200_000;
933 const REPEATS: usize = 5;
934
935 let rows: Vec<SqliteValue> = (0..ROWS).map(|_| text("payload")).collect();
936 let mut best_ns = u128::MAX;
937 let mut result_len = 0usize;
938
939 for _ in 0..REPEATS {
940 let started = Instant::now();
941 let result = black_box(run_agg(&GroupConcatFunc, black_box(rows.as_slice())));
942 let elapsed_ns = started.elapsed().as_nanos();
943 if elapsed_ns < best_ns {
944 best_ns = elapsed_ns;
945 }
946 result_len = match result {
947 SqliteValue::Text(text) => text.len(),
948 SqliteValue::Null
949 | SqliteValue::Integer(_)
950 | SqliteValue::Float(_)
951 | SqliteValue::Blob(_) => 0,
952 };
953 }
954
955 println!(
956 "group_concat_text_rows rows={ROWS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
957 );
958 }
959
960 #[test]
963 fn test_max_aggregate() {
964 let r = run_agg(&AggMaxFunc, &[int(3), int(7), int(1), int(5)]);
965 assert_eq!(r, int(7));
966 }
967
968 #[test]
969 fn test_max_aggregate_null_skipped() {
970 let r = run_agg(&AggMaxFunc, &[int(3), null(), int(7), null()]);
971 assert_eq!(r, int(7));
972 }
973
974 #[test]
975 fn test_max_aggregate_empty() {
976 let r = run_agg(&AggMaxFunc, &[]);
977 assert_eq!(r, SqliteValue::Null);
978 }
979
980 #[test]
983 fn test_min_aggregate() {
984 let r = run_agg(&AggMinFunc, &[int(3), int(7), int(1), int(5)]);
985 assert_eq!(r, int(1));
986 }
987
988 #[test]
989 fn test_min_aggregate_null_skipped() {
990 let r = run_agg(&AggMinFunc, &[int(3), null(), int(1), null()]);
991 assert_eq!(r, int(1));
992 }
993
994 #[test]
995 fn test_min_aggregate_empty() {
996 let r = run_agg(&AggMinFunc, &[]);
997 assert_eq!(r, SqliteValue::Null);
998 }
999
1000 #[test]
1003 fn test_sum_integers() {
1004 let r = run_agg(&SumFunc, &[int(1), int(2), int(3)]);
1005 assert_eq!(r, int(6));
1006 }
1007
1008 #[test]
1009 fn test_sum_reals() {
1010 let r = run_agg(&SumFunc, &[float(1.5), float(2.5)]);
1011 assert_float_eq(&r, 4.0);
1012 }
1013
1014 #[test]
1015 fn test_sum_empty_null() {
1016 let r = run_agg(&SumFunc, &[]);
1017 assert_eq!(r, SqliteValue::Null);
1018 }
1019
1020 #[test]
1021 fn test_sum_overflow_error() {
1022 let mut state = SumFunc.initial_state();
1023 SumFunc.step(&mut state, &[int(i64::MAX)]).unwrap();
1024 SumFunc.step(&mut state, &[int(1)]).unwrap();
1025 let err = SumFunc.finalize(state);
1026 assert!(err.is_err(), "sum should raise overflow error");
1027 }
1028
1029 #[test]
1030 fn test_sum_later_real_value_clears_integer_overflow_error() {
1031 let r = run_agg(&SumFunc, &[int(i64::MAX), int(1), float(0.5)]);
1032 assert_float_eq(&r, 9_223_372_036_854_776_000.0);
1033 }
1034
1035 #[test]
1036 fn test_sum_integer_text_preserves_overflow_error() -> Result<()> {
1037 let mut state = SumFunc.initial_state();
1038 SumFunc.step(&mut state, &[text("9223372036854775807")])?;
1039 SumFunc.step(&mut state, &[text("1")])?;
1040 let err = SumFunc.finalize(state);
1041 assert!(err.is_err(), "integer-text sum should raise overflow");
1042 Ok(())
1043 }
1044
1045 #[test]
1046 fn test_sum_integer_text_later_real_clears_overflow_error() {
1047 let r = run_agg(
1048 &SumFunc,
1049 &[text("9223372036854775807"), text("1"), text("0.5")],
1050 );
1051 assert_float_eq(&r, 9_223_372_036_854_776_000.0);
1052 }
1053
1054 #[test]
1055 fn test_sum_prefix_text_uses_real_accumulator() {
1056 let r = run_agg(&SumFunc, &[text("123abc"), int(1)]);
1057 assert_float_eq(&r, 124.0);
1058 }
1059
1060 #[test]
1061 fn test_sum_unicode_whitespace_text_uses_sqlite_ascii_space_rules() {
1062 let leading = run_agg(&SumFunc, &[text("\u{00a0}123"), int(1)]);
1063 assert_float_eq(&leading, 1.0);
1064
1065 let trailing = run_agg(&SumFunc, &[text("123\u{00a0}"), int(1)]);
1066 assert_float_eq(&trailing, 124.0);
1067 }
1068
1069 #[test]
1070 fn test_sum_null_skipped() {
1071 let r = run_agg(&SumFunc, &[int(1), null(), int(3)]);
1072 assert_eq!(r, int(4));
1073 }
1074
1075 #[test]
1078 fn test_total_basic() {
1079 let r = run_agg(&TotalFunc, &[int(1), int(2), int(3)]);
1080 assert_float_eq(&r, 6.0);
1081 }
1082
1083 #[test]
1084 fn test_total_empty_zero() {
1085 let r = run_agg(&TotalFunc, &[]);
1086 assert_float_eq(&r, 0.0);
1087 }
1088
1089 #[test]
1090 fn test_total_no_overflow() {
1091 let r = run_agg(&TotalFunc, &[int(i64::MAX), int(i64::MAX)]);
1093 assert!(matches!(r, SqliteValue::Float(_)));
1094 }
1095
1096 #[test]
1099 fn test_median_basic() {
1100 let r = run_agg(&MedianFunc, &[int(1), int(2), int(3), int(4), int(5)]);
1101 assert_float_eq(&r, 3.0);
1102 }
1103
1104 #[test]
1105 fn test_median_even() {
1106 let r = run_agg(&MedianFunc, &[int(1), int(2), int(3), int(4)]);
1107 assert_float_eq(&r, 2.5);
1108 }
1109
1110 #[test]
1111 fn test_median_null_skipped() {
1112 let r = run_agg(&MedianFunc, &[int(1), null(), int(3)]);
1113 assert_float_eq(&r, 2.0);
1114 }
1115
1116 #[test]
1117 fn test_median_empty() {
1118 let r = run_agg(&MedianFunc, &[]);
1119 assert_eq!(r, SqliteValue::Null);
1120 }
1121
1122 #[test]
1125 fn test_percentile_50() {
1126 let rows: Vec<(SqliteValue, SqliteValue)> = vec![
1128 (int(1), float(50.0)),
1129 (int(2), float(50.0)),
1130 (int(3), float(50.0)),
1131 (int(4), float(50.0)),
1132 (int(5), float(50.0)),
1133 ];
1134 let r = run_agg2(&PercentileFunc, &rows);
1135 assert_float_eq(&r, 3.0);
1136 }
1137
1138 #[test]
1139 fn test_percentile_0() {
1140 let rows: Vec<(SqliteValue, SqliteValue)> = vec![
1141 (int(10), float(0.0)),
1142 (int(20), float(0.0)),
1143 (int(30), float(0.0)),
1144 ];
1145 let r = run_agg2(&PercentileFunc, &rows);
1146 assert_float_eq(&r, 10.0);
1147 }
1148
1149 #[test]
1150 fn test_percentile_100() {
1151 let rows: Vec<(SqliteValue, SqliteValue)> = vec![
1152 (int(10), float(100.0)),
1153 (int(20), float(100.0)),
1154 (int(30), float(100.0)),
1155 ];
1156 let r = run_agg2(&PercentileFunc, &rows);
1157 assert_float_eq(&r, 30.0);
1158 }
1159
1160 #[test]
1163 fn test_percentile_cont_basic() {
1164 let rows: Vec<(SqliteValue, SqliteValue)> = vec![
1165 (int(1), float(0.5)),
1166 (int(2), float(0.5)),
1167 (int(3), float(0.5)),
1168 (int(4), float(0.5)),
1169 (int(5), float(0.5)),
1170 ];
1171 let r = run_agg2(&PercentileContFunc, &rows);
1172 assert_float_eq(&r, 3.0);
1173 }
1174
1175 #[test]
1178 fn test_percentile_disc_basic() {
1179 let rows: Vec<(SqliteValue, SqliteValue)> = vec![
1180 (int(1), float(0.5)),
1181 (int(2), float(0.5)),
1182 (int(3), float(0.5)),
1183 (int(4), float(0.5)),
1184 (int(5), float(0.5)),
1185 ];
1186 let r = run_agg2(&PercentileDiscFunc, &rows);
1187 match r {
1189 SqliteValue::Float(v) => {
1190 assert!(
1192 [1.0, 2.0, 3.0, 4.0, 5.0].contains(&v),
1193 "expected actual value, got {v}"
1194 );
1195 }
1196 other => {
1197 assert!(
1198 matches!(other, SqliteValue::Float(_)),
1199 "expected Float, got {other:?}"
1200 );
1201 }
1202 }
1203 }
1204
1205 #[test]
1206 fn test_percentile_disc_no_interpolation() {
1207 let rows: Vec<(SqliteValue, SqliteValue)> = vec![
1209 (int(10), float(0.5)),
1210 (int(20), float(0.5)),
1211 (int(30), float(0.5)),
1212 (int(40), float(0.5)),
1213 ];
1214 let r = run_agg2(&PercentileDiscFunc, &rows);
1215 match r {
1216 SqliteValue::Float(v) => {
1217 assert!(
1219 [10.0, 20.0, 30.0, 40.0].contains(&v),
1220 "disc must not interpolate: got {v}"
1221 );
1222 }
1223 other => {
1224 assert!(
1225 matches!(other, SqliteValue::Float(_)),
1226 "expected Float, got {other:?}"
1227 );
1228 }
1229 }
1230 }
1231
1232 #[test]
1235 fn test_string_agg_alias() {
1236 let mut reg = FunctionRegistry::new();
1237 register_aggregate_builtins(&mut reg);
1238 let sa = reg
1239 .find_aggregate("string_agg", 2)
1240 .expect("string_agg registered");
1241 let mut state = sa.initial_state();
1242 sa.step(&mut state, &[text("a"), text(",")]).unwrap();
1243 sa.step(&mut state, &[text("b"), text(",")]).unwrap();
1244 let r = sa.finalize(state).unwrap();
1245 assert_eq!(r, SqliteValue::Text("a,b".into()));
1246 }
1247
1248 #[test]
1251 fn test_register_aggregate_builtins_all_present() {
1252 let mut reg = FunctionRegistry::new();
1253 register_aggregate_builtins(&mut reg);
1254
1255 let expected = [
1256 ("avg", 1),
1257 ("count", 0), ("count", 1), ("max", 1),
1260 ("min", 1),
1261 ("sum", 1),
1262 ("total", 1),
1263 ("median", 1),
1264 ("percentile", 2),
1265 ("percentile_cont", 2),
1266 ("percentile_disc", 2),
1267 ("string_agg", 2),
1268 ];
1269
1270 for (name, arity) in expected {
1271 assert!(
1272 reg.find_aggregate(name, arity).is_some(),
1273 "aggregate '{name}/{arity}' not registered"
1274 );
1275 }
1276
1277 assert!(reg.find_aggregate("group_concat", 1).is_some());
1279 assert!(reg.find_aggregate("group_concat", 2).is_some());
1280
1281 let group_concat_zero = reg.find_aggregate("group_concat", 0).unwrap();
1282 let err = group_concat_zero
1283 .finalize(group_concat_zero.initial_state())
1284 .expect_err("group_concat() should reject zero arguments");
1285 assert!(
1286 matches!(&err, FrankenError::FunctionError(message)
1287 if message == "wrong number of arguments to function group_concat()"),
1288 "unexpected error: {err:?}"
1289 );
1290 }
1291
1292 #[test]
1295 fn test_e2e_registry_invoke_aggregates() {
1296 let mut reg = FunctionRegistry::new();
1297 register_aggregate_builtins(&mut reg);
1298
1299 let avg = reg.find_aggregate("avg", 1).unwrap();
1301 let mut state = avg.initial_state();
1302 avg.step(&mut state, &[int(10)]).unwrap();
1303 avg.step(&mut state, &[int(20)]).unwrap();
1304 avg.step(&mut state, &[int(30)]).unwrap();
1305 let r = avg.finalize(state).unwrap();
1306 assert_float_eq(&r, 20.0);
1307
1308 let sum = reg.find_aggregate("sum", 1).unwrap();
1310 let mut state = sum.initial_state();
1311 sum.step(&mut state, &[int(1)]).unwrap();
1312 sum.step(&mut state, &[int(2)]).unwrap();
1313 sum.step(&mut state, &[int(3)]).unwrap();
1314 let r = sum.finalize(state).unwrap();
1315 assert_eq!(r, int(6));
1316 }
1317}