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(
72 rows: Vec<HashMap<String, Value>>,
73 _plan: &WindowExecutionPlan,
74 ) -> Result<Value> {
75 let projected_rows: Vec<Value> = rows
83 .into_iter()
84 .map(|row| {
85 let mut obj = serde_json::Map::new();
86 for (key, value) in row {
87 obj.insert(key, value);
88 }
89 Value::Object(obj)
90 })
91 .collect();
92
93 Ok(Value::Array(projected_rows))
94 }
95
96 #[must_use]
115 pub fn wrap_in_data_envelope(projected: Value, query_name: &str) -> Value {
116 let mut data = serde_json::Map::new();
117 data.insert(query_name.to_string(), projected);
118
119 let mut response = serde_json::Map::new();
120 response.insert("data".to_string(), Value::Object(data));
121
122 Value::Object(response)
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use serde_json::json;
129
130 use super::*;
131 use crate::compiler::window_functions::{
132 SelectColumn, WindowExecutionPlan, WindowFunction, WindowFunctionType,
133 };
134
135 fn create_test_plan() -> WindowExecutionPlan {
136 WindowExecutionPlan {
137 table: "tf_sales".to_string(),
138 select: vec![
139 SelectColumn {
140 expression: "revenue".to_string(),
141 alias: "revenue".to_string(),
142 },
143 SelectColumn {
144 expression: "category".to_string(),
145 alias: "category".to_string(),
146 },
147 ],
148 windows: vec![WindowFunction {
149 function: WindowFunctionType::RowNumber,
150 alias: "rank".to_string(),
151 partition_by: vec!["category".to_string()],
152 order_by: vec![],
153 frame: None,
154 }],
155 where_clause: None,
156 order_by: vec![],
157 limit: None,
158 offset: None,
159 }
160 }
161
162 #[test]
163 fn test_project_empty_results() {
164 let plan = create_test_plan();
165 let rows: Vec<HashMap<String, Value>> = vec![];
166
167 let result = WindowProjector::project(rows, &plan).unwrap();
168 assert_eq!(result, json!([]));
169 }
170
171 #[test]
172 fn test_project_single_row() {
173 let plan = create_test_plan();
174 let mut row = HashMap::new();
175 row.insert("revenue".to_string(), json!(100.00));
176 row.insert("category".to_string(), json!("Electronics"));
177 row.insert("rank".to_string(), json!(1));
178
179 let rows = vec![row];
180 let result = WindowProjector::project(rows, &plan).unwrap();
181
182 let expected = json!([
183 {"revenue": 100.00, "category": "Electronics", "rank": 1}
184 ]);
185 assert_eq!(result, expected);
186 }
187
188 #[test]
189 fn test_project_multiple_rows() {
190 let plan = create_test_plan();
191
192 let mut row1 = HashMap::new();
193 row1.insert("revenue".to_string(), json!(100.00));
194 row1.insert("category".to_string(), json!("Electronics"));
195 row1.insert("rank".to_string(), json!(1));
196
197 let mut row2 = HashMap::new();
198 row2.insert("revenue".to_string(), json!(150.00));
199 row2.insert("category".to_string(), json!("Electronics"));
200 row2.insert("rank".to_string(), json!(2));
201
202 let mut row3 = HashMap::new();
203 row3.insert("revenue".to_string(), json!(50.00));
204 row3.insert("category".to_string(), json!("Books"));
205 row3.insert("rank".to_string(), json!(1));
206
207 let rows = vec![row1, row2, row3];
208 let result = WindowProjector::project(rows, &plan).unwrap();
209
210 let expected = json!([
211 {"revenue": 100.00, "category": "Electronics", "rank": 1},
212 {"revenue": 150.00, "category": "Electronics", "rank": 2},
213 {"revenue": 50.00, "category": "Books", "rank": 1}
214 ]);
215 assert_eq!(result, expected);
216 }
217
218 #[test]
219 fn test_wrap_in_data_envelope() {
220 let projected = json!([{"rank": 1}, {"rank": 2}]);
221 let response = WindowProjector::wrap_in_data_envelope(projected, "sales_window");
222
223 let expected = json!({
224 "data": {
225 "sales_window": [{"rank": 1}, {"rank": 2}]
226 }
227 });
228 assert_eq!(response, expected);
229 }
230
231 #[test]
232 fn test_project_with_null_values() {
233 let plan = create_test_plan();
234
235 let mut row = HashMap::new();
236 row.insert("revenue".to_string(), json!(null));
237 row.insert("category".to_string(), json!("Unknown"));
238 row.insert("rank".to_string(), json!(1));
239
240 let rows = vec![row];
241 let result = WindowProjector::project(rows, &plan).unwrap();
242
243 let expected = json!([
244 {"revenue": null, "category": "Unknown", "rank": 1}
245 ]);
246 assert_eq!(result, expected);
247 }
248
249 #[test]
250 fn test_project_with_numeric_types() {
251 let plan = create_test_plan();
252
253 let mut row = HashMap::new();
254 row.insert("revenue".to_string(), json!(1234.56));
255 row.insert("category".to_string(), json!("Electronics"));
256 row.insert("rank".to_string(), json!(1));
257 row.insert("running_total".to_string(), json!(5000.00));
258 row.insert("row_count".to_string(), json!(42));
259
260 let rows = vec![row];
261 let result = WindowProjector::project(rows, &plan).unwrap();
262
263 let arr = result.as_array().unwrap();
265 let first_row = &arr[0];
266 assert_eq!(first_row["revenue"], json!(1234.56));
267 assert_eq!(first_row["rank"], json!(1));
268 assert_eq!(first_row["running_total"], json!(5000.00));
269 assert_eq!(first_row["row_count"], json!(42));
270 }
271}