1use std::collections::HashMap;
7use std::fmt;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum AggregateFunc {
12 Count { distinct: bool },
14 Sum,
16 Avg,
18 Min,
20 Max,
22 Sample,
24 GroupConcat { separator: String },
26 CountAll,
28}
29
30#[derive(Debug, Clone, PartialEq)]
32pub enum AggregateValue {
33 Integer(i64),
35 Float(f64),
37 Text(String),
39 Null,
41}
42
43impl fmt::Display for AggregateValue {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 AggregateValue::Integer(n) => write!(f, "{n}"),
47 AggregateValue::Float(v) => write!(f, "{v}"),
48 AggregateValue::Text(s) => write!(f, "{s}"),
49 AggregateValue::Null => write!(f, "NULL"),
50 }
51 }
52}
53
54impl PartialOrd for AggregateValue {
55 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
56 use std::cmp::Ordering;
57 match (self, other) {
58 (AggregateValue::Null, AggregateValue::Null) => Some(Ordering::Equal),
59 (AggregateValue::Null, _) => Some(Ordering::Less),
60 (_, AggregateValue::Null) => Some(Ordering::Greater),
61 (AggregateValue::Integer(a), AggregateValue::Integer(b)) => a.partial_cmp(b),
62 (AggregateValue::Integer(a), AggregateValue::Float(b)) => (*a as f64).partial_cmp(b),
63 (AggregateValue::Float(a), AggregateValue::Integer(b)) => a.partial_cmp(&(*b as f64)),
64 (AggregateValue::Float(a), AggregateValue::Float(b)) => a.partial_cmp(b),
65 (AggregateValue::Text(a), AggregateValue::Text(b)) => a.partial_cmp(b),
66 (AggregateValue::Integer(_), AggregateValue::Text(_)) => Some(Ordering::Less),
68 (AggregateValue::Float(_), AggregateValue::Text(_)) => Some(Ordering::Less),
69 (AggregateValue::Text(_), AggregateValue::Integer(_)) => Some(Ordering::Greater),
70 (AggregateValue::Text(_), AggregateValue::Float(_)) => Some(Ordering::Greater),
71 }
72 }
73}
74
75impl std::ops::Add for AggregateValue {
76 type Output = AggregateValue;
77 fn add(self, rhs: Self) -> Self::Output {
78 match (self, rhs) {
79 (AggregateValue::Null, _) | (_, AggregateValue::Null) => AggregateValue::Null,
80 (AggregateValue::Integer(a), AggregateValue::Integer(b)) => {
81 AggregateValue::Integer(a.saturating_add(b))
82 }
83 (AggregateValue::Integer(a), AggregateValue::Float(b)) => {
84 AggregateValue::Float(a as f64 + b)
85 }
86 (AggregateValue::Float(a), AggregateValue::Integer(b)) => {
87 AggregateValue::Float(a + b as f64)
88 }
89 (AggregateValue::Float(a), AggregateValue::Float(b)) => AggregateValue::Float(a + b),
90 _ => AggregateValue::Null,
91 }
92 }
93}
94
95impl std::ops::Sub for AggregateValue {
96 type Output = AggregateValue;
97 fn sub(self, rhs: Self) -> Self::Output {
98 match (self, rhs) {
99 (AggregateValue::Null, _) | (_, AggregateValue::Null) => AggregateValue::Null,
100 (AggregateValue::Integer(a), AggregateValue::Integer(b)) => {
101 AggregateValue::Integer(a.saturating_sub(b))
102 }
103 (AggregateValue::Integer(a), AggregateValue::Float(b)) => {
104 AggregateValue::Float(a as f64 - b)
105 }
106 (AggregateValue::Float(a), AggregateValue::Integer(b)) => {
107 AggregateValue::Float(a - b as f64)
108 }
109 (AggregateValue::Float(a), AggregateValue::Float(b)) => AggregateValue::Float(a - b),
110 _ => AggregateValue::Null,
111 }
112 }
113}
114
115impl std::ops::Mul for AggregateValue {
116 type Output = AggregateValue;
117 fn mul(self, rhs: Self) -> Self::Output {
118 match (self, rhs) {
119 (AggregateValue::Null, _) | (_, AggregateValue::Null) => AggregateValue::Null,
120 (AggregateValue::Integer(a), AggregateValue::Integer(b)) => {
121 AggregateValue::Integer(a.saturating_mul(b))
122 }
123 (AggregateValue::Integer(a), AggregateValue::Float(b)) => {
124 AggregateValue::Float(a as f64 * b)
125 }
126 (AggregateValue::Float(a), AggregateValue::Integer(b)) => {
127 AggregateValue::Float(a * b as f64)
128 }
129 (AggregateValue::Float(a), AggregateValue::Float(b)) => AggregateValue::Float(a * b),
130 _ => AggregateValue::Null,
131 }
132 }
133}
134
135pub type GroupKey = Vec<(String, String)>;
137
138#[derive(Debug, Clone)]
140pub struct AggregateResult {
141 pub group_key: GroupKey,
143 pub bindings: HashMap<String, AggregateValue>,
145}
146
147pub struct AggregateExecutor;
149
150impl AggregateExecutor {
151 pub fn group_by(
156 rows: &[HashMap<String, String>],
157 group_vars: &[String],
158 ) -> HashMap<GroupKey, Vec<HashMap<String, String>>> {
159 let mut groups: HashMap<GroupKey, Vec<HashMap<String, String>>> = HashMap::new();
160 for row in rows {
161 let key: GroupKey = group_vars
162 .iter()
163 .map(|v| {
164 let val = row.get(v).cloned().unwrap_or_default();
165 (v.clone(), val)
166 })
167 .collect();
168 groups.entry(key).or_default().push(row.clone());
169 }
170 if rows.is_empty() && group_vars.is_empty() {
172 groups.entry(vec![]).or_default();
173 }
174 groups
175 }
176
177 pub fn apply(
181 func: &AggregateFunc,
182 var: &str,
183 group: &[HashMap<String, String>],
184 ) -> AggregateValue {
185 match func {
186 AggregateFunc::CountAll => AggregateValue::Integer(group.len() as i64),
187
188 AggregateFunc::Count { distinct } => {
189 let values: Vec<&str> = group
190 .iter()
191 .filter_map(|row| row.get(var).map(|s| s.as_str()))
192 .collect();
193 if *distinct {
194 let mut seen = std::collections::HashSet::new();
195 let count = values.into_iter().filter(|v| seen.insert(*v)).count();
196 AggregateValue::Integer(count as i64)
197 } else {
198 AggregateValue::Integer(values.len() as i64)
199 }
200 }
201
202 AggregateFunc::Sum => {
203 let nums: Vec<f64> = group
204 .iter()
205 .filter_map(|row| row.get(var).and_then(|s| s.parse::<f64>().ok()))
206 .collect();
207 if nums.is_empty() {
208 AggregateValue::Null
209 } else {
210 let sum: f64 = nums.iter().sum();
211 if nums.iter().all(|n| n.fract() == 0.0) {
213 AggregateValue::Integer(sum as i64)
214 } else {
215 AggregateValue::Float(sum)
216 }
217 }
218 }
219
220 AggregateFunc::Avg => {
221 let nums: Vec<f64> = group
222 .iter()
223 .filter_map(|row| row.get(var).and_then(|s| s.parse::<f64>().ok()))
224 .collect();
225 if nums.is_empty() {
226 AggregateValue::Null
227 } else {
228 let avg = nums.iter().sum::<f64>() / nums.len() as f64;
229 AggregateValue::Float(avg)
230 }
231 }
232
233 AggregateFunc::Min => {
234 let mut min_val: Option<AggregateValue> = None;
235 for row in group {
236 if let Some(s) = row.get(var) {
237 let v = if let Ok(n) = s.parse::<f64>() {
238 if n.fract() == 0.0 {
239 AggregateValue::Integer(n as i64)
240 } else {
241 AggregateValue::Float(n)
242 }
243 } else {
244 AggregateValue::Text(s.clone())
245 };
246 min_val = Some(match min_val {
247 None => v,
248 Some(cur) => {
249 if v < cur {
250 v
251 } else {
252 cur
253 }
254 }
255 });
256 }
257 }
258 min_val.unwrap_or(AggregateValue::Null)
259 }
260
261 AggregateFunc::Max => {
262 let mut max_val: Option<AggregateValue> = None;
263 for row in group {
264 if let Some(s) = row.get(var) {
265 let v = if let Ok(n) = s.parse::<f64>() {
266 if n.fract() == 0.0 {
267 AggregateValue::Integer(n as i64)
268 } else {
269 AggregateValue::Float(n)
270 }
271 } else {
272 AggregateValue::Text(s.clone())
273 };
274 max_val = Some(match max_val {
275 None => v,
276 Some(cur) => {
277 if v > cur {
278 v
279 } else {
280 cur
281 }
282 }
283 });
284 }
285 }
286 max_val.unwrap_or(AggregateValue::Null)
287 }
288
289 AggregateFunc::Sample => {
290 for row in group {
291 if let Some(s) = row.get(var) {
292 return AggregateValue::Text(s.clone());
293 }
294 }
295 AggregateValue::Null
296 }
297
298 AggregateFunc::GroupConcat { separator } => {
299 let parts: Vec<&str> = group
300 .iter()
301 .filter_map(|row| row.get(var).map(|s| s.as_str()))
302 .collect();
303 if parts.is_empty() {
304 AggregateValue::Null
305 } else {
306 AggregateValue::Text(parts.join(separator.as_str()))
307 }
308 }
309 }
310 }
311
312 pub fn execute(
316 rows: &[HashMap<String, String>],
317 group_vars: &[String],
318 aggregates: &[(String, AggregateFunc, String)],
319 ) -> Vec<AggregateResult> {
320 let groups = Self::group_by(rows, group_vars);
321 let mut results: Vec<AggregateResult> = groups
322 .into_iter()
323 .map(|(key, group_rows)| {
324 let mut bindings = HashMap::new();
325 for (input_var, func, output_var) in aggregates {
326 let value = Self::apply(func, input_var, &group_rows);
327 bindings.insert(output_var.clone(), value);
328 }
329 AggregateResult {
330 group_key: key,
331 bindings,
332 }
333 })
334 .collect();
335
336 results.sort_by(|a, b| a.group_key.cmp(&b.group_key));
338 results
339 }
340
341 pub fn having_filter(
346 results: &[AggregateResult],
347 var: &str,
348 op: &str,
349 value: &str,
350 ) -> Vec<AggregateResult> {
351 results
352 .iter()
353 .filter(|r| {
354 let bound = r.bindings.get(var);
355 Self::compare_value(bound, op, value)
356 })
357 .cloned()
358 .collect()
359 }
360
361 fn compare_value(bound: Option<&AggregateValue>, op: &str, threshold: &str) -> bool {
363 let Some(av) = bound else {
364 return false;
365 };
366
367 if let Ok(threshold_f) = threshold.parse::<f64>() {
369 let av_f = match av {
370 AggregateValue::Integer(n) => Some(*n as f64),
371 AggregateValue::Float(f) => Some(*f),
372 AggregateValue::Text(t) => t.parse::<f64>().ok(),
373 AggregateValue::Null => None,
374 };
375 if let Some(av_f) = av_f {
376 return match op {
377 "=" => (av_f - threshold_f).abs() < f64::EPSILON,
378 "!=" => (av_f - threshold_f).abs() >= f64::EPSILON,
379 "<" => av_f < threshold_f,
380 ">" => av_f > threshold_f,
381 "<=" => av_f <= threshold_f,
382 ">=" => av_f >= threshold_f,
383 _ => false,
384 };
385 }
386 }
387
388 let av_s = match av {
390 AggregateValue::Text(t) => t.as_str(),
391 AggregateValue::Integer(n) => {
392 return match op {
394 "=" => n.to_string() == threshold,
395 "!=" => n.to_string() != threshold,
396 _ => false,
397 };
398 }
399 AggregateValue::Float(f) => {
400 return match op {
401 "=" => f.to_string() == threshold,
402 "!=" => f.to_string() != threshold,
403 _ => false,
404 };
405 }
406 AggregateValue::Null => return false,
407 };
408
409 match op {
410 "=" => av_s == threshold,
411 "!=" => av_s != threshold,
412 "<" => av_s < threshold,
413 ">" => av_s > threshold,
414 "<=" => av_s <= threshold,
415 ">=" => av_s >= threshold,
416 _ => false,
417 }
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 fn row(pairs: &[(&str, &str)]) -> HashMap<String, String> {
426 pairs
427 .iter()
428 .map(|(k, v)| (k.to_string(), v.to_string()))
429 .collect()
430 }
431
432 #[test]
435 fn test_display_integer() {
436 assert_eq!(AggregateValue::Integer(42).to_string(), "42");
437 }
438
439 #[test]
440 fn test_display_float() {
441 assert_eq!(AggregateValue::Float(2.71).to_string(), "2.71");
442 }
443
444 #[test]
445 fn test_display_text() {
446 assert_eq!(
447 AggregateValue::Text("hello".to_string()).to_string(),
448 "hello"
449 );
450 }
451
452 #[test]
453 fn test_display_null() {
454 assert_eq!(AggregateValue::Null.to_string(), "NULL");
455 }
456
457 #[test]
460 fn test_ordering_null_less_than_integer() {
461 assert!(AggregateValue::Null < AggregateValue::Integer(0));
462 }
463
464 #[test]
465 fn test_ordering_integer_less_than_float() {
466 assert!(AggregateValue::Integer(1) < AggregateValue::Float(1.5));
467 }
468
469 #[test]
470 fn test_ordering_float_less_than_text() {
471 assert!(AggregateValue::Float(99.9) < AggregateValue::Text("a".to_string()));
472 }
473
474 #[test]
475 fn test_ordering_integers() {
476 assert!(AggregateValue::Integer(1) < AggregateValue::Integer(2));
477 assert!(AggregateValue::Integer(2) > AggregateValue::Integer(1));
478 }
479
480 #[test]
483 fn test_add_integers() {
484 let r = AggregateValue::Integer(3) + AggregateValue::Integer(4);
485 assert_eq!(r, AggregateValue::Integer(7));
486 }
487
488 #[test]
489 fn test_add_float_and_integer() {
490 let r = AggregateValue::Float(1.5) + AggregateValue::Integer(2);
491 assert_eq!(r, AggregateValue::Float(3.5));
492 }
493
494 #[test]
495 fn test_add_null_propagates() {
496 let r = AggregateValue::Null + AggregateValue::Integer(5);
497 assert_eq!(r, AggregateValue::Null);
498 }
499
500 #[test]
501 fn test_sub_integers() {
502 let r = AggregateValue::Integer(10) - AggregateValue::Integer(3);
503 assert_eq!(r, AggregateValue::Integer(7));
504 }
505
506 #[test]
507 fn test_mul_integers() {
508 let r = AggregateValue::Integer(4) * AggregateValue::Integer(5);
509 assert_eq!(r, AggregateValue::Integer(20));
510 }
511
512 #[test]
515 fn test_group_by_empty_rows() {
516 let groups = AggregateExecutor::group_by(&[], &["x".to_string()]);
517 assert!(groups.is_empty());
518 }
519
520 #[test]
521 fn test_group_by_no_group_vars_single_group() {
522 let rows = vec![row(&[("x", "1")]), row(&[("x", "2")])];
523 let groups = AggregateExecutor::group_by(&rows, &[]);
524 assert_eq!(groups.len(), 1);
525 let group = groups.get(&vec![]).expect("single empty-key group");
526 assert_eq!(group.len(), 2);
527 }
528
529 #[test]
530 fn test_group_by_single_var() {
531 let rows = vec![
532 row(&[("type", "a"), ("val", "1")]),
533 row(&[("type", "b"), ("val", "2")]),
534 row(&[("type", "a"), ("val", "3")]),
535 ];
536 let groups = AggregateExecutor::group_by(&rows, &["type".to_string()]);
537 assert_eq!(groups.len(), 2);
538 let a_key = vec![("type".to_string(), "a".to_string())];
539 assert_eq!(groups[&a_key].len(), 2);
540 }
541
542 #[test]
543 fn test_group_by_multiple_vars() {
544 let rows = vec![
545 row(&[("a", "1"), ("b", "x")]),
546 row(&[("a", "1"), ("b", "y")]),
547 row(&[("a", "2"), ("b", "x")]),
548 ];
549 let groups = AggregateExecutor::group_by(&rows, &["a".to_string(), "b".to_string()]);
550 assert_eq!(groups.len(), 3);
551 }
552
553 #[test]
556 fn test_count_basic() {
557 let group = vec![row(&[("x", "a")]), row(&[("x", "b")]), row(&[])];
558 let r = AggregateExecutor::apply(&AggregateFunc::Count { distinct: false }, "x", &group);
559 assert_eq!(r, AggregateValue::Integer(2));
560 }
561
562 #[test]
563 fn test_count_distinct() {
564 let group = vec![row(&[("x", "a")]), row(&[("x", "a")]), row(&[("x", "b")])];
565 let r = AggregateExecutor::apply(&AggregateFunc::Count { distinct: true }, "x", &group);
566 assert_eq!(r, AggregateValue::Integer(2));
567 }
568
569 #[test]
570 fn test_count_all() {
571 let group = vec![row(&[("x", "a")]), row(&[])];
572 let r = AggregateExecutor::apply(&AggregateFunc::CountAll, "x", &group);
573 assert_eq!(r, AggregateValue::Integer(2));
574 }
575
576 #[test]
577 fn test_sum_integers() {
578 let group = vec![row(&[("n", "10")]), row(&[("n", "20")]), row(&[("n", "5")])];
579 let r = AggregateExecutor::apply(&AggregateFunc::Sum, "n", &group);
580 assert_eq!(r, AggregateValue::Integer(35));
581 }
582
583 #[test]
584 fn test_sum_floats() {
585 let group = vec![row(&[("n", "1.5")]), row(&[("n", "2.5")])];
586 let r = AggregateExecutor::apply(&AggregateFunc::Sum, "n", &group);
587 assert_eq!(r, AggregateValue::Float(4.0));
588 }
589
590 #[test]
591 fn test_sum_empty() {
592 let group: Vec<HashMap<String, String>> = vec![];
593 let r = AggregateExecutor::apply(&AggregateFunc::Sum, "n", &group);
594 assert_eq!(r, AggregateValue::Null);
595 }
596
597 #[test]
598 fn test_avg_basic() {
599 let group = vec![row(&[("n", "10")]), row(&[("n", "20")])];
600 let r = AggregateExecutor::apply(&AggregateFunc::Avg, "n", &group);
601 assert_eq!(r, AggregateValue::Float(15.0));
602 }
603
604 #[test]
605 fn test_avg_empty() {
606 let group: Vec<HashMap<String, String>> = vec![];
607 let r = AggregateExecutor::apply(&AggregateFunc::Avg, "n", &group);
608 assert_eq!(r, AggregateValue::Null);
609 }
610
611 #[test]
612 fn test_min_numeric() {
613 let group = vec![row(&[("n", "5")]), row(&[("n", "2")]), row(&[("n", "8")])];
614 let r = AggregateExecutor::apply(&AggregateFunc::Min, "n", &group);
615 assert_eq!(r, AggregateValue::Integer(2));
616 }
617
618 #[test]
619 fn test_max_numeric() {
620 let group = vec![row(&[("n", "5")]), row(&[("n", "2")]), row(&[("n", "8")])];
621 let r = AggregateExecutor::apply(&AggregateFunc::Max, "n", &group);
622 assert_eq!(r, AggregateValue::Integer(8));
623 }
624
625 #[test]
626 fn test_min_empty() {
627 let group: Vec<HashMap<String, String>> = vec![];
628 let r = AggregateExecutor::apply(&AggregateFunc::Min, "n", &group);
629 assert_eq!(r, AggregateValue::Null);
630 }
631
632 #[test]
633 fn test_min_text() {
634 let group = vec![row(&[("s", "banana")]), row(&[("s", "apple")])];
635 let r = AggregateExecutor::apply(&AggregateFunc::Min, "s", &group);
636 assert_eq!(r, AggregateValue::Text("apple".to_string()));
637 }
638
639 #[test]
640 fn test_max_text() {
641 let group = vec![row(&[("s", "banana")]), row(&[("s", "apple")])];
642 let r = AggregateExecutor::apply(&AggregateFunc::Max, "s", &group);
643 assert_eq!(r, AggregateValue::Text("banana".to_string()));
644 }
645
646 #[test]
647 fn test_sample_returns_first_non_null() {
648 let group = vec![row(&[]), row(&[("x", "second")]), row(&[("x", "third")])];
649 let r = AggregateExecutor::apply(&AggregateFunc::Sample, "x", &group);
650 assert_eq!(r, AggregateValue::Text("second".to_string()));
651 }
652
653 #[test]
654 fn test_sample_empty() {
655 let group: Vec<HashMap<String, String>> = vec![];
656 let r = AggregateExecutor::apply(&AggregateFunc::Sample, "x", &group);
657 assert_eq!(r, AggregateValue::Null);
658 }
659
660 #[test]
661 fn test_group_concat_default_separator() {
662 let group = vec![row(&[("x", "a")]), row(&[("x", "b")]), row(&[("x", "c")])];
663 let r = AggregateExecutor::apply(
664 &AggregateFunc::GroupConcat {
665 separator: " ".to_string(),
666 },
667 "x",
668 &group,
669 );
670 assert_eq!(r, AggregateValue::Text("a b c".to_string()));
671 }
672
673 #[test]
674 fn test_group_concat_custom_separator() {
675 let group = vec![row(&[("x", "a")]), row(&[("x", "b")])];
676 let r = AggregateExecutor::apply(
677 &AggregateFunc::GroupConcat {
678 separator: ",".to_string(),
679 },
680 "x",
681 &group,
682 );
683 assert_eq!(r, AggregateValue::Text("a,b".to_string()));
684 }
685
686 #[test]
687 fn test_group_concat_empty() {
688 let group: Vec<HashMap<String, String>> = vec![];
689 let r = AggregateExecutor::apply(
690 &AggregateFunc::GroupConcat {
691 separator: ",".to_string(),
692 },
693 "x",
694 &group,
695 );
696 assert_eq!(r, AggregateValue::Null);
697 }
698
699 #[test]
702 fn test_execute_grouped_count() {
703 let rows = vec![
704 row(&[("type", "a"), ("val", "1")]),
705 row(&[("type", "a"), ("val", "2")]),
706 row(&[("type", "b"), ("val", "3")]),
707 ];
708 let aggs = vec![(
709 "val".to_string(),
710 AggregateFunc::Count { distinct: false },
711 "cnt".to_string(),
712 )];
713 let results = AggregateExecutor::execute(&rows, &["type".to_string()], &aggs);
714 assert_eq!(results.len(), 2);
715 assert_eq!(
717 results[0].bindings.get("cnt"),
718 Some(&AggregateValue::Integer(2))
719 );
720 assert_eq!(
721 results[1].bindings.get("cnt"),
722 Some(&AggregateValue::Integer(1))
723 );
724 }
725
726 #[test]
727 fn test_execute_no_group_vars() {
728 let rows = vec![row(&[("n", "10")]), row(&[("n", "20")])];
729 let aggs = vec![("n".to_string(), AggregateFunc::Sum, "total".to_string())];
730 let results = AggregateExecutor::execute(&rows, &[], &aggs);
731 assert_eq!(results.len(), 1);
732 assert_eq!(
733 results[0].bindings.get("total"),
734 Some(&AggregateValue::Integer(30))
735 );
736 }
737
738 #[test]
739 fn test_execute_multiple_aggregates() {
740 let rows = vec![
741 row(&[("g", "x"), ("n", "10")]),
742 row(&[("g", "x"), ("n", "20")]),
743 ];
744 let aggs = vec![
745 ("n".to_string(), AggregateFunc::Min, "mn".to_string()),
746 ("n".to_string(), AggregateFunc::Max, "mx".to_string()),
747 ("n".to_string(), AggregateFunc::Avg, "av".to_string()),
748 ];
749 let results = AggregateExecutor::execute(&rows, &["g".to_string()], &aggs);
750 assert_eq!(results.len(), 1);
751 assert_eq!(
752 results[0].bindings.get("mn"),
753 Some(&AggregateValue::Integer(10))
754 );
755 assert_eq!(
756 results[0].bindings.get("mx"),
757 Some(&AggregateValue::Integer(20))
758 );
759 assert_eq!(
760 results[0].bindings.get("av"),
761 Some(&AggregateValue::Float(15.0))
762 );
763 }
764
765 #[test]
766 fn test_execute_sorted_deterministic() {
767 let rows = vec![
768 row(&[("g", "c"), ("n", "1")]),
769 row(&[("g", "a"), ("n", "2")]),
770 row(&[("g", "b"), ("n", "3")]),
771 ];
772 let aggs = vec![("n".to_string(), AggregateFunc::Sum, "s".to_string())];
773 let results = AggregateExecutor::execute(&rows, &["g".to_string()], &aggs);
774 assert_eq!(results[0].group_key[0].1, "a");
775 assert_eq!(results[1].group_key[0].1, "b");
776 assert_eq!(results[2].group_key[0].1, "c");
777 }
778
779 #[test]
782 fn test_having_eq() {
783 let rows = vec![row(&[("n", "1")]), row(&[("n", "2")]), row(&[("n", "2")])];
784 let aggs = vec![(
785 "n".to_string(),
786 AggregateFunc::Count { distinct: false },
787 "cnt".to_string(),
788 )];
789 let results = AggregateExecutor::execute(&rows, &["n".to_string()], &aggs);
790 let filtered = AggregateExecutor::having_filter(&results, "cnt", "=", "2");
791 assert_eq!(filtered.len(), 1);
792 }
793
794 #[test]
795 fn test_having_neq() {
796 let rows = vec![row(&[("n", "1")]), row(&[("n", "2")]), row(&[("n", "2")])];
797 let aggs = vec![(
798 "n".to_string(),
799 AggregateFunc::Count { distinct: false },
800 "cnt".to_string(),
801 )];
802 let results = AggregateExecutor::execute(&rows, &["n".to_string()], &aggs);
803 let filtered = AggregateExecutor::having_filter(&results, "cnt", "!=", "1");
804 assert_eq!(filtered.len(), 1);
805 }
806
807 #[test]
808 fn test_having_gt() {
809 let rows = vec![
810 row(&[("g", "a"), ("n", "10")]),
811 row(&[("g", "a"), ("n", "20")]),
812 row(&[("g", "b"), ("n", "5")]),
813 ];
814 let aggs = vec![("n".to_string(), AggregateFunc::Sum, "s".to_string())];
815 let results = AggregateExecutor::execute(&rows, &["g".to_string()], &aggs);
816 let filtered = AggregateExecutor::having_filter(&results, "s", ">", "10");
817 assert_eq!(filtered.len(), 1);
818 assert_eq!(filtered[0].group_key[0].1, "a");
819 }
820
821 #[test]
822 fn test_having_lt() {
823 let rows = vec![
824 row(&[("g", "a"), ("n", "10")]),
825 row(&[("g", "b"), ("n", "5")]),
826 ];
827 let aggs = vec![("n".to_string(), AggregateFunc::Sum, "s".to_string())];
828 let results = AggregateExecutor::execute(&rows, &["g".to_string()], &aggs);
829 let filtered = AggregateExecutor::having_filter(&results, "s", "<", "8");
830 assert_eq!(filtered.len(), 1);
831 assert_eq!(filtered[0].group_key[0].1, "b");
832 }
833
834 #[test]
835 fn test_having_lte() {
836 let rows = vec![
837 row(&[("g", "a"), ("n", "10")]),
838 row(&[("g", "b"), ("n", "5")]),
839 ];
840 let aggs = vec![("n".to_string(), AggregateFunc::Sum, "s".to_string())];
841 let results = AggregateExecutor::execute(&rows, &["g".to_string()], &aggs);
842 let filtered = AggregateExecutor::having_filter(&results, "s", "<=", "10");
843 assert_eq!(filtered.len(), 2);
844 }
845
846 #[test]
847 fn test_having_gte() {
848 let rows = vec![
849 row(&[("g", "a"), ("n", "10")]),
850 row(&[("g", "b"), ("n", "5")]),
851 ];
852 let aggs = vec![("n".to_string(), AggregateFunc::Sum, "s".to_string())];
853 let results = AggregateExecutor::execute(&rows, &["g".to_string()], &aggs);
854 let filtered = AggregateExecutor::having_filter(&results, "s", ">=", "10");
855 assert_eq!(filtered.len(), 1);
856 assert_eq!(filtered[0].group_key[0].1, "a");
857 }
858
859 #[test]
860 fn test_having_no_match() {
861 let rows = vec![row(&[("n", "5")])];
862 let aggs = vec![("n".to_string(), AggregateFunc::Sum, "s".to_string())];
863 let results = AggregateExecutor::execute(&rows, &[], &aggs);
864 let filtered = AggregateExecutor::having_filter(&results, "s", ">", "100");
865 assert!(filtered.is_empty());
866 }
867
868 #[test]
869 fn test_execute_empty_rows_no_group_vars() {
870 let rows: Vec<HashMap<String, String>> = vec![];
871 let aggs = vec![("n".to_string(), AggregateFunc::Sum, "s".to_string())];
872 let results = AggregateExecutor::execute(&rows, &[], &aggs);
873 assert_eq!(results.len(), 1);
874 assert_eq!(results[0].bindings.get("s"), Some(&AggregateValue::Null));
875 }
876
877 #[test]
878 fn test_count_distinct_all_unique() {
879 let group = vec![row(&[("x", "a")]), row(&[("x", "b")]), row(&[("x", "c")])];
880 let r = AggregateExecutor::apply(&AggregateFunc::Count { distinct: true }, "x", &group);
881 assert_eq!(r, AggregateValue::Integer(3));
882 }
883
884 #[test]
885 fn test_group_concat_skips_nulls() {
886 let group = vec![row(&[("x", "a")]), row(&[]), row(&[("x", "b")])];
887 let r = AggregateExecutor::apply(
888 &AggregateFunc::GroupConcat {
889 separator: "-".to_string(),
890 },
891 "x",
892 &group,
893 );
894 assert_eq!(r, AggregateValue::Text("a-b".to_string()));
895 }
896}