1use super::aggregate::apply_aggregate_window;
6use super::helpers::build_partitions;
7use super::offset::{apply_lag, apply_lead, apply_nth_value};
8use super::ranking::{
9 apply_cume_dist, apply_dense_rank, apply_ntile, apply_percent_rank, apply_rank,
10 apply_row_number,
11};
12use super::spec::WindowFuncSpec;
13
14pub fn evaluate_window_functions(
30 rows: &mut [(String, nodedb_types::Value)],
31 specs: &[WindowFuncSpec],
32) -> Result<(), crate::expr::EvalError> {
33 for spec in specs {
34 let partitions = build_partitions(rows, &spec.partition_by, &spec.order_by)?;
35
36 for partition_indices in &partitions {
37 match spec.func_name.as_str() {
38 "row_number" => apply_row_number(rows, partition_indices, &spec.alias),
39 "rank" => apply_rank(rows, partition_indices, &spec.alias, &spec.order_by)?,
40 "dense_rank" => {
41 apply_dense_rank(rows, partition_indices, &spec.alias, &spec.order_by)?
42 }
43 "ntile" => apply_ntile(rows, partition_indices, spec),
44 "percent_rank" => {
45 apply_percent_rank(rows, partition_indices, &spec.alias, &spec.order_by)?
46 }
47 "cume_dist" => {
48 apply_cume_dist(rows, partition_indices, &spec.alias, &spec.order_by)?
49 }
50 "lag" => apply_lag(rows, partition_indices, spec)?,
51 "lead" => apply_lead(rows, partition_indices, spec)?,
52 "nth_value" => apply_nth_value(rows, partition_indices, spec)?,
53 "sum" | "count" | "avg" | "min" | "max" | "first_value" | "last_value" => {
54 apply_aggregate_window(rows, partition_indices, spec)?
55 }
56 other => {
57 unreachable!(
58 "invariant: SQL planner validates window function names before dispatch; '{other}' is unrecognized and should have been rejected at planning time"
59 )
60 }
61 }
62 }
63 }
64 Ok(())
65}
66
67#[cfg(test)]
68mod tests {
69 use super::super::spec::{WindowFrame, WindowFuncSpec};
70 use super::evaluate_window_functions;
71 use crate::expr::SqlExpr;
72 use nodedb_types::Value;
73 use serde_json::json;
74
75 type Row = (String, Value);
76
77 fn row(id: &str, doc: serde_json::Value) -> Row {
78 (id.to_string(), Value::from(doc))
79 }
80
81 fn col(row: &Row, name: &str) -> serde_json::Value {
83 serde_json::Value::from(row.1.get(name).cloned().unwrap_or(Value::Null))
84 }
85
86 fn make_rows() -> Vec<Row> {
87 vec![
88 row("1", json!({"dept": "eng", "salary": 100, "name": "Alice"})),
89 row("2", json!({"dept": "eng", "salary": 120, "name": "Bob"})),
90 row("3", json!({"dept": "eng", "salary": 90, "name": "Carol"})),
91 row("4", json!({"dept": "sales", "salary": 80, "name": "Dave"})),
92 row("5", json!({"dept": "sales", "salary": 110, "name": "Eve"})),
93 ]
94 }
95
96 fn numbered(n: usize) -> Vec<Row> {
97 (1..=n)
98 .map(|i| row(&i.to_string(), json!({ "n": i })))
99 .collect()
100 }
101
102 fn peers() -> Vec<Row> {
103 vec![
104 row("a", json!({"n": 1})),
105 row("b", json!({"n": 1})),
106 row("c", json!({"n": 2})),
107 row("d", json!({"n": 3})),
108 ]
109 }
110
111 fn ordered_by_n(alias: &str, func: &str) -> WindowFuncSpec {
112 WindowFuncSpec {
113 alias: alias.into(),
114 func_name: func.into(),
115 args: vec![],
116 partition_by: vec![],
117 order_by: vec![(SqlExpr::Column("n".into()), true)],
118 frame: WindowFrame::default(),
119 }
120 }
121
122 #[test]
123 fn row_number_single_partition() {
124 let mut rows = make_rows();
125 let spec = WindowFuncSpec {
126 alias: "rn".into(),
127 func_name: "row_number".into(),
128 args: vec![],
129 partition_by: vec![],
130 order_by: vec![],
131 frame: WindowFrame::default(),
132 };
133 evaluate_window_functions(&mut rows, &[spec]).unwrap();
134 assert_eq!(col(&rows[0], "rn"), json!(1));
135 assert_eq!(col(&rows[4], "rn"), json!(5));
136 }
137
138 #[test]
139 fn row_number_partitioned() {
140 let mut rows = make_rows();
141 let spec = WindowFuncSpec {
142 alias: "rn".into(),
143 func_name: "row_number".into(),
144 args: vec![],
145 partition_by: vec![SqlExpr::Column("dept".into())],
146 order_by: vec![],
147 frame: WindowFrame::default(),
148 };
149 evaluate_window_functions(&mut rows, &[spec]).unwrap();
150 assert_eq!(col(&rows[0], "rn"), json!(1));
151 assert_eq!(col(&rows[2], "rn"), json!(3));
152 assert_eq!(col(&rows[3], "rn"), json!(1));
153 assert_eq!(col(&rows[4], "rn"), json!(2));
154 }
155
156 #[test]
157 fn running_sum() {
158 let mut rows = make_rows();
159 let spec = WindowFuncSpec {
160 alias: "running_total".into(),
161 func_name: "sum".into(),
162 args: vec![SqlExpr::Column("salary".into())],
163 partition_by: vec![SqlExpr::Column("dept".into())],
164 order_by: vec![(SqlExpr::Column("salary".into()), true)],
165 frame: WindowFrame::default(),
166 };
167 evaluate_window_functions(&mut rows, &[spec]).unwrap();
168 assert_eq!(col(&rows[0], "running_total"), json!(190));
172 assert_eq!(col(&rows[1], "running_total"), json!(310));
173 assert_eq!(col(&rows[2], "running_total"), json!(90));
174 assert_eq!(col(&rows[3], "running_total"), json!(80));
175 assert_eq!(col(&rows[4], "running_total"), json!(190));
176 }
177
178 #[test]
180 fn running_sum_keeps_infinity() {
181 let mut rows = vec![
182 (
183 "a".to_string(),
184 Value::Object(
185 [
186 ("n".to_string(), Value::Integer(1)),
187 ("x".to_string(), Value::Float(1e308)),
188 ]
189 .into(),
190 ),
191 ),
192 (
193 "b".to_string(),
194 Value::Object(
195 [
196 ("n".to_string(), Value::Integer(2)),
197 ("x".to_string(), Value::Float(1e308)),
198 ]
199 .into(),
200 ),
201 ),
202 ];
203 let mut spec = ordered_by_n("total", "sum");
204 spec.args = vec![SqlExpr::Column("x".into())];
205 evaluate_window_functions(&mut rows, &[spec]).unwrap();
206 assert_eq!(rows[0].1.get("total"), Some(&Value::Float(1e308)));
207 assert_eq!(rows[1].1.get("total"), Some(&Value::Float(f64::INFINITY)));
208 }
209
210 #[test]
212 fn nan_order_keys_rank_last_as_peers() {
213 let doc = |n: f64| Value::Object([("n".to_string(), Value::Float(n))].into());
214 let mut rows = vec![
215 ("a".to_string(), doc(f64::NAN)),
216 ("b".to_string(), doc(2.0)),
217 ("c".to_string(), doc(f64::NAN)),
218 ("d".to_string(), doc(f64::INFINITY)),
219 ];
220 evaluate_window_functions(&mut rows, &[ordered_by_n("rnk", "rank")]).unwrap();
221 assert_eq!(col(&rows[1], "rnk"), json!(1));
222 assert_eq!(col(&rows[3], "rnk"), json!(2));
223 assert_eq!(col(&rows[0], "rnk"), json!(3));
224 assert_eq!(col(&rows[2], "rnk"), json!(3));
225 }
226
227 #[test]
228 fn percent_rank_distinct_keys() {
229 let mut rows = numbered(5);
230 evaluate_window_functions(&mut rows, &[ordered_by_n("pr", "percent_rank")]).unwrap();
231 assert_eq!(col(&rows[0], "pr"), json!(0.0));
232 assert_eq!(col(&rows[1], "pr"), json!(0.25));
233 assert_eq!(col(&rows[2], "pr"), json!(0.5));
234 assert_eq!(col(&rows[3], "pr"), json!(0.75));
235 assert_eq!(col(&rows[4], "pr"), json!(1.0));
236 }
237
238 #[test]
239 fn percent_rank_with_peers() {
240 let mut rows = peers();
243 evaluate_window_functions(&mut rows, &[ordered_by_n("pr", "percent_rank")]).unwrap();
244 assert_eq!(col(&rows[0], "pr"), json!(0.0));
245 assert_eq!(col(&rows[1], "pr"), json!(0.0));
246 assert_eq!(col(&rows[2], "pr"), json!(2.0 / 3.0));
247 assert_eq!(col(&rows[3], "pr"), json!(1.0));
248 }
249
250 #[test]
251 fn cume_dist_distinct_keys() {
252 let mut rows = numbered(5);
253 evaluate_window_functions(&mut rows, &[ordered_by_n("cd", "cume_dist")]).unwrap();
254 assert_eq!(col(&rows[0], "cd"), json!(0.2));
255 assert_eq!(col(&rows[1], "cd"), json!(0.4));
256 assert_eq!(col(&rows[2], "cd"), json!(0.6));
257 assert_eq!(col(&rows[3], "cd"), json!(0.8));
258 assert_eq!(col(&rows[4], "cd"), json!(1.0));
259 }
260
261 #[test]
262 fn cume_dist_with_peers() {
263 let mut rows = peers();
264 evaluate_window_functions(&mut rows, &[ordered_by_n("cd", "cume_dist")]).unwrap();
265 assert_eq!(col(&rows[0], "cd"), json!(0.5));
267 assert_eq!(col(&rows[1], "cd"), json!(0.5));
268 assert_eq!(col(&rows[2], "cd"), json!(0.75));
269 assert_eq!(col(&rows[3], "cd"), json!(1.0));
270 }
271
272 #[test]
273 fn nth_value_returns_nth_then_holds() {
274 let mut rows = numbered(5);
275 let mut spec = ordered_by_n("nv", "nth_value");
276 spec.args = vec![
277 SqlExpr::Column("n".into()),
278 SqlExpr::Literal(Value::Integer(2)),
279 ];
280 evaluate_window_functions(&mut rows, &[spec]).unwrap();
281 assert_eq!(col(&rows[0], "nv"), json!(null));
282 assert_eq!(col(&rows[1], "nv"), json!(2));
283 assert_eq!(col(&rows[2], "nv"), json!(2));
284 assert_eq!(col(&rows[3], "nv"), json!(2));
285 assert_eq!(col(&rows[4], "nv"), json!(2));
286 }
287
288 #[test]
289 fn rank_orders_by_spec_order_by_not_row_arrival_order() {
290 let mut rows = make_rows();
294 let spec = WindowFuncSpec {
295 alias: "rnk".into(),
296 func_name: "rank".into(),
297 args: vec![],
298 partition_by: vec![SqlExpr::Column("dept".into())],
299 order_by: vec![(SqlExpr::Column("salary".into()), false)],
300 frame: WindowFrame::default(),
301 };
302 evaluate_window_functions(&mut rows, &[spec]).unwrap();
303 assert_eq!(col(&rows[0], "name"), json!("Alice"));
304 assert_eq!(col(&rows[1], "name"), json!("Bob"));
305 assert_eq!(col(&rows[2], "name"), json!("Carol"));
306 assert_eq!(col(&rows[3], "name"), json!("Dave"));
307 assert_eq!(col(&rows[4], "name"), json!("Eve"));
308 assert_eq!(col(&rows[0], "rnk"), json!(2)); assert_eq!(col(&rows[1], "rnk"), json!(1)); assert_eq!(col(&rows[2], "rnk"), json!(3)); assert_eq!(col(&rows[3], "rnk"), json!(2)); assert_eq!(col(&rows[4], "rnk"), json!(1)); }
314
315 #[test]
316 #[should_panic(expected = "should have been rejected at planning time")]
317 fn unknown_function_panics_at_evaluator() {
318 let mut rows = numbered(2);
319 let spec = WindowFuncSpec {
320 alias: "x".into(),
321 func_name: "frobnicate".into(),
322 args: vec![],
323 partition_by: vec![],
324 order_by: vec![],
325 frame: WindowFrame::default(),
326 };
327 evaluate_window_functions(&mut rows, &[spec]).unwrap();
328 }
329}