fraiseql_core/runtime/
window_projector.rs1use std::collections::HashMap;
35
36use serde_json::Value;
37
38use crate::{compiler::window_functions::WindowExecutionPlan, error::Result};
39
40pub struct WindowProjector;
44
45impl WindowProjector {
46 pub fn project(
79 rows: Vec<HashMap<String, Value>>,
80 _plan: &WindowExecutionPlan,
81 ) -> Result<Value> {
82 let projected_rows: Vec<Value> = rows
90 .into_iter()
91 .map(|row| {
92 let mut obj = serde_json::Map::new();
93 for (key, value) in row {
94 obj.insert(key, value);
95 }
96 Value::Object(obj)
97 })
98 .collect();
99
100 Ok(Value::Array(projected_rows))
101 }
102
103 #[must_use]
125 pub fn wrap_in_data_envelope(projected: Value, query_name: &str) -> Value {
126 let mut data = serde_json::Map::new();
127 data.insert(query_name.to_string(), projected);
128
129 let mut response = serde_json::Map::new();
130 response.insert("data".to_string(), Value::Object(data));
131
132 Value::Object(response)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 #![allow(clippy::unwrap_used)] use serde_json::json;
141
142 use super::*;
143 use crate::compiler::window_functions::{
144 SelectColumn, WindowExecutionPlan, WindowFunction, WindowFunctionType,
145 };
146
147 fn create_test_plan() -> WindowExecutionPlan {
148 WindowExecutionPlan {
149 table: "tf_sales".to_string(),
150 select: vec![
151 SelectColumn {
152 expression: "revenue".to_string(),
153 alias: "revenue".to_string(),
154 },
155 SelectColumn {
156 expression: "category".to_string(),
157 alias: "category".to_string(),
158 },
159 ],
160 windows: vec![WindowFunction {
161 function: WindowFunctionType::RowNumber,
162 alias: "rank".to_string(),
163 partition_by: vec!["category".to_string()],
164 order_by: vec![],
165 frame: None,
166 }],
167 where_clause: None,
168 order_by: vec![],
169 limit: None,
170 offset: None,
171 }
172 }
173
174 #[test]
175 fn test_project_empty_results() {
176 let plan = create_test_plan();
177 let rows: Vec<HashMap<String, Value>> = vec![];
178
179 let result = WindowProjector::project(rows, &plan).unwrap();
180 assert_eq!(result, json!([]));
181 }
182
183 #[test]
184 fn test_project_single_row() {
185 let plan = create_test_plan();
186 let mut row = HashMap::new();
187 row.insert("revenue".to_string(), json!(100.00));
188 row.insert("category".to_string(), json!("Electronics"));
189 row.insert("rank".to_string(), json!(1));
190
191 let rows = vec![row];
192 let result = WindowProjector::project(rows, &plan).unwrap();
193
194 let expected = json!([
195 {"revenue": 100.00, "category": "Electronics", "rank": 1}
196 ]);
197 assert_eq!(result, expected);
198 }
199
200 #[test]
201 fn test_project_multiple_rows() {
202 let plan = create_test_plan();
203
204 let mut row1 = HashMap::new();
205 row1.insert("revenue".to_string(), json!(100.00));
206 row1.insert("category".to_string(), json!("Electronics"));
207 row1.insert("rank".to_string(), json!(1));
208
209 let mut row2 = HashMap::new();
210 row2.insert("revenue".to_string(), json!(150.00));
211 row2.insert("category".to_string(), json!("Electronics"));
212 row2.insert("rank".to_string(), json!(2));
213
214 let mut row3 = HashMap::new();
215 row3.insert("revenue".to_string(), json!(50.00));
216 row3.insert("category".to_string(), json!("Books"));
217 row3.insert("rank".to_string(), json!(1));
218
219 let rows = vec![row1, row2, row3];
220 let result = WindowProjector::project(rows, &plan).unwrap();
221
222 let expected = json!([
223 {"revenue": 100.00, "category": "Electronics", "rank": 1},
224 {"revenue": 150.00, "category": "Electronics", "rank": 2},
225 {"revenue": 50.00, "category": "Books", "rank": 1}
226 ]);
227 assert_eq!(result, expected);
228 }
229
230 #[test]
231 fn test_wrap_in_data_envelope() {
232 let projected = json!([{"rank": 1}, {"rank": 2}]);
233 let response = WindowProjector::wrap_in_data_envelope(projected, "sales_window");
234
235 let expected = json!({
236 "data": {
237 "sales_window": [{"rank": 1}, {"rank": 2}]
238 }
239 });
240 assert_eq!(response, expected);
241 }
242
243 #[test]
244 fn test_project_with_null_values() {
245 let plan = create_test_plan();
246
247 let mut row = HashMap::new();
248 row.insert("revenue".to_string(), json!(null));
249 row.insert("category".to_string(), json!("Unknown"));
250 row.insert("rank".to_string(), json!(1));
251
252 let rows = vec![row];
253 let result = WindowProjector::project(rows, &plan).unwrap();
254
255 let expected = json!([
256 {"revenue": null, "category": "Unknown", "rank": 1}
257 ]);
258 assert_eq!(result, expected);
259 }
260
261 #[test]
262 fn test_project_with_numeric_types() {
263 let plan = create_test_plan();
264
265 let mut row = HashMap::new();
266 row.insert("revenue".to_string(), json!(1234.56));
267 row.insert("category".to_string(), json!("Electronics"));
268 row.insert("rank".to_string(), json!(1));
269 row.insert("running_total".to_string(), json!(5000.00));
270 row.insert("row_count".to_string(), json!(42));
271
272 let rows = vec![row];
273 let result = WindowProjector::project(rows, &plan).unwrap();
274
275 let arr = result.as_array().unwrap();
277 let first_row = &arr[0];
278 assert_eq!(first_row["revenue"], json!(1234.56));
279 assert_eq!(first_row["rank"], json!(1));
280 assert_eq!(first_row["running_total"], json!(5000.00));
281 assert_eq!(first_row["row_count"], json!(42));
282 }
283}