nodedb_query/window/
value_partition.rs1use std::collections::HashMap;
7
8use nodedb_types::Value;
9
10use super::helpers::null_order;
11use super::spec::WindowFuncSpec;
12use super::value_eval::{WindowError, cmp_values, eval_arg_for_row};
13use crate::expr::types::SqlExpr;
14
15pub(super) fn build_value_partitions(
21 rows: &[Vec<Value>],
22 column_index: &HashMap<String, usize>,
23 spec: &WindowFuncSpec,
24) -> Result<Vec<Vec<usize>>, WindowError> {
25 let mut partitions = if spec.partition_by.is_empty() {
26 vec![(0..rows.len()).collect::<Vec<usize>>()]
27 } else {
28 let mut groups: HashMap<String, Vec<usize>> = HashMap::new();
29 let mut order: Vec<String> = Vec::new();
30
31 for (i, row) in rows.iter().enumerate() {
32 let key = partition_key(row, column_index, &spec.partition_by)?;
33 let entry = groups.entry(key.clone()).or_default();
34 if entry.is_empty() {
35 order.push(key);
36 }
37 entry.push(i);
38 }
39
40 order.iter().filter_map(|k| groups.remove(k)).collect()
41 };
42
43 if !spec.order_by.is_empty() {
44 let mut keys: Vec<Vec<Value>> = Vec::with_capacity(rows.len());
45 for row in rows.iter() {
46 keys.push(
47 spec.order_by
48 .iter()
49 .map(|(expr, _)| eval_arg_for_row(expr, row, column_index))
50 .collect::<Result<Vec<_>, _>>()?,
51 );
52 }
53
54 for partition in &mut partitions {
55 partition.sort_by(|&a, &b| compare_order_keys(&keys[a], &keys[b], &spec.order_by));
56 }
57 }
58
59 Ok(partitions)
60}
61
62fn partition_key(
63 row: &[Value],
64 column_index: &HashMap<String, usize>,
65 partition_by: &[SqlExpr],
66) -> Result<String, WindowError> {
67 Ok(partition_by
68 .iter()
69 .map(|expr| {
70 let v = eval_arg_for_row(expr, row, column_index)?;
71 Ok(format!("{v:?}"))
72 })
73 .collect::<Result<Vec<_>, WindowError>>()?
74 .join("\x00"))
75}
76
77fn compare_order_keys(
79 a: &[Value],
80 b: &[Value],
81 order_by: &[(SqlExpr, bool)],
82) -> std::cmp::Ordering {
83 use std::cmp::Ordering;
84 for (idx, (_, ascending)) in order_by.iter().enumerate() {
85 let (Some(va), Some(vb)) = (a.get(idx), b.get(idx)) else {
86 continue;
87 };
88 let ord = null_order(
89 matches!(va, Value::Null),
90 matches!(vb, Value::Null),
91 *ascending,
92 )
93 .unwrap_or_else(|| {
94 let c = cmp_values(va, vb);
95 if *ascending { c } else { c.reverse() }
96 });
97 if ord != Ordering::Equal {
98 return ord;
99 }
100 }
101 Ordering::Equal
102}