1use std::collections::HashMap;
10
11use nodedb_types::Value;
12
13use super::spec::WindowFuncSpec;
14use super::value_agg::apply_v_aggregate;
15use super::value_partition::build_value_partitions;
16use crate::expr::types::SqlExpr;
17use crate::value_ops::compare_values;
18
19#[derive(Debug, thiserror::Error)]
21pub enum WindowError {
22 #[error("window column '{name}' not found in result columns")]
23 ColumnNotFound { name: String },
24
25 #[error("window function argument error: {detail}")]
26 ArgEval { detail: String },
27
28 #[error("window frame error: {detail}")]
29 BadFrame { detail: String },
30
31 #[error("window expression: {0}")]
32 Eval(#[from] crate::expr::EvalError),
33}
34
35pub fn evaluate_window_functions_value(
43 rows: &mut [Vec<Value>],
44 column_index: &HashMap<String, usize>,
45 specs: &[WindowFuncSpec],
46) -> Result<Vec<String>, WindowError> {
47 let mut new_cols: Vec<String> = Vec::with_capacity(specs.len());
48
49 for spec in specs {
50 let partitions = build_value_partitions(rows, column_index, spec)?;
51 let write_col = rows.first().map(|r| r.len()).unwrap_or(0);
52
53 for row in rows.iter_mut() {
54 row.push(Value::Null);
55 }
56
57 for partition_indices in &partitions {
58 match spec.func_name.as_str() {
59 "row_number" => apply_v_row_number(rows, partition_indices, write_col),
60 "rank" => apply_v_rank(rows, partition_indices, column_index, spec, write_col)?,
61 "dense_rank" => {
62 apply_v_dense_rank(rows, partition_indices, column_index, spec, write_col)?
63 }
64 "ntile" => apply_v_ntile(rows, partition_indices, spec, write_col)?,
65 "percent_rank" => {
66 apply_v_percent_rank(rows, partition_indices, column_index, spec, write_col)?
67 }
68 "cume_dist" => {
69 apply_v_cume_dist(rows, partition_indices, column_index, spec, write_col)?
70 }
71 "lag" => apply_v_lag(rows, partition_indices, column_index, spec, write_col)?,
72 "lead" => apply_v_lead(rows, partition_indices, column_index, spec, write_col)?,
73 "nth_value" => {
74 apply_v_nth_value(rows, partition_indices, column_index, spec, write_col)?
75 }
76 "sum" | "count" | "avg" | "min" | "max" | "first_value" | "last_value" => {
77 apply_v_aggregate(rows, partition_indices, column_index, spec, write_col)?
78 }
79 other => {
80 return Err(WindowError::ArgEval {
81 detail: format!(
82 "unknown window function '{other}'; valid names: row_number, rank, \
83 dense_rank, ntile, percent_rank, cume_dist, lag, lead, nth_value, \
84 sum, count, avg, min, max, first_value, last_value"
85 ),
86 });
87 }
88 }
89 }
90
91 new_cols.push(spec.alias.clone());
92 }
93
94 Ok(new_cols)
95}
96
97pub(super) fn cmp_values(a: &Value, b: &Value) -> std::cmp::Ordering {
100 match (a, b) {
101 (Value::Null, Value::Null) => std::cmp::Ordering::Equal,
102 (Value::Null, _) => std::cmp::Ordering::Less,
103 (_, Value::Null) => std::cmp::Ordering::Greater,
104 (va, vb) => compare_values(va, vb),
105 }
106}
107
108pub(super) fn order_keys_equal_v(
109 rows: &[Vec<Value>],
110 a: usize,
111 b: usize,
112 column_index: &HashMap<String, usize>,
113 order_by: &[(SqlExpr, bool)],
114) -> Result<bool, WindowError> {
115 for (expr, _) in order_by {
116 let row_a = rows.get(a).map(|r| r.as_slice()).unwrap_or(&[]);
117 let row_b = rows.get(b).map(|r| r.as_slice()).unwrap_or(&[]);
118 let va = eval_arg_for_row(expr, row_a, column_index)?;
119 let vb = eval_arg_for_row(expr, row_b, column_index)?;
120 if !matches!(cmp_values(&va, &vb), std::cmp::Ordering::Equal) {
121 return Ok(false);
122 }
123 }
124 Ok(true)
125}
126
127pub(super) fn eval_arg_for_row(
134 expr: &SqlExpr,
135 row: &[Value],
136 column_index: &HashMap<String, usize>,
137) -> Result<Value, crate::expr::EvalError> {
138 match expr {
139 SqlExpr::Column(name) => Ok(column_index
140 .get(name.as_str())
141 .and_then(|&idx| row.get(idx))
142 .cloned()
143 .unwrap_or(Value::Null)),
144 SqlExpr::Literal(v) => Ok(v.clone()),
145 other => {
146 let doc = row_to_obj(row, column_index);
147 Ok(other.eval(&doc)?)
148 }
149 }
150}
151
152fn row_to_obj(row: &[Value], column_index: &HashMap<String, usize>) -> Value {
153 let mut map = HashMap::new();
154 for (name, &idx) in column_index {
155 if let Some(v) = row.get(idx) {
156 map.insert(name.clone(), v.clone());
157 }
158 }
159 Value::Object(map)
160}
161
162fn usize_arg(spec: &WindowFuncSpec, idx: usize, default: usize) -> usize {
163 spec.args
164 .get(idx)
165 .and_then(|e| match e {
166 SqlExpr::Literal(v) => v.as_f64().map(|n| n as usize),
167 _ => None,
168 })
169 .unwrap_or(default)
170}
171
172fn default_arg_value(spec: &WindowFuncSpec, idx: usize) -> Value {
173 spec.args
174 .get(idx)
175 .and_then(|e| match e {
176 SqlExpr::Literal(v) => Some(v.clone()),
177 _ => None,
178 })
179 .unwrap_or(Value::Null)
180}
181
182pub(super) fn set_cell(rows: &mut [Vec<Value>], row_idx: usize, col_idx: usize, val: Value) {
185 if let Some(row) = rows.get_mut(row_idx)
186 && let Some(cell) = row.get_mut(col_idx)
187 {
188 *cell = val;
189 }
190}
191
192fn apply_v_row_number(rows: &mut [Vec<Value>], indices: &[usize], write_col: usize) {
195 for (rank, &i) in indices.iter().enumerate() {
196 set_cell(rows, i, write_col, Value::Integer((rank + 1) as i64));
197 }
198}
199
200fn apply_v_rank(
201 rows: &mut [Vec<Value>],
202 indices: &[usize],
203 column_index: &HashMap<String, usize>,
204 spec: &WindowFuncSpec,
205 write_col: usize,
206) -> Result<(), WindowError> {
207 if indices.is_empty() {
208 return Ok(());
209 }
210 let mut current_rank = 1usize;
211 set_cell(rows, indices[0], write_col, Value::Integer(1));
212 for pos in 1..indices.len() {
213 if !order_keys_equal_v(
214 rows,
215 indices[pos - 1],
216 indices[pos],
217 column_index,
218 &spec.order_by,
219 )? {
220 current_rank = pos + 1;
221 }
222 set_cell(
223 rows,
224 indices[pos],
225 write_col,
226 Value::Integer(current_rank as i64),
227 );
228 }
229 Ok(())
230}
231
232fn apply_v_dense_rank(
233 rows: &mut [Vec<Value>],
234 indices: &[usize],
235 column_index: &HashMap<String, usize>,
236 spec: &WindowFuncSpec,
237 write_col: usize,
238) -> Result<(), WindowError> {
239 if indices.is_empty() {
240 return Ok(());
241 }
242 let mut current_rank = 1usize;
243 set_cell(rows, indices[0], write_col, Value::Integer(1));
244 for pos in 1..indices.len() {
245 if !order_keys_equal_v(
246 rows,
247 indices[pos - 1],
248 indices[pos],
249 column_index,
250 &spec.order_by,
251 )? {
252 current_rank += 1;
253 }
254 set_cell(
255 rows,
256 indices[pos],
257 write_col,
258 Value::Integer(current_rank as i64),
259 );
260 }
261 Ok(())
262}
263
264fn apply_v_ntile(
265 rows: &mut [Vec<Value>],
266 indices: &[usize],
267 spec: &WindowFuncSpec,
268 write_col: usize,
269) -> Result<(), WindowError> {
270 let n = usize_arg(spec, 0, 1).max(1);
271 let total = indices.len();
272 if total == 0 {
273 return Ok(());
274 }
275 for (pos, &i) in indices.iter().enumerate() {
276 let bucket = (pos * n / total) + 1;
277 set_cell(rows, i, write_col, Value::Integer(bucket as i64));
278 }
279 Ok(())
280}
281
282fn apply_v_percent_rank(
283 rows: &mut [Vec<Value>],
284 indices: &[usize],
285 column_index: &HashMap<String, usize>,
286 spec: &WindowFuncSpec,
287 write_col: usize,
288) -> Result<(), WindowError> {
289 let total = indices.len();
290 if total == 0 {
291 return Ok(());
292 }
293 if total == 1 {
294 set_cell(rows, indices[0], write_col, Value::Float(0.0));
295 return Ok(());
296 }
297 let denom = (total - 1) as f64;
298 let mut current_rank = 1usize;
299 set_cell(rows, indices[0], write_col, Value::Float(0.0));
300 for pos in 1..total {
301 if !order_keys_equal_v(
302 rows,
303 indices[pos - 1],
304 indices[pos],
305 column_index,
306 &spec.order_by,
307 )? {
308 current_rank = pos + 1;
309 }
310 let pr = (current_rank - 1) as f64 / denom;
311 set_cell(rows, indices[pos], write_col, Value::Float(pr));
312 }
313 Ok(())
314}
315
316fn apply_v_cume_dist(
317 rows: &mut [Vec<Value>],
318 indices: &[usize],
319 column_index: &HashMap<String, usize>,
320 spec: &WindowFuncSpec,
321 write_col: usize,
322) -> Result<(), WindowError> {
323 let total = indices.len();
324 if total == 0 {
325 return Ok(());
326 }
327 let denom = total as f64;
328 let mut group_start = 0;
329 while group_start < total {
330 let mut group_end = group_start + 1;
331 while group_end < total
332 && order_keys_equal_v(
333 rows,
334 indices[group_start],
335 indices[group_end],
336 column_index,
337 &spec.order_by,
338 )?
339 {
340 group_end += 1;
341 }
342 let cd = group_end as f64 / denom;
343 for &idx in &indices[group_start..group_end] {
344 set_cell(rows, idx, write_col, Value::Float(cd));
345 }
346 group_start = group_end;
347 }
348 Ok(())
349}
350
351fn collect_arg_values(
354 rows: &[Vec<Value>],
355 indices: &[usize],
356 column_index: &HashMap<String, usize>,
357 spec: &WindowFuncSpec,
358) -> Result<Vec<Value>, WindowError> {
359 indices
360 .iter()
361 .map(|&i| match (rows.get(i), spec.args.first()) {
362 (Some(row), Some(expr)) => Ok(eval_arg_for_row(expr, row, column_index)?),
363 _ => Ok(Value::Null),
364 })
365 .collect()
366}
367
368fn apply_v_lag(
369 rows: &mut [Vec<Value>],
370 indices: &[usize],
371 column_index: &HashMap<String, usize>,
372 spec: &WindowFuncSpec,
373 write_col: usize,
374) -> Result<(), WindowError> {
375 let offset = usize_arg(spec, 1, 1);
376 let default = default_arg_value(spec, 2);
377 let values = collect_arg_values(rows, indices, column_index, spec)?;
378 for (pos, &i) in indices.iter().enumerate() {
379 let val = if pos >= offset {
380 values[pos - offset].clone()
381 } else {
382 default.clone()
383 };
384 set_cell(rows, i, write_col, val);
385 }
386 Ok(())
387}
388
389fn apply_v_lead(
390 rows: &mut [Vec<Value>],
391 indices: &[usize],
392 column_index: &HashMap<String, usize>,
393 spec: &WindowFuncSpec,
394 write_col: usize,
395) -> Result<(), WindowError> {
396 let offset = usize_arg(spec, 1, 1);
397 let default = default_arg_value(spec, 2);
398 let values = collect_arg_values(rows, indices, column_index, spec)?;
399 for (pos, &i) in indices.iter().enumerate() {
400 let val = if pos + offset < indices.len() {
401 values[pos + offset].clone()
402 } else {
403 default.clone()
404 };
405 set_cell(rows, i, write_col, val);
406 }
407 Ok(())
408}
409
410fn apply_v_nth_value(
411 rows: &mut [Vec<Value>],
412 indices: &[usize],
413 column_index: &HashMap<String, usize>,
414 spec: &WindowFuncSpec,
415 write_col: usize,
416) -> Result<(), WindowError> {
417 let n = usize_arg(spec, 1, 1).max(1);
418 let values = collect_arg_values(rows, indices, column_index, spec)?;
419 for (pos, &i) in indices.iter().enumerate() {
420 let val = if pos + 1 >= n {
421 values[n - 1].clone()
422 } else {
423 Value::Null
424 };
425 set_cell(rows, i, write_col, val);
426 }
427 Ok(())
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use crate::expr::types::SqlExpr;
434 use crate::window::spec::WindowFrame;
435
436 fn ci(names: &[&str]) -> HashMap<String, usize> {
437 names
438 .iter()
439 .enumerate()
440 .map(|(i, n)| (n.to_string(), i))
441 .collect()
442 }
443
444 fn spec(
445 func: &str,
446 args: Vec<SqlExpr>,
447 partition_by: Vec<SqlExpr>,
448 order_by: Vec<(SqlExpr, bool)>,
449 ) -> WindowFuncSpec {
450 WindowFuncSpec {
451 alias: format!("w_{func}"),
452 func_name: func.to_string(),
453 args,
454 partition_by,
455 order_by,
456 frame: WindowFrame::default(),
457 }
458 }
459
460 fn col(name: &str) -> SqlExpr {
461 SqlExpr::Column(name.to_string())
462 }
463
464 fn rows_v(vals: &[i64]) -> Vec<Vec<Value>> {
466 vals.iter().map(|&v| vec![Value::Integer(v)]).collect()
467 }
468
469 fn out_int(rows: &[Vec<Value>], col_idx: usize) -> Vec<i64> {
470 rows.iter()
471 .map(|r| match &r[col_idx] {
472 Value::Integer(n) => *n,
473 other => panic!("expected integer, got {other:?}"),
474 })
475 .collect()
476 }
477
478 fn out_f64(rows: &[Vec<Value>], col_idx: usize) -> Vec<f64> {
479 rows.iter().map(|r| r[col_idx].as_f64().unwrap()).collect()
480 }
481
482 #[test]
483 fn row_number_sequential() {
484 let mut rows = rows_v(&[5, 5, 5]);
485 let cols = ci(&["v"]);
486 let s = spec("row_number", vec![], vec![], vec![]);
487 evaluate_window_functions_value(&mut rows, &cols, &[s]).unwrap();
488 assert_eq!(out_int(&rows, 1), vec![1, 2, 3]);
489 }
490
491 #[test]
492 fn rank_handles_ties() {
493 let mut rows = rows_v(&[10, 10, 20]);
494 let cols = ci(&["v"]);
495 let s = spec("rank", vec![], vec![], vec![(col("v"), true)]);
496 evaluate_window_functions_value(&mut rows, &cols, &[s]).unwrap();
497 assert_eq!(out_int(&rows, 1), vec![1, 1, 3]);
498 }
499
500 #[test]
501 fn dense_rank_handles_ties() {
502 let mut rows = rows_v(&[10, 10, 20]);
503 let cols = ci(&["v"]);
504 let s = spec("dense_rank", vec![], vec![], vec![(col("v"), true)]);
505 evaluate_window_functions_value(&mut rows, &cols, &[s]).unwrap();
506 assert_eq!(out_int(&rows, 1), vec![1, 1, 2]);
507 }
508
509 #[test]
510 fn ntile_buckets() {
511 let mut rows = rows_v(&[1, 2, 3]);
512 let cols = ci(&["v"]);
513 let s = spec(
514 "ntile",
515 vec![SqlExpr::Literal(Value::Integer(2))],
516 vec![],
517 vec![(col("v"), true)],
518 );
519 evaluate_window_functions_value(&mut rows, &cols, &[s]).unwrap();
520 assert_eq!(out_int(&rows, 1), vec![1, 1, 2]);
521 }
522
523 #[test]
524 fn lag_default_and_offset() {
525 let mut rows = rows_v(&[1, 2, 3]);
526 let cols = ci(&["v"]);
527 let s = spec("lag", vec![col("v")], vec![], vec![(col("v"), true)]);
528 evaluate_window_functions_value(&mut rows, &cols, &[s]).unwrap();
529 assert!(matches!(rows[0][1], Value::Null));
531 assert_eq!(rows[1][1].as_f64().unwrap() as i64, 1);
532 assert_eq!(rows[2][1].as_f64().unwrap() as i64, 2);
533 }
534
535 #[test]
536 fn lead_boundary() {
537 let mut rows = rows_v(&[1, 2, 3]);
538 let cols = ci(&["v"]);
539 let s = spec("lead", vec![col("v")], vec![], vec![(col("v"), true)]);
540 evaluate_window_functions_value(&mut rows, &cols, &[s]).unwrap();
541 assert_eq!(rows[0][1].as_f64().unwrap() as i64, 2);
542 assert_eq!(rows[1][1].as_f64().unwrap() as i64, 3);
543 assert!(matches!(rows[2][1], Value::Null));
545 }
546
547 #[test]
548 fn percent_rank_and_cume_dist() {
549 let cols = ci(&["v"]);
550
551 let mut rows = rows_v(&[10, 10, 20]);
552 let pr = spec("percent_rank", vec![], vec![], vec![(col("v"), true)]);
553 evaluate_window_functions_value(&mut rows, &cols, &[pr]).unwrap();
554 let pr_out = out_f64(&rows, 1);
555 assert!((pr_out[0] - 0.0).abs() < 1e-9);
556 assert!((pr_out[1] - 0.0).abs() < 1e-9);
557 assert!((pr_out[2] - 1.0).abs() < 1e-9);
558
559 let mut rows = rows_v(&[10, 10, 20]);
560 let cd = spec("cume_dist", vec![], vec![], vec![(col("v"), true)]);
561 evaluate_window_functions_value(&mut rows, &cols, &[cd]).unwrap();
562 let cd_out = out_f64(&rows, 1);
563 assert!((cd_out[0] - 2.0 / 3.0).abs() < 1e-9);
564 assert!((cd_out[1] - 2.0 / 3.0).abs() < 1e-9);
565 assert!((cd_out[2] - 1.0).abs() < 1e-9);
566 }
567
568 #[test]
569 fn partition_resets_row_number() {
570 let cols = ci(&["g", "v"]);
571 let mut rows = vec![
572 vec![Value::Integer(1), Value::Integer(100)],
573 vec![Value::Integer(1), Value::Integer(101)],
574 vec![Value::Integer(2), Value::Integer(102)],
575 ];
576 let s = spec("row_number", vec![], vec![col("g")], vec![]);
577 evaluate_window_functions_value(&mut rows, &cols, &[s]).unwrap();
578 assert_eq!(out_int(&rows, 2), vec![1, 2, 1]);
580 }
581
582 #[test]
583 fn rank_orders_by_spec_order_by_not_row_arrival_order() {
584 let mut rows = rows_v(&[10, 30, 20]);
588 let cols = ci(&["v"]);
589 let s = spec("rank", vec![], vec![], vec![(col("v"), false)]);
590 evaluate_window_functions_value(&mut rows, &cols, &[s]).unwrap();
591 assert_eq!(out_int(&rows, 0), vec![10, 30, 20]);
592 assert_eq!(out_int(&rows, 1), vec![3, 1, 2]);
593 }
594
595 #[test]
596 fn unknown_function_errors() {
597 let mut rows = rows_v(&[1]);
598 let cols = ci(&["v"]);
599 let s = spec("nonexistent", vec![], vec![], vec![]);
600 let err = evaluate_window_functions_value(&mut rows, &cols, &[s]).unwrap_err();
601 assert!(matches!(err, WindowError::ArgEval { .. }));
602 }
603
604 #[test]
605 fn missing_partition_column_is_null_keyed() {
606 let cols = ci(&["v"]);
609 let mut rows = rows_v(&[1, 2]);
610 let s = spec("row_number", vec![], vec![col("missing")], vec![]);
611 evaluate_window_functions_value(&mut rows, &cols, &[s]).unwrap();
612 assert_eq!(out_int(&rows, 1), vec![1, 2]);
613 }
614}