Skip to main content

fraiseql_core/runtime/
window_projector.rs

1//! Window Function Result Projector
2//!
3//! Projects SQL window function results to GraphQL JSON.
4//!
5//! # Overview
6//!
7//! Window functions return rows with computed window values alongside regular columns.
8//! This module transforms raw SQL results into GraphQL-compatible JSON format.
9//!
10//! # Example
11//!
12//! SQL Result:
13//! ```text
14//! | revenue | category    | rank | running_total |
15//! |---------|-------------|------|---------------|
16//! | 100.00  | Electronics | 1    | 100.00        |
17//! | 150.00  | Electronics | 2    | 250.00        |
18//! | 50.00   | Books       | 1    | 50.00         |
19//! ```
20//!
21//! GraphQL Response:
22//! ```json
23//! {
24//!   "data": {
25//!     "sales_window": [
26//!       {"revenue": 100.00, "category": "Electronics", "rank": 1, "running_total": 100.00},
27//!       {"revenue": 150.00, "category": "Electronics", "rank": 2, "running_total": 250.00},
28//!       {"revenue": 50.00, "category": "Books", "rank": 1, "running_total": 50.00}
29//!     ]
30//!   }
31//! }
32//! ```
33
34use std::collections::HashMap;
35
36use serde_json::Value;
37
38use crate::{compiler::window_functions::WindowExecutionPlan, error::Result};
39
40/// Window function result projector.
41///
42/// Transforms SQL query results into GraphQL-compatible JSON format.
43pub struct WindowProjector;
44
45impl WindowProjector {
46    /// Project SQL window function results to GraphQL JSON.
47    ///
48    /// # Arguments
49    ///
50    /// * `rows` - SQL result rows as `HashMaps` (column name → value)
51    /// * `plan` - Window execution plan (for metadata like aliases)
52    ///
53    /// # Errors
54    ///
55    /// Currently infallible; reserved for future extension (e.g., type coercion failures).
56    ///
57    /// # Returns
58    ///
59    /// GraphQL-compatible JSON array of objects
60    ///
61    /// # Example
62    ///
63    /// ```no_run
64    /// // Requires: a WindowExecutionPlan built from compiled schema metadata.
65    /// // See: tests/integration/ for runnable examples.
66    /// use std::collections::HashMap;
67    /// use serde_json::json;
68    /// # use fraiseql_core::runtime::WindowProjector;
69    ///
70    /// let mut row = HashMap::new();
71    /// row.insert("revenue".to_string(), json!(100.00));
72    /// row.insert("category".to_string(), json!("Electronics"));
73    /// row.insert("rank".to_string(), json!(1));
74    /// let rows = vec![row];
75    /// // let result = WindowProjector::project(rows, &plan)?;
76    /// // result: [{"revenue": 100.00, "category": "Electronics", "rank": 1}]
77    /// ```
78    pub fn project(
79        rows: Vec<HashMap<String, Value>>,
80        _plan: &WindowExecutionPlan,
81    ) -> Result<Value> {
82        // Simple projection: convert each row HashMap to JSON object
83        // Future enhancements could include:
84        // - Type coercion (ensure numbers are numbers, not strings)
85        // - Null handling
86        // - Alias mapping (SQL alias → GraphQL field name)
87        // - Decimal precision handling
88
89        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    /// Wrap projected results in a GraphQL data envelope.
104    ///
105    /// # Arguments
106    ///
107    /// * `projected` - The projected JSON value (array of objects)
108    /// * `query_name` - The GraphQL field name (e.g., "`sales_window`")
109    ///
110    /// # Returns
111    ///
112    /// Complete GraphQL response structure
113    ///
114    /// # Example
115    ///
116    /// ```rust
117    /// # use fraiseql_core::runtime::WindowProjector;
118    /// # use serde_json::json;
119    /// let projected = json!([{"rank": 1}, {"rank": 2}]);
120    /// let response = WindowProjector::wrap_in_data_envelope(projected, "sales_window");
121    /// // { "data": { "sales_window": [{"rank": 1}, {"rank": 2}] } }
122    /// assert!(response.get("data").is_some());
123    /// ```
124    #[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}