1use serde_json::Value;
52
53use super::aggregate_parser::split_where_key;
54use crate::{
55 backend::where_clause::WhereClause,
56 compiler::{
57 aggregation::OrderDirection,
58 fact_table::FactTableMetadata,
59 window_functions::{
60 FrameBoundary, FrameExclusion, FrameType, PartitionByColumn, WindowFrame,
61 WindowFunctionRequest, WindowFunctionSpec, WindowOrderBy, WindowRequest,
62 WindowSelectColumn,
63 },
64 },
65 error::{FraiseQLError, Result},
66};
67
68pub struct WindowQueryParser;
70
71impl WindowQueryParser {
72 pub fn parse(query_json: &Value, metadata: &FactTableMetadata) -> Result<WindowRequest> {
83 let table_name = query_json
85 .get("table")
86 .and_then(|v| v.as_str())
87 .ok_or_else(|| FraiseQLError::Validation {
88 message: "Missing 'table' field in window query".to_string(),
89 path: None,
90 })?
91 .to_string();
92
93 let select = if let Some(select_array) = query_json.get("select") {
95 Self::parse_select_columns(select_array)?
96 } else {
97 vec![]
98 };
99
100 let windows = if let Some(windows_array) = query_json.get("windows") {
102 Self::parse_window_functions(windows_array)?
103 } else {
104 vec![]
105 };
106
107 let where_clause = if let Some(where_obj) = query_json.get("where") {
109 Some(Self::parse_where_clause(where_obj, metadata)?)
110 } else {
111 None
112 };
113
114 let order_by = if let Some(order_array) = query_json.get("orderBy") {
116 Self::parse_order_by(order_array)?
117 } else {
118 vec![]
119 };
120
121 let limit = query_json
123 .get("limit")
124 .and_then(|v| v.as_u64())
125 .map(|n| u32::try_from(n).unwrap_or(u32::MAX));
126
127 let offset = query_json
128 .get("offset")
129 .and_then(|v| v.as_u64())
130 .map(|n| u32::try_from(n).unwrap_or(u32::MAX));
131
132 Ok(WindowRequest {
133 table_name,
134 select,
135 windows,
136 where_clause,
137 order_by,
138 limit,
139 offset,
140 })
141 }
142
143 fn parse_select_columns(select_array: &Value) -> Result<Vec<WindowSelectColumn>> {
145 let Some(arr) = select_array.as_array() else {
146 return Ok(vec![]);
147 };
148
149 arr.iter().map(Self::parse_single_select_column).collect()
150 }
151
152 fn parse_single_select_column(col: &Value) -> Result<WindowSelectColumn> {
153 let col_type =
154 col.get("type")
155 .and_then(|v| v.as_str())
156 .ok_or_else(|| FraiseQLError::Validation {
157 message: "Missing 'type' in select column".to_string(),
158 path: None,
159 })?;
160
161 let alias = col
162 .get("alias")
163 .and_then(|v| v.as_str())
164 .ok_or_else(|| FraiseQLError::Validation {
165 message: "Missing 'alias' in select column".to_string(),
166 path: None,
167 })?
168 .to_string();
169
170 match col_type {
171 "measure" => {
172 let name = col
173 .get("name")
174 .and_then(|v| v.as_str())
175 .ok_or_else(|| FraiseQLError::Validation {
176 message: "Missing 'name' in measure select column".to_string(),
177 path: None,
178 })?
179 .to_string();
180 Ok(WindowSelectColumn::Measure { name, alias })
181 },
182 "dimension" => {
183 let path = col
184 .get("path")
185 .and_then(|v| v.as_str())
186 .ok_or_else(|| FraiseQLError::Validation {
187 message: "Missing 'path' in dimension select column".to_string(),
188 path: None,
189 })?
190 .to_string();
191 Ok(WindowSelectColumn::Dimension { path, alias })
192 },
193 "filter" => {
194 let name = col
195 .get("name")
196 .and_then(|v| v.as_str())
197 .ok_or_else(|| FraiseQLError::Validation {
198 message: "Missing 'name' in filter select column".to_string(),
199 path: None,
200 })?
201 .to_string();
202 Ok(WindowSelectColumn::Filter { name, alias })
203 },
204 _ => Err(FraiseQLError::Validation {
205 message: format!("Unknown select column type: {col_type}"),
206 path: None,
207 }),
208 }
209 }
210
211 fn parse_window_functions(windows_array: &Value) -> Result<Vec<WindowFunctionRequest>> {
213 let Some(arr) = windows_array.as_array() else {
214 return Ok(vec![]);
215 };
216
217 arr.iter().map(Self::parse_single_window_function).collect()
218 }
219
220 fn parse_single_window_function(window: &Value) -> Result<WindowFunctionRequest> {
221 let function = window
223 .get("function")
224 .ok_or_else(|| FraiseQLError::Validation {
225 message: "Missing 'function' in window definition".to_string(),
226 path: None,
227 })
228 .and_then(Self::parse_function_spec)?;
229
230 let alias = window
232 .get("alias")
233 .and_then(|v| v.as_str())
234 .ok_or_else(|| FraiseQLError::Validation {
235 message: "Missing 'alias' in window definition".to_string(),
236 path: None,
237 })?
238 .to_string();
239
240 let partition_by = if let Some(partition_array) = window.get("partitionBy") {
242 Self::parse_partition_by(partition_array)?
243 } else {
244 vec![]
245 };
246
247 let order_by = if let Some(order_array) = window.get("orderBy") {
249 Self::parse_order_by(order_array)?
250 } else {
251 vec![]
252 };
253
254 let frame = window.get("frame").map(Self::parse_frame).transpose()?;
256
257 Ok(WindowFunctionRequest {
258 function,
259 alias,
260 partition_by,
261 order_by,
262 frame,
263 })
264 }
265
266 fn parse_function_spec(func: &Value) -> Result<WindowFunctionSpec> {
268 let func_type =
269 func.get("type")
270 .and_then(|v| v.as_str())
271 .ok_or_else(|| FraiseQLError::Validation {
272 message: "Missing 'type' in function spec".to_string(),
273 path: None,
274 })?;
275
276 match func_type {
277 "row_number" => Ok(WindowFunctionSpec::RowNumber),
279 "rank" => Ok(WindowFunctionSpec::Rank),
280 "dense_rank" => Ok(WindowFunctionSpec::DenseRank),
281 "ntile" => {
282 let n = u32::try_from(func.get("n").and_then(|v| v.as_u64()).ok_or_else(|| {
283 FraiseQLError::Validation {
284 message: "Missing 'n' in NTILE function".to_string(),
285 path: None,
286 }
287 })?)
288 .unwrap_or(u32::MAX);
289 Ok(WindowFunctionSpec::Ntile { n })
290 },
291 "percent_rank" => Ok(WindowFunctionSpec::PercentRank),
292 "cume_dist" => Ok(WindowFunctionSpec::CumeDist),
293
294 "lag" => {
296 let field = Self::extract_string_field(func, "field")?;
297 let offset =
298 i32::try_from(func.get("offset").and_then(|v| v.as_i64()).unwrap_or(1))
299 .unwrap_or(1);
300 let default = func.get("default").cloned();
301 Ok(WindowFunctionSpec::Lag {
302 field,
303 offset,
304 default,
305 })
306 },
307 "lead" => {
308 let field = Self::extract_string_field(func, "field")?;
309 let offset =
310 i32::try_from(func.get("offset").and_then(|v| v.as_i64()).unwrap_or(1))
311 .unwrap_or(1);
312 let default = func.get("default").cloned();
313 Ok(WindowFunctionSpec::Lead {
314 field,
315 offset,
316 default,
317 })
318 },
319 "first_value" => {
320 let field = Self::extract_string_field(func, "field")?;
321 Ok(WindowFunctionSpec::FirstValue { field })
322 },
323 "last_value" => {
324 let field = Self::extract_string_field(func, "field")?;
325 Ok(WindowFunctionSpec::LastValue { field })
326 },
327 "nth_value" => {
328 let field = Self::extract_string_field(func, "field")?;
329 let n = u32::try_from(func.get("n").and_then(|v| v.as_u64()).ok_or_else(|| {
330 FraiseQLError::Validation {
331 message: "Missing 'n' in NTH_VALUE function".to_string(),
332 path: None,
333 }
334 })?)
335 .unwrap_or(u32::MAX);
336 Ok(WindowFunctionSpec::NthValue { field, n })
337 },
338
339 "running_sum" => {
341 let measure = Self::extract_string_field(func, "measure")?;
342 Ok(WindowFunctionSpec::RunningSum { measure })
343 },
344 "running_avg" => {
345 let measure = Self::extract_string_field(func, "measure")?;
346 Ok(WindowFunctionSpec::RunningAvg { measure })
347 },
348 "running_count" => {
349 if let Some(field) = func.get("field").and_then(|v| v.as_str()) {
350 Ok(WindowFunctionSpec::RunningCountField {
351 field: field.to_string(),
352 })
353 } else {
354 Ok(WindowFunctionSpec::RunningCount)
355 }
356 },
357 "running_min" => {
358 let measure = Self::extract_string_field(func, "measure")?;
359 Ok(WindowFunctionSpec::RunningMin { measure })
360 },
361 "running_max" => {
362 let measure = Self::extract_string_field(func, "measure")?;
363 Ok(WindowFunctionSpec::RunningMax { measure })
364 },
365 "running_stddev" => {
366 let measure = Self::extract_string_field(func, "measure")?;
367 Ok(WindowFunctionSpec::RunningStddev { measure })
368 },
369 "running_variance" => {
370 let measure = Self::extract_string_field(func, "measure")?;
371 Ok(WindowFunctionSpec::RunningVariance { measure })
372 },
373
374 _ => Err(FraiseQLError::Validation {
375 message: format!("Unknown window function type: {func_type}"),
376 path: None,
377 }),
378 }
379 }
380
381 fn extract_string_field(obj: &Value, field_name: &str) -> Result<String> {
383 obj.get(field_name).and_then(|v| v.as_str()).map(String::from).ok_or_else(|| {
384 FraiseQLError::Validation {
385 message: format!("Missing '{field_name}' in function spec"),
386 path: None,
387 }
388 })
389 }
390
391 fn parse_partition_by(partition_array: &Value) -> Result<Vec<PartitionByColumn>> {
393 let Some(arr) = partition_array.as_array() else {
394 return Ok(vec![]);
395 };
396
397 arr.iter()
398 .map(|item| {
399 let col_type = item.get("type").and_then(|v| v.as_str()).ok_or_else(|| {
400 FraiseQLError::Validation {
401 message: "Missing 'type' in partitionBy column".to_string(),
402 path: None,
403 }
404 })?;
405
406 match col_type {
407 "dimension" => {
408 let path = item
409 .get("path")
410 .and_then(|v| v.as_str())
411 .ok_or_else(|| FraiseQLError::Validation {
412 message: "Missing 'path' in dimension partition column".to_string(),
413 path: None,
414 })?
415 .to_string();
416 Ok(PartitionByColumn::Dimension { path })
417 },
418 "filter" => {
419 let name = item
420 .get("name")
421 .and_then(|v| v.as_str())
422 .ok_or_else(|| FraiseQLError::Validation {
423 message: "Missing 'name' in filter partition column".to_string(),
424 path: None,
425 })?
426 .to_string();
427 Ok(PartitionByColumn::Filter { name })
428 },
429 "measure" => {
430 let name = item
431 .get("name")
432 .and_then(|v| v.as_str())
433 .ok_or_else(|| FraiseQLError::Validation {
434 message: "Missing 'name' in measure partition column".to_string(),
435 path: None,
436 })?
437 .to_string();
438 Ok(PartitionByColumn::Measure { name })
439 },
440 _ => Err(FraiseQLError::Validation {
441 message: format!("Unknown partition column type: {col_type}"),
442 path: None,
443 }),
444 }
445 })
446 .collect()
447 }
448
449 fn parse_order_by(order_array: &Value) -> Result<Vec<WindowOrderBy>> {
451 let Some(arr) = order_array.as_array() else {
452 return Ok(vec![]);
453 };
454
455 arr.iter()
456 .map(|item| {
457 let field = item
458 .get("field")
459 .and_then(|v| v.as_str())
460 .ok_or_else(|| FraiseQLError::Validation {
461 message: "Missing 'field' in orderBy".to_string(),
462 path: None,
463 })?
464 .to_string();
465
466 let direction = match item.get("direction").and_then(|v| v.as_str()) {
467 Some("DESC" | "desc") => OrderDirection::Desc,
468 _ => OrderDirection::Asc,
469 };
470
471 Ok(WindowOrderBy { field, direction })
472 })
473 .collect()
474 }
475
476 fn parse_where_clause(where_obj: &Value, metadata: &FactTableMetadata) -> Result<WhereClause> {
478 let Some(obj) = where_obj.as_object() else {
479 return Ok(WhereClause::And(vec![]));
480 };
481
482 let no_native_columns = std::collections::HashMap::new();
483 let mut conditions = Vec::new();
484 for (key, value) in obj {
485 let (field, operator) = split_where_key(key, metadata, &no_native_columns)?;
486 conditions.push(WhereClause::Field {
487 path: vec![crate::utils::to_snake_case(field)],
491 operator,
492 value: value.clone(),
493 });
494 }
495
496 Ok(WhereClause::And(conditions))
497 }
498
499 fn parse_frame(frame: &Value) -> Result<WindowFrame> {
501 let frame_type = match frame.get("frame_type").and_then(|v| v.as_str()) {
502 Some("ROWS") => FrameType::Rows,
503 Some("RANGE") => FrameType::Range,
504 Some("GROUPS") => FrameType::Groups,
505 _ => {
506 return Err(FraiseQLError::Validation {
507 message: "Invalid or missing 'frame_type' in frame".to_string(),
508 path: None,
509 });
510 },
511 };
512
513 let start = frame
514 .get("start")
515 .ok_or_else(|| FraiseQLError::Validation {
516 message: "Missing 'start' in frame".to_string(),
517 path: None,
518 })
519 .and_then(Self::parse_frame_boundary)?;
520
521 let end = frame
522 .get("end")
523 .ok_or_else(|| FraiseQLError::Validation {
524 message: "Missing 'end' in frame".to_string(),
525 path: None,
526 })
527 .and_then(Self::parse_frame_boundary)?;
528
529 let exclusion = frame.get("exclusion").and_then(|v| v.as_str()).map(|s| match s {
530 "current_row" => FrameExclusion::CurrentRow,
531 "group" => FrameExclusion::Group,
532 "ties" => FrameExclusion::Ties,
533 _ => FrameExclusion::NoOthers,
534 });
535
536 Ok(WindowFrame {
537 frame_type,
538 start,
539 end,
540 exclusion,
541 })
542 }
543
544 fn parse_frame_boundary(boundary: &Value) -> Result<FrameBoundary> {
546 let boundary_type = boundary.get("type").and_then(|v| v.as_str()).ok_or_else(|| {
547 FraiseQLError::Validation {
548 message: "Missing 'type' in frame boundary".to_string(),
549 path: None,
550 }
551 })?;
552
553 match boundary_type {
554 "unbounded_preceding" => Ok(FrameBoundary::UnboundedPreceding),
555 "n_preceding" => {
556 let n =
557 u32::try_from(boundary.get("n").and_then(|v| v.as_u64()).ok_or_else(|| {
558 FraiseQLError::Validation {
559 message: "Missing 'n' in N PRECEDING boundary".to_string(),
560 path: None,
561 }
562 })?)
563 .unwrap_or(u32::MAX);
564 Ok(FrameBoundary::NPreceding { n })
565 },
566 "current_row" => Ok(FrameBoundary::CurrentRow),
567 "n_following" => {
568 let n =
569 u32::try_from(boundary.get("n").and_then(|v| v.as_u64()).ok_or_else(|| {
570 FraiseQLError::Validation {
571 message: "Missing 'n' in N FOLLOWING boundary".to_string(),
572 path: None,
573 }
574 })?)
575 .unwrap_or(u32::MAX);
576 Ok(FrameBoundary::NFollowing { n })
577 },
578 "unbounded_following" => Ok(FrameBoundary::UnboundedFollowing),
579 _ => Err(FraiseQLError::Validation {
580 message: format!("Unknown frame boundary type: {boundary_type}"),
581 path: None,
582 }),
583 }
584 }
585}