1use std::collections::HashMap;
5
6#[derive(Debug, Clone, PartialEq)]
8pub enum WindowValue {
9 Integer(i64),
10 Float(f64),
11 Text(String),
12 Null,
13}
14
15impl PartialOrd for WindowValue {
16 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
17 Some(self.cmp_window(other))
18 }
19}
20
21impl WindowValue {
22 fn cmp_window(&self, other: &Self) -> std::cmp::Ordering {
23 use std::cmp::Ordering;
24 match (self, other) {
25 (WindowValue::Null, WindowValue::Null) => Ordering::Equal,
26 (WindowValue::Null, _) => Ordering::Less,
27 (_, WindowValue::Null) => Ordering::Greater,
28 (WindowValue::Integer(a), WindowValue::Integer(b)) => a.cmp(b),
29 (WindowValue::Float(a), WindowValue::Float(b)) => {
30 a.partial_cmp(b).unwrap_or(Ordering::Equal)
31 }
32 (WindowValue::Integer(a), WindowValue::Float(b)) => {
33 (*a as f64).partial_cmp(b).unwrap_or(Ordering::Equal)
34 }
35 (WindowValue::Float(a), WindowValue::Integer(b)) => {
36 a.partial_cmp(&(*b as f64)).unwrap_or(Ordering::Equal)
37 }
38 (WindowValue::Text(a), WindowValue::Text(b)) => a.cmp(b),
39 (WindowValue::Integer(_) | WindowValue::Float(_), WindowValue::Text(_)) => {
40 Ordering::Less
41 }
42 (WindowValue::Text(_), WindowValue::Integer(_) | WindowValue::Float(_)) => {
43 Ordering::Greater
44 }
45 }
46 }
47
48 #[allow(dead_code)]
50 fn as_f64(&self) -> Option<f64> {
51 match self {
52 WindowValue::Integer(i) => Some(*i as f64),
53 WindowValue::Float(f) => Some(*f),
54 _ => None,
55 }
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct WindowRow {
62 pub values: HashMap<String, WindowValue>,
63}
64
65impl WindowRow {
66 pub fn new(values: HashMap<String, WindowValue>) -> Self {
67 Self { values }
68 }
69
70 pub fn get(&self, column: &str) -> Option<&WindowValue> {
71 self.values.get(column)
72 }
73
74 pub fn set(&mut self, column: String, value: WindowValue) {
75 self.values.insert(column, value);
76 }
77}
78
79#[derive(Debug, Clone)]
81pub enum WindowFunc {
82 RowNumber,
83 Rank,
84 DenseRank,
85 Ntile(usize),
86 Lag {
87 column: String,
88 offset: usize,
89 default: Option<WindowValue>,
90 },
91 Lead {
92 column: String,
93 offset: usize,
94 default: Option<WindowValue>,
95 },
96 CumSum {
97 column: String,
98 },
99 CumCount,
100}
101
102#[derive(Debug, Clone)]
104pub struct WindowSpec {
105 pub partition_by: Vec<String>,
106 pub order_by: Vec<(String, bool)>, pub func: WindowFunc,
108 pub output_column: String,
109}
110
111#[derive(Debug)]
113pub enum WindowError {
114 ColumnNotFound(String),
115 InvalidNtile(String),
116 InvalidOffset(String),
117 NonNumericColumn(String),
118}
119
120impl std::fmt::Display for WindowError {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match self {
123 WindowError::ColumnNotFound(c) => write!(f, "Column not found: {c}"),
124 WindowError::InvalidNtile(m) => write!(f, "Invalid NTILE: {m}"),
125 WindowError::InvalidOffset(m) => write!(f, "Invalid offset: {m}"),
126 WindowError::NonNumericColumn(c) => write!(f, "Non-numeric column: {c}"),
127 }
128 }
129}
130
131impl std::error::Error for WindowError {}
132
133fn partition_key(row: &WindowRow, partition_by: &[String]) -> String {
135 partition_by
136 .iter()
137 .map(|col| match row.values.get(col) {
138 Some(WindowValue::Integer(i)) => format!("i:{i}"),
139 Some(WindowValue::Float(f)) => format!("f:{f}"),
140 Some(WindowValue::Text(t)) => format!("t:{t}"),
141 Some(WindowValue::Null) | None => "null".to_string(),
142 })
143 .collect::<Vec<_>>()
144 .join("|")
145}
146
147fn cmp_rows(a: &WindowRow, b: &WindowRow, order_by: &[(String, bool)]) -> std::cmp::Ordering {
149 for (col, ascending) in order_by {
150 let av = a.values.get(col).unwrap_or(&WindowValue::Null);
151 let bv = b.values.get(col).unwrap_or(&WindowValue::Null);
152 let ord = av.cmp_window(bv);
153 if ord != std::cmp::Ordering::Equal {
154 return if *ascending { ord } else { ord.reverse() };
155 }
156 }
157 std::cmp::Ordering::Equal
158}
159
160pub fn apply_window(
162 rows: &[WindowRow],
163 spec: &WindowSpec,
164) -> Result<Vec<(WindowRow, WindowValue)>, WindowError> {
165 if rows.is_empty() {
166 return Ok(vec![]);
167 }
168
169 let mut partitions: HashMap<String, Vec<usize>> = HashMap::new();
171 for (idx, row) in rows.iter().enumerate() {
172 let key = partition_key(row, &spec.partition_by);
173 partitions.entry(key).or_default().push(idx);
174 }
175
176 for indices in partitions.values_mut() {
178 indices.sort_by(|&a, &b| cmp_rows(&rows[a], &rows[b], &spec.order_by));
179 }
180
181 let mut results: Vec<Option<WindowValue>> = vec![None; rows.len()];
182
183 for indices in partitions.values() {
184 compute_partition_values(rows, indices, &spec.func, &spec.order_by, &mut results)?;
185 }
186
187 Ok(rows
188 .iter()
189 .enumerate()
190 .map(|(i, row)| (row.clone(), results[i].take().unwrap_or(WindowValue::Null)))
191 .collect())
192}
193
194fn compute_partition_values(
195 rows: &[WindowRow],
196 sorted_indices: &[usize],
197 func: &WindowFunc,
198 order_by: &[(String, bool)],
199 results: &mut [Option<WindowValue>],
200) -> Result<(), WindowError> {
201 let n = sorted_indices.len();
202 match func {
203 WindowFunc::RowNumber => {
204 for (rank, &orig_idx) in sorted_indices.iter().enumerate() {
205 results[orig_idx] = Some(WindowValue::Integer((rank + 1) as i64));
206 }
207 }
208 WindowFunc::Rank => {
209 let mut current_rank = 1usize;
210 let mut i = 0;
211 while i < n {
212 let mut j = i + 1;
214 while j < n
215 && cmp_rows(&rows[sorted_indices[i]], &rows[sorted_indices[j]], order_by)
216 == std::cmp::Ordering::Equal
217 {
218 j += 1;
219 }
220 for &orig_idx in &sorted_indices[i..j] {
222 results[orig_idx] = Some(WindowValue::Integer(current_rank as i64));
223 }
224 current_rank += j - i; i = j;
226 }
227 }
228 WindowFunc::DenseRank => {
229 let mut current_rank = 1usize;
230 let mut i = 0;
231 while i < n {
232 let mut j = i + 1;
233 while j < n
234 && cmp_rows(&rows[sorted_indices[i]], &rows[sorted_indices[j]], order_by)
235 == std::cmp::Ordering::Equal
236 {
237 j += 1;
238 }
239 for &orig_idx in &sorted_indices[i..j] {
240 results[orig_idx] = Some(WindowValue::Integer(current_rank as i64));
241 }
242 current_rank += 1; i = j;
244 }
245 }
246 WindowFunc::Ntile(buckets) => {
247 if *buckets == 0 {
248 return Err(WindowError::InvalidNtile(
249 "NTILE bucket count must be > 0".to_string(),
250 ));
251 }
252 let total = n;
253 let buckets = *buckets;
254 for (pos, &orig_idx) in sorted_indices.iter().enumerate() {
255 let bucket = (pos * buckets / total) + 1;
257 results[orig_idx] = Some(WindowValue::Integer(bucket as i64));
258 }
259 }
260 WindowFunc::Lag {
261 column,
262 offset,
263 default,
264 } => {
265 for (pos, &orig_idx) in sorted_indices.iter().enumerate() {
266 let value = if pos >= *offset {
267 let source_idx = sorted_indices[pos - offset];
268 rows[source_idx]
269 .values
270 .get(column)
271 .cloned()
272 .unwrap_or(WindowValue::Null)
273 } else {
274 default.clone().unwrap_or(WindowValue::Null)
275 };
276 results[orig_idx] = Some(value);
277 }
278 }
279 WindowFunc::Lead {
280 column,
281 offset,
282 default,
283 } => {
284 for (pos, &orig_idx) in sorted_indices.iter().enumerate() {
285 let value = if pos + offset < n {
286 let source_idx = sorted_indices[pos + offset];
287 rows[source_idx]
288 .values
289 .get(column)
290 .cloned()
291 .unwrap_or(WindowValue::Null)
292 } else {
293 default.clone().unwrap_or(WindowValue::Null)
294 };
295 results[orig_idx] = Some(value);
296 }
297 }
298 WindowFunc::CumSum { column } => {
299 let mut running_sum = 0.0f64;
300 let mut running_int: Option<i64> = Some(0);
301 for &orig_idx in sorted_indices.iter() {
302 let val = rows[orig_idx]
303 .values
304 .get(column)
305 .unwrap_or(&WindowValue::Null);
306 match val {
307 WindowValue::Integer(i) => {
308 running_sum += *i as f64;
309 running_int = running_int.and_then(|s| s.checked_add(*i));
310 }
311 WindowValue::Float(f) => {
312 running_sum += f;
313 running_int = None;
314 }
315 WindowValue::Null => {
316 }
318 WindowValue::Text(_) => {
319 return Err(WindowError::NonNumericColumn(column.clone()));
320 }
321 }
322 let result_val = if let Some(int_sum) = running_int {
323 WindowValue::Integer(int_sum)
324 } else {
325 WindowValue::Float(running_sum)
326 };
327 results[orig_idx] = Some(result_val);
328 }
329 }
330 WindowFunc::CumCount => {
331 for (pos, &orig_idx) in sorted_indices.iter().enumerate() {
332 results[orig_idx] = Some(WindowValue::Integer((pos + 1) as i64));
333 }
334 }
335 }
336 Ok(())
337}
338
339pub fn apply_windows(
341 mut rows: Vec<WindowRow>,
342 specs: &[WindowSpec],
343) -> Result<Vec<WindowRow>, WindowError> {
344 for spec in specs {
345 let result_pairs = apply_window(&rows, spec)?;
346 for (idx, (_row, value)) in result_pairs.into_iter().enumerate() {
347 rows[idx].values.insert(spec.output_column.clone(), value);
348 }
349 }
350 Ok(rows)
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 fn make_row(pairs: &[(&str, WindowValue)]) -> WindowRow {
358 let mut values = HashMap::new();
359 for (k, v) in pairs {
360 values.insert(k.to_string(), v.clone());
361 }
362 WindowRow { values }
363 }
364
365 fn int_spec(func: WindowFunc, order_col: &str, out: &str) -> WindowSpec {
366 WindowSpec {
367 partition_by: vec![],
368 order_by: vec![(order_col.to_string(), true)],
369 func,
370 output_column: out.to_string(),
371 }
372 }
373
374 fn partitioned_spec(
375 func: WindowFunc,
376 partition_col: &str,
377 order_col: &str,
378 out: &str,
379 ) -> WindowSpec {
380 WindowSpec {
381 partition_by: vec![partition_col.to_string()],
382 order_by: vec![(order_col.to_string(), true)],
383 func,
384 output_column: out.to_string(),
385 }
386 }
387
388 #[test]
391 fn test_row_number_sequential() {
392 let rows = vec![
393 make_row(&[("n", WindowValue::Integer(3))]),
394 make_row(&[("n", WindowValue::Integer(1))]),
395 make_row(&[("n", WindowValue::Integer(2))]),
396 ];
397 let spec = int_spec(WindowFunc::RowNumber, "n", "rn");
398 let result = apply_window(&rows, &spec).unwrap();
399 let pairs: Vec<(i64, i64)> = result
401 .iter()
402 .map(|(row, val)| {
403 let n = match row.values["n"] {
404 WindowValue::Integer(i) => i,
405 _ => 0,
406 };
407 let rn = match val {
408 WindowValue::Integer(i) => *i,
409 _ => 0,
410 };
411 (n, rn)
412 })
413 .collect();
414 for (n, rn) in &pairs {
416 assert_eq!(*rn, *n, "Expected rn == n for n={n}");
417 }
418 let mut nums: Vec<i64> = pairs.iter().map(|(_, rn)| *rn).collect();
419 nums.sort();
420 assert_eq!(nums, vec![1, 2, 3]);
421 }
422
423 #[test]
424 fn test_row_number_empty() {
425 let rows: Vec<WindowRow> = vec![];
426 let spec = int_spec(WindowFunc::RowNumber, "n", "rn");
427 let result = apply_window(&rows, &spec).unwrap();
428 assert!(result.is_empty());
429 }
430
431 #[test]
432 fn test_row_number_single() {
433 let rows = vec![make_row(&[("n", WindowValue::Integer(42))])];
434 let spec = int_spec(WindowFunc::RowNumber, "n", "rn");
435 let result = apply_window(&rows, &spec).unwrap();
436 assert_eq!(result.len(), 1);
437 assert_eq!(result[0].1, WindowValue::Integer(1));
438 }
439
440 #[test]
443 fn test_rank_no_ties() {
444 let rows = vec![
445 make_row(&[("v", WindowValue::Integer(10))]),
446 make_row(&[("v", WindowValue::Integer(20))]),
447 make_row(&[("v", WindowValue::Integer(30))]),
448 ];
449 let spec = int_spec(WindowFunc::Rank, "v", "r");
450 let result = apply_window(&rows, &spec).unwrap();
451 let mut ranks: Vec<i64> = result
452 .iter()
453 .map(|(_, v)| match v {
454 WindowValue::Integer(i) => *i,
455 _ => 0,
456 })
457 .collect();
458 ranks.sort();
459 assert_eq!(ranks, vec![1, 2, 3]);
460 }
461
462 #[test]
463 fn test_rank_with_ties() {
464 let rows = vec![
465 make_row(&[("v", WindowValue::Integer(10))]),
466 make_row(&[("v", WindowValue::Integer(10))]),
467 make_row(&[("v", WindowValue::Integer(20))]),
468 ];
469 let spec = int_spec(WindowFunc::Rank, "v", "r");
470 let result = apply_window(&rows, &spec).unwrap();
471 let mut ranks: Vec<i64> = result
472 .iter()
473 .map(|(_, v)| match v {
474 WindowValue::Integer(i) => *i,
475 _ => 0,
476 })
477 .collect();
478 ranks.sort();
479 assert_eq!(ranks, vec![1, 1, 3]);
481 }
482
483 #[test]
484 fn test_rank_all_tied() {
485 let rows = vec![
486 make_row(&[("v", WindowValue::Integer(5))]),
487 make_row(&[("v", WindowValue::Integer(5))]),
488 make_row(&[("v", WindowValue::Integer(5))]),
489 ];
490 let spec = int_spec(WindowFunc::Rank, "v", "r");
491 let result = apply_window(&rows, &spec).unwrap();
492 for (_, val) in &result {
493 assert_eq!(*val, WindowValue::Integer(1));
494 }
495 }
496
497 #[test]
500 fn test_dense_rank_with_ties() {
501 let rows = vec![
502 make_row(&[("v", WindowValue::Integer(10))]),
503 make_row(&[("v", WindowValue::Integer(10))]),
504 make_row(&[("v", WindowValue::Integer(20))]),
505 ];
506 let spec = int_spec(WindowFunc::DenseRank, "v", "dr");
507 let result = apply_window(&rows, &spec).unwrap();
508 let mut ranks: Vec<i64> = result
509 .iter()
510 .map(|(_, v)| match v {
511 WindowValue::Integer(i) => *i,
512 _ => 0,
513 })
514 .collect();
515 ranks.sort();
516 assert_eq!(ranks, vec![1, 1, 2]);
518 }
519
520 #[test]
521 fn test_dense_rank_no_ties() {
522 let rows = vec![
523 make_row(&[("v", WindowValue::Integer(1))]),
524 make_row(&[("v", WindowValue::Integer(2))]),
525 make_row(&[("v", WindowValue::Integer(3))]),
526 ];
527 let spec = int_spec(WindowFunc::DenseRank, "v", "dr");
528 let result = apply_window(&rows, &spec).unwrap();
529 let mut ranks: Vec<i64> = result
530 .iter()
531 .map(|(_, v)| match v {
532 WindowValue::Integer(i) => *i,
533 _ => 0,
534 })
535 .collect();
536 ranks.sort();
537 assert_eq!(ranks, vec![1, 2, 3]);
538 }
539
540 #[test]
541 fn test_dense_rank_multiple_ties() {
542 let rows = vec![
543 make_row(&[("v", WindowValue::Integer(10))]),
544 make_row(&[("v", WindowValue::Integer(10))]),
545 make_row(&[("v", WindowValue::Integer(20))]),
546 make_row(&[("v", WindowValue::Integer(20))]),
547 make_row(&[("v", WindowValue::Integer(30))]),
548 ];
549 let spec = int_spec(WindowFunc::DenseRank, "v", "dr");
550 let result = apply_window(&rows, &spec).unwrap();
551 let mut ranks: Vec<i64> = result
552 .iter()
553 .map(|(_, v)| match v {
554 WindowValue::Integer(i) => *i,
555 _ => 0,
556 })
557 .collect();
558 ranks.sort();
559 assert_eq!(ranks, vec![1, 1, 2, 2, 3]);
560 }
561
562 #[test]
565 fn test_ntile_4_even() {
566 let rows: Vec<WindowRow> = (1i64..=8)
567 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
568 .collect();
569 let spec = int_spec(WindowFunc::Ntile(4), "v", "nt");
570 let result = apply_window(&rows, &spec).unwrap();
571 let mut buckets: Vec<i64> = result
572 .iter()
573 .map(|(_, v)| match v {
574 WindowValue::Integer(i) => *i,
575 _ => 0,
576 })
577 .collect();
578 buckets.sort();
579 assert_eq!(buckets, vec![1, 1, 2, 2, 3, 3, 4, 4]);
581 }
582
583 #[test]
584 fn test_ntile_3_uneven() {
585 let rows: Vec<WindowRow> = (1i64..=7)
586 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
587 .collect();
588 let spec = int_spec(WindowFunc::Ntile(3), "v", "nt");
589 let result = apply_window(&rows, &spec).unwrap();
590 let mut buckets: Vec<i64> = result
591 .iter()
592 .map(|(_, v)| match v {
593 WindowValue::Integer(i) => *i,
594 _ => 0,
595 })
596 .collect();
597 buckets.sort();
598 assert_eq!(buckets.len(), 7);
599 for b in &buckets {
601 assert!(*b >= 1 && *b <= 3, "bucket out of range: {b}");
602 }
603 }
604
605 #[test]
606 fn test_ntile_zero_error() {
607 let rows = vec![make_row(&[("v", WindowValue::Integer(1))])];
608 let spec = int_spec(WindowFunc::Ntile(0), "v", "nt");
609 assert!(matches!(
610 apply_window(&rows, &spec),
611 Err(WindowError::InvalidNtile(_))
612 ));
613 }
614
615 #[test]
616 fn test_ntile_1() {
617 let rows: Vec<WindowRow> = (1i64..=5)
618 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
619 .collect();
620 let spec = int_spec(WindowFunc::Ntile(1), "v", "nt");
621 let result = apply_window(&rows, &spec).unwrap();
622 for (_, val) in &result {
623 assert_eq!(*val, WindowValue::Integer(1));
624 }
625 }
626
627 #[test]
630 fn test_lag_offset_1_no_default() {
631 let rows: Vec<WindowRow> = (1i64..=4)
632 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
633 .collect();
634 let spec = int_spec(
635 WindowFunc::Lag {
636 column: "v".to_string(),
637 offset: 1,
638 default: None,
639 },
640 "v",
641 "lag",
642 );
643 let result = apply_window(&rows, &spec).unwrap();
644 let mut sorted_pairs: Vec<(i64, &WindowValue)> = result
646 .iter()
647 .map(|(row, val)| {
648 let n = match row.values["v"] {
649 WindowValue::Integer(i) => i,
650 _ => 0,
651 };
652 (n, val)
653 })
654 .collect();
655 sorted_pairs.sort_by_key(|(n, _)| *n);
656 assert_eq!(sorted_pairs[0].1, &WindowValue::Null);
657 assert_eq!(sorted_pairs[1].1, &WindowValue::Integer(1));
658 assert_eq!(sorted_pairs[2].1, &WindowValue::Integer(2));
659 assert_eq!(sorted_pairs[3].1, &WindowValue::Integer(3));
660 }
661
662 #[test]
663 fn test_lag_with_default() {
664 let rows: Vec<WindowRow> = (1i64..=3)
665 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
666 .collect();
667 let spec = int_spec(
668 WindowFunc::Lag {
669 column: "v".to_string(),
670 offset: 1,
671 default: Some(WindowValue::Integer(-1)),
672 },
673 "v",
674 "lag",
675 );
676 let result = apply_window(&rows, &spec).unwrap();
677 let mut sorted_pairs: Vec<(i64, WindowValue)> = result
678 .iter()
679 .map(|(row, val)| {
680 let n = match row.values["v"] {
681 WindowValue::Integer(i) => i,
682 _ => 0,
683 };
684 (n, val.clone())
685 })
686 .collect();
687 sorted_pairs.sort_by_key(|(n, _)| *n);
688 assert_eq!(sorted_pairs[0].1, WindowValue::Integer(-1));
689 assert_eq!(sorted_pairs[1].1, WindowValue::Integer(1));
690 assert_eq!(sorted_pairs[2].1, WindowValue::Integer(2));
691 }
692
693 #[test]
694 fn test_lag_offset_2() {
695 let rows: Vec<WindowRow> = (1i64..=5)
696 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
697 .collect();
698 let spec = int_spec(
699 WindowFunc::Lag {
700 column: "v".to_string(),
701 offset: 2,
702 default: Some(WindowValue::Integer(0)),
703 },
704 "v",
705 "lag",
706 );
707 let result = apply_window(&rows, &spec).unwrap();
708 let mut sorted_pairs: Vec<(i64, WindowValue)> = result
709 .iter()
710 .map(|(row, val)| {
711 let n = match row.values["v"] {
712 WindowValue::Integer(i) => i,
713 _ => 0,
714 };
715 (n, val.clone())
716 })
717 .collect();
718 sorted_pairs.sort_by_key(|(n, _)| *n);
719 assert_eq!(sorted_pairs[0].1, WindowValue::Integer(0)); assert_eq!(sorted_pairs[1].1, WindowValue::Integer(0)); assert_eq!(sorted_pairs[2].1, WindowValue::Integer(1));
722 assert_eq!(sorted_pairs[3].1, WindowValue::Integer(2));
723 assert_eq!(sorted_pairs[4].1, WindowValue::Integer(3));
724 }
725
726 #[test]
729 fn test_lead_offset_1_no_default() {
730 let rows: Vec<WindowRow> = (1i64..=4)
731 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
732 .collect();
733 let spec = int_spec(
734 WindowFunc::Lead {
735 column: "v".to_string(),
736 offset: 1,
737 default: None,
738 },
739 "v",
740 "lead",
741 );
742 let result = apply_window(&rows, &spec).unwrap();
743 let mut sorted_pairs: Vec<(i64, WindowValue)> = result
744 .iter()
745 .map(|(row, val)| {
746 let n = match row.values["v"] {
747 WindowValue::Integer(i) => i,
748 _ => 0,
749 };
750 (n, val.clone())
751 })
752 .collect();
753 sorted_pairs.sort_by_key(|(n, _)| *n);
754 assert_eq!(sorted_pairs[0].1, WindowValue::Integer(2));
755 assert_eq!(sorted_pairs[1].1, WindowValue::Integer(3));
756 assert_eq!(sorted_pairs[2].1, WindowValue::Integer(4));
757 assert_eq!(sorted_pairs[3].1, WindowValue::Null);
758 }
759
760 #[test]
761 fn test_lead_with_default() {
762 let rows: Vec<WindowRow> = (1i64..=3)
763 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
764 .collect();
765 let spec = int_spec(
766 WindowFunc::Lead {
767 column: "v".to_string(),
768 offset: 1,
769 default: Some(WindowValue::Integer(99)),
770 },
771 "v",
772 "lead",
773 );
774 let result = apply_window(&rows, &spec).unwrap();
775 let mut sorted_pairs: Vec<(i64, WindowValue)> = result
776 .iter()
777 .map(|(row, val)| {
778 let n = match row.values["v"] {
779 WindowValue::Integer(i) => i,
780 _ => 0,
781 };
782 (n, val.clone())
783 })
784 .collect();
785 sorted_pairs.sort_by_key(|(n, _)| *n);
786 assert_eq!(sorted_pairs[2].1, WindowValue::Integer(99));
787 }
788
789 #[test]
792 fn test_cum_sum_integers() {
793 let rows: Vec<WindowRow> = (1i64..=5)
794 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
795 .collect();
796 let spec = int_spec(
797 WindowFunc::CumSum {
798 column: "v".to_string(),
799 },
800 "v",
801 "cs",
802 );
803 let result = apply_window(&rows, &spec).unwrap();
804 let mut sorted_pairs: Vec<(i64, WindowValue)> = result
805 .iter()
806 .map(|(row, val)| {
807 let n = match row.values["v"] {
808 WindowValue::Integer(i) => i,
809 _ => 0,
810 };
811 (n, val.clone())
812 })
813 .collect();
814 sorted_pairs.sort_by_key(|(n, _)| *n);
815 assert_eq!(sorted_pairs[0].1, WindowValue::Integer(1));
816 assert_eq!(sorted_pairs[1].1, WindowValue::Integer(3));
817 assert_eq!(sorted_pairs[2].1, WindowValue::Integer(6));
818 assert_eq!(sorted_pairs[3].1, WindowValue::Integer(10));
819 assert_eq!(sorted_pairs[4].1, WindowValue::Integer(15));
820 }
821
822 #[test]
823 fn test_cum_sum_floats() {
824 let rows = vec![
825 make_row(&[
826 ("v", WindowValue::Float(1.5)),
827 ("k", WindowValue::Integer(1)),
828 ]),
829 make_row(&[
830 ("v", WindowValue::Float(2.5)),
831 ("k", WindowValue::Integer(2)),
832 ]),
833 make_row(&[
834 ("v", WindowValue::Float(1.0)),
835 ("k", WindowValue::Integer(3)),
836 ]),
837 ];
838 let spec = WindowSpec {
839 partition_by: vec![],
840 order_by: vec![("k".to_string(), true)],
841 func: WindowFunc::CumSum {
842 column: "v".to_string(),
843 },
844 output_column: "cs".to_string(),
845 };
846 let result = apply_window(&rows, &spec).unwrap();
847 let mut sorted_pairs: Vec<(i64, f64)> = result
848 .iter()
849 .map(|(row, val)| {
850 let k = match row.values["k"] {
851 WindowValue::Integer(i) => i,
852 _ => 0,
853 };
854 let cs = match val {
855 WindowValue::Float(f) => *f,
856 WindowValue::Integer(i) => *i as f64,
857 _ => 0.0,
858 };
859 (k, cs)
860 })
861 .collect();
862 sorted_pairs.sort_by_key(|(k, _)| *k);
863 assert!((sorted_pairs[0].1 - 1.5).abs() < 1e-10);
864 assert!((sorted_pairs[1].1 - 4.0).abs() < 1e-10);
865 assert!((sorted_pairs[2].1 - 5.0).abs() < 1e-10);
866 }
867
868 #[test]
869 fn test_cum_sum_text_error() {
870 let rows = vec![make_row(&[("v", WindowValue::Text("hello".to_string()))])];
871 let spec = int_spec(
872 WindowFunc::CumSum {
873 column: "v".to_string(),
874 },
875 "v",
876 "cs",
877 );
878 assert!(matches!(
879 apply_window(&rows, &spec),
880 Err(WindowError::NonNumericColumn(_))
881 ));
882 }
883
884 #[test]
887 fn test_cum_count() {
888 let rows: Vec<WindowRow> = (1i64..=5)
889 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
890 .collect();
891 let spec = int_spec(WindowFunc::CumCount, "v", "cc");
892 let result = apply_window(&rows, &spec).unwrap();
893 let mut sorted_pairs: Vec<(i64, i64)> = result
894 .iter()
895 .map(|(row, val)| {
896 let n = match row.values["v"] {
897 WindowValue::Integer(i) => i,
898 _ => 0,
899 };
900 let cc = match val {
901 WindowValue::Integer(i) => *i,
902 _ => 0,
903 };
904 (n, cc)
905 })
906 .collect();
907 sorted_pairs.sort_by_key(|(n, _)| *n);
908 for (i, (_, cc)) in sorted_pairs.iter().enumerate() {
909 assert_eq!(*cc, (i + 1) as i64);
910 }
911 }
912
913 #[test]
916 fn test_row_number_partitioned() {
917 let rows = vec![
918 make_row(&[
919 ("g", WindowValue::Text("A".into())),
920 ("v", WindowValue::Integer(2)),
921 ]),
922 make_row(&[
923 ("g", WindowValue::Text("A".into())),
924 ("v", WindowValue::Integer(1)),
925 ]),
926 make_row(&[
927 ("g", WindowValue::Text("B".into())),
928 ("v", WindowValue::Integer(3)),
929 ]),
930 make_row(&[
931 ("g", WindowValue::Text("B".into())),
932 ("v", WindowValue::Integer(1)),
933 ]),
934 ];
935 let spec = partitioned_spec(WindowFunc::RowNumber, "g", "v", "rn");
936 let result = apply_window(&rows, &spec).unwrap();
937 let mut pairs: Vec<(String, i64, i64)> = result
938 .iter()
939 .map(|(row, val)| {
940 let g = match &row.values["g"] {
941 WindowValue::Text(s) => s.clone(),
942 _ => String::new(),
943 };
944 let v = match row.values["v"] {
945 WindowValue::Integer(i) => i,
946 _ => 0,
947 };
948 let rn = match val {
949 WindowValue::Integer(i) => *i,
950 _ => 0,
951 };
952 (g, v, rn)
953 })
954 .collect();
955 pairs.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
956 let group_a: Vec<_> = pairs.iter().filter(|(g, _, _)| g == "A").collect();
958 assert_eq!(group_a.len(), 2);
959 assert_eq!(group_a[0].2, 1);
960 assert_eq!(group_a[1].2, 2);
961 let group_b: Vec<_> = pairs.iter().filter(|(g, _, _)| g == "B").collect();
963 assert_eq!(group_b.len(), 2);
964 assert_eq!(group_b[0].2, 1);
965 assert_eq!(group_b[1].2, 2);
966 }
967
968 #[test]
969 fn test_rank_partitioned_with_ties() {
970 let rows = vec![
971 make_row(&[
972 ("g", WindowValue::Text("A".into())),
973 ("v", WindowValue::Integer(5)),
974 ]),
975 make_row(&[
976 ("g", WindowValue::Text("A".into())),
977 ("v", WindowValue::Integer(5)),
978 ]),
979 make_row(&[
980 ("g", WindowValue::Text("A".into())),
981 ("v", WindowValue::Integer(10)),
982 ]),
983 ];
984 let spec = partitioned_spec(WindowFunc::Rank, "g", "v", "r");
985 let result = apply_window(&rows, &spec).unwrap();
986 let mut ranks: Vec<i64> = result
987 .iter()
988 .map(|(_, v)| match v {
989 WindowValue::Integer(i) => *i,
990 _ => 0,
991 })
992 .collect();
993 ranks.sort();
994 assert_eq!(ranks, vec![1, 1, 3]);
995 }
996
997 #[test]
998 fn test_dense_rank_partitioned() {
999 let rows = vec![
1000 make_row(&[
1001 ("g", WindowValue::Text("X".into())),
1002 ("v", WindowValue::Integer(5)),
1003 ]),
1004 make_row(&[
1005 ("g", WindowValue::Text("X".into())),
1006 ("v", WindowValue::Integer(5)),
1007 ]),
1008 make_row(&[
1009 ("g", WindowValue::Text("X".into())),
1010 ("v", WindowValue::Integer(10)),
1011 ]),
1012 ];
1013 let spec = partitioned_spec(WindowFunc::DenseRank, "g", "v", "dr");
1014 let result = apply_window(&rows, &spec).unwrap();
1015 let mut ranks: Vec<i64> = result
1016 .iter()
1017 .map(|(_, v)| match v {
1018 WindowValue::Integer(i) => *i,
1019 _ => 0,
1020 })
1021 .collect();
1022 ranks.sort();
1023 assert_eq!(ranks, vec![1, 1, 2]);
1024 }
1025
1026 #[test]
1029 fn test_row_number_descending() {
1030 let rows: Vec<WindowRow> = (1i64..=4)
1031 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
1032 .collect();
1033 let spec = WindowSpec {
1034 partition_by: vec![],
1035 order_by: vec![("v".to_string(), false)], func: WindowFunc::RowNumber,
1037 output_column: "rn".to_string(),
1038 };
1039 let result = apply_window(&rows, &spec).unwrap();
1040 let mut pairs: Vec<(i64, i64)> = result
1041 .iter()
1042 .map(|(row, val)| {
1043 let v = match row.values["v"] {
1044 WindowValue::Integer(i) => i,
1045 _ => 0,
1046 };
1047 let rn = match val {
1048 WindowValue::Integer(i) => *i,
1049 _ => 0,
1050 };
1051 (v, rn)
1052 })
1053 .collect();
1054 pairs.sort_by_key(|(v, _)| *v);
1055 assert_eq!(pairs[0].1, 4); assert_eq!(pairs[3].1, 1); }
1059
1060 #[test]
1061 fn test_rank_descending() {
1062 let rows = vec![
1063 make_row(&[("v", WindowValue::Integer(30))]),
1064 make_row(&[("v", WindowValue::Integer(20))]),
1065 make_row(&[("v", WindowValue::Integer(10))]),
1066 ];
1067 let spec = WindowSpec {
1068 partition_by: vec![],
1069 order_by: vec![("v".to_string(), false)],
1070 func: WindowFunc::Rank,
1071 output_column: "r".to_string(),
1072 };
1073 let result = apply_window(&rows, &spec).unwrap();
1074 let mut pairs: Vec<(i64, i64)> = result
1075 .iter()
1076 .map(|(row, val)| {
1077 let v = match row.values["v"] {
1078 WindowValue::Integer(i) => i,
1079 _ => 0,
1080 };
1081 let r = match val {
1082 WindowValue::Integer(i) => *i,
1083 _ => 0,
1084 };
1085 (v, r)
1086 })
1087 .collect();
1088 pairs.sort_by_key(|(v, _)| *v);
1089 assert_eq!(pairs[0].1, 3); assert_eq!(pairs[2].1, 1); }
1093
1094 #[test]
1097 fn test_apply_windows_multiple_specs() {
1098 let rows: Vec<WindowRow> = (1i64..=3)
1099 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
1100 .collect();
1101 let specs = vec![
1102 int_spec(WindowFunc::RowNumber, "v", "rn"),
1103 int_spec(
1104 WindowFunc::CumSum {
1105 column: "v".to_string(),
1106 },
1107 "v",
1108 "cs",
1109 ),
1110 ];
1111 let result = apply_windows(rows, &specs).unwrap();
1112 assert_eq!(result.len(), 3);
1113 for row in &result {
1114 assert!(row.values.contains_key("rn"));
1115 assert!(row.values.contains_key("cs"));
1116 }
1117 }
1118
1119 #[test]
1120 fn test_apply_windows_empty() {
1121 let rows: Vec<WindowRow> = vec![];
1122 let specs = vec![int_spec(WindowFunc::RowNumber, "v", "rn")];
1123 let result = apply_windows(rows, &specs).unwrap();
1124 assert!(result.is_empty());
1125 }
1126
1127 #[test]
1130 fn test_window_value_null_smallest() {
1131 assert_eq!(
1132 WindowValue::Null.cmp_window(&WindowValue::Integer(0)),
1133 std::cmp::Ordering::Less
1134 );
1135 assert_eq!(
1136 WindowValue::Integer(0).cmp_window(&WindowValue::Null),
1137 std::cmp::Ordering::Greater
1138 );
1139 assert_eq!(
1140 WindowValue::Null.cmp_window(&WindowValue::Null),
1141 std::cmp::Ordering::Equal
1142 );
1143 }
1144
1145 #[test]
1146 fn test_window_value_int_vs_float() {
1147 assert_eq!(
1148 WindowValue::Integer(1).cmp_window(&WindowValue::Float(2.0)),
1149 std::cmp::Ordering::Less
1150 );
1151 assert_eq!(
1152 WindowValue::Float(1.5).cmp_window(&WindowValue::Integer(1)),
1153 std::cmp::Ordering::Greater
1154 );
1155 }
1156
1157 #[test]
1158 fn test_window_value_text_ordering() {
1159 assert_eq!(
1160 WindowValue::Text("apple".into()).cmp_window(&WindowValue::Text("banana".into())),
1161 std::cmp::Ordering::Less
1162 );
1163 }
1164
1165 #[test]
1168 fn test_cum_sum_with_nulls() {
1169 let rows = vec![
1170 make_row(&[
1171 ("v", WindowValue::Integer(1)),
1172 ("k", WindowValue::Integer(1)),
1173 ]),
1174 make_row(&[("v", WindowValue::Null), ("k", WindowValue::Integer(2))]),
1175 make_row(&[
1176 ("v", WindowValue::Integer(3)),
1177 ("k", WindowValue::Integer(3)),
1178 ]),
1179 ];
1180 let spec = WindowSpec {
1181 partition_by: vec![],
1182 order_by: vec![("k".to_string(), true)],
1183 func: WindowFunc::CumSum {
1184 column: "v".to_string(),
1185 },
1186 output_column: "cs".to_string(),
1187 };
1188 let result = apply_window(&rows, &spec).unwrap();
1189 let mut sorted: Vec<(i64, WindowValue)> = result
1190 .iter()
1191 .map(|(row, val)| {
1192 let k = match row.values["k"] {
1193 WindowValue::Integer(i) => i,
1194 _ => 0,
1195 };
1196 (k, val.clone())
1197 })
1198 .collect();
1199 sorted.sort_by_key(|(k, _)| *k);
1200 assert_eq!(sorted[0].1, WindowValue::Integer(1));
1201 assert_eq!(sorted[1].1, WindowValue::Integer(1)); assert_eq!(sorted[2].1, WindowValue::Integer(4));
1203 }
1204
1205 #[test]
1206 fn test_lag_empty_partition() {
1207 let rows: Vec<WindowRow> = vec![];
1208 let spec = int_spec(
1209 WindowFunc::Lag {
1210 column: "v".to_string(),
1211 offset: 1,
1212 default: None,
1213 },
1214 "v",
1215 "lag",
1216 );
1217 let result = apply_window(&rows, &spec).unwrap();
1218 assert!(result.is_empty());
1219 }
1220
1221 #[test]
1222 fn test_lead_large_offset() {
1223 let rows: Vec<WindowRow> = (1i64..=3)
1224 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
1225 .collect();
1226 let spec = int_spec(
1227 WindowFunc::Lead {
1228 column: "v".to_string(),
1229 offset: 10,
1230 default: Some(WindowValue::Integer(-99)),
1231 },
1232 "v",
1233 "lead",
1234 );
1235 let result = apply_window(&rows, &spec).unwrap();
1236 for (_, val) in &result {
1237 assert_eq!(*val, WindowValue::Integer(-99));
1238 }
1239 }
1240
1241 #[test]
1242 fn test_ntile_more_buckets_than_rows() {
1243 let rows = vec![
1244 make_row(&[("v", WindowValue::Integer(1))]),
1245 make_row(&[("v", WindowValue::Integer(2))]),
1246 ];
1247 let spec = int_spec(WindowFunc::Ntile(5), "v", "nt");
1248 let result = apply_window(&rows, &spec).unwrap();
1249 assert_eq!(result.len(), 2);
1251 for (_, val) in &result {
1252 match val {
1253 WindowValue::Integer(b) => assert!(*b >= 1 && *b <= 5, "bucket out of range: {b}"),
1254 _ => panic!("Expected integer bucket"),
1255 }
1256 }
1257 }
1258
1259 #[test]
1260 fn test_as_f64() {
1261 assert_eq!(WindowValue::Integer(5).as_f64(), Some(5.0));
1262 assert_eq!(WindowValue::Float(2.71).as_f64(), Some(2.71));
1263 assert_eq!(WindowValue::Text("x".into()).as_f64(), None);
1264 assert_eq!(WindowValue::Null.as_f64(), None);
1265 }
1266
1267 #[test]
1268 fn test_window_row_get_set() {
1269 let mut row = make_row(&[("a", WindowValue::Integer(1))]);
1270 assert_eq!(row.get("a"), Some(&WindowValue::Integer(1)));
1271 assert_eq!(row.get("b"), None);
1272 row.set("b".to_string(), WindowValue::Float(2.0));
1273 assert_eq!(row.get("b"), Some(&WindowValue::Float(2.0)));
1274 }
1275
1276 #[test]
1277 fn test_cum_count_matches_row_number() {
1278 let rows: Vec<WindowRow> = (1i64..=6)
1279 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
1280 .collect();
1281 let rn_spec = int_spec(WindowFunc::RowNumber, "v", "rn");
1282 let cc_spec = int_spec(WindowFunc::CumCount, "v", "cc");
1283 let rn_result = apply_window(&rows, &rn_spec).unwrap();
1284 let cc_result = apply_window(&rows, &cc_spec).unwrap();
1285 let mut rn_vals: Vec<i64> = rn_result
1286 .iter()
1287 .map(|(_, v)| match v {
1288 WindowValue::Integer(i) => *i,
1289 _ => 0,
1290 })
1291 .collect();
1292 let mut cc_vals: Vec<i64> = cc_result
1293 .iter()
1294 .map(|(_, v)| match v {
1295 WindowValue::Integer(i) => *i,
1296 _ => 0,
1297 })
1298 .collect();
1299 rn_vals.sort();
1300 cc_vals.sort();
1301 assert_eq!(rn_vals, cc_vals);
1302 }
1303
1304 #[test]
1305 fn test_lead_offset_2() {
1306 let rows: Vec<WindowRow> = (1i64..=5)
1307 .map(|i| make_row(&[("v", WindowValue::Integer(i))]))
1308 .collect();
1309 let spec = int_spec(
1310 WindowFunc::Lead {
1311 column: "v".to_string(),
1312 offset: 2,
1313 default: Some(WindowValue::Integer(0)),
1314 },
1315 "v",
1316 "lead",
1317 );
1318 let result = apply_window(&rows, &spec).unwrap();
1319 let mut sorted: Vec<(i64, WindowValue)> = result
1320 .iter()
1321 .map(|(row, val)| {
1322 let v = match row.values["v"] {
1323 WindowValue::Integer(i) => i,
1324 _ => 0,
1325 };
1326 (v, val.clone())
1327 })
1328 .collect();
1329 sorted.sort_by_key(|(v, _)| *v);
1330 assert_eq!(sorted[0].1, WindowValue::Integer(3));
1331 assert_eq!(sorted[1].1, WindowValue::Integer(4));
1332 assert_eq!(sorted[2].1, WindowValue::Integer(5));
1333 assert_eq!(sorted[3].1, WindowValue::Integer(0));
1334 assert_eq!(sorted[4].1, WindowValue::Integer(0));
1335 }
1336}