fraiseql_core/compiler/window_functions/
codegen.rs1use super::{
2 FactTableMetadata, FraiseQLError, OrderByClause, PartitionByColumn, Result, SelectColumn,
3 WindowExecutionPlan, WindowFunction, WindowFunctionRequest, WindowFunctionSpec,
4 WindowFunctionType, WindowOrderBy, WindowRequest, WindowSelectColumn,
5};
6
7pub struct WindowPlanner;
25
26impl WindowPlanner {
27 pub fn plan(
41 request: WindowRequest,
42 metadata: &FactTableMetadata,
43 ) -> Result<WindowExecutionPlan> {
44 let select = Self::convert_select_columns(&request.select, metadata)?;
46
47 let windows = Self::convert_window_functions(&request.windows, metadata)?;
49
50 let order_by = Self::convert_order_by(&request.order_by, metadata)?;
52
53 Ok(WindowExecutionPlan {
54 table: request.table_name,
55 select,
56 windows,
57 where_clause: request.where_clause,
58 order_by,
59 limit: request.limit,
60 offset: request.offset,
61 })
62 }
63
64 fn convert_select_columns(
66 columns: &[WindowSelectColumn],
67 metadata: &FactTableMetadata,
68 ) -> Result<Vec<SelectColumn>> {
69 columns
70 .iter()
71 .map(|col| Self::convert_single_select_column(col, metadata))
72 .collect()
73 }
74
75 fn convert_single_select_column(
76 column: &WindowSelectColumn,
77 metadata: &FactTableMetadata,
78 ) -> Result<SelectColumn> {
79 match column {
80 WindowSelectColumn::Measure { name, alias } => {
81 if !metadata.measures.iter().any(|m| m.name == *name) {
83 return Err(FraiseQLError::Validation {
84 message: format!(
85 "Measure '{}' not found in fact table '{}'",
86 name, metadata.table_name
87 ),
88 path: None,
89 });
90 }
91 Ok(SelectColumn {
93 expression: name.clone(),
94 alias: alias.clone(),
95 })
96 },
97 WindowSelectColumn::Dimension { path, alias } => {
98 let expression = format!("{}->>'{}'", metadata.dimensions.name, path);
100 Ok(SelectColumn {
101 expression,
102 alias: alias.clone(),
103 })
104 },
105 WindowSelectColumn::Filter { name, alias } => {
106 if !metadata.denormalized_filters.iter().any(|f| f.name == *name) {
108 return Err(FraiseQLError::Validation {
109 message: format!(
110 "Filter column '{}' not found in fact table '{}'",
111 name, metadata.table_name
112 ),
113 path: None,
114 });
115 }
116 Ok(SelectColumn {
118 expression: name.clone(),
119 alias: alias.clone(),
120 })
121 },
122 }
123 }
124
125 fn convert_window_functions(
127 windows: &[WindowFunctionRequest],
128 metadata: &FactTableMetadata,
129 ) -> Result<Vec<WindowFunction>> {
130 windows
131 .iter()
132 .map(|w| Self::convert_single_window_function(w, metadata))
133 .collect()
134 }
135
136 fn convert_single_window_function(
137 request: &WindowFunctionRequest,
138 metadata: &FactTableMetadata,
139 ) -> Result<WindowFunction> {
140 let function = Self::convert_function_spec(&request.function, metadata)?;
142
143 let partition_by = request
145 .partition_by
146 .iter()
147 .map(|p| Self::convert_partition_by(p, metadata))
148 .collect::<Result<Vec<_>>>()?;
149
150 let order_by = request
152 .order_by
153 .iter()
154 .map(|o| Self::convert_window_order_by(o, metadata))
155 .collect::<Result<Vec<_>>>()?;
156
157 Ok(WindowFunction {
158 function,
159 alias: request.alias.clone(),
160 partition_by,
161 order_by,
162 frame: request.frame.clone(),
163 })
164 }
165
166 fn convert_function_spec(
168 spec: &WindowFunctionSpec,
169 metadata: &FactTableMetadata,
170 ) -> Result<WindowFunctionType> {
171 match spec {
172 WindowFunctionSpec::RowNumber => Ok(WindowFunctionType::RowNumber),
174 WindowFunctionSpec::Rank => Ok(WindowFunctionType::Rank),
175 WindowFunctionSpec::DenseRank => Ok(WindowFunctionType::DenseRank),
176 WindowFunctionSpec::Ntile { n } => Ok(WindowFunctionType::Ntile { n: *n }),
177 WindowFunctionSpec::PercentRank => Ok(WindowFunctionType::PercentRank),
178 WindowFunctionSpec::CumeDist => Ok(WindowFunctionType::CumeDist),
179
180 WindowFunctionSpec::Lag {
182 field,
183 offset,
184 default,
185 } => {
186 let sql_field = Self::resolve_field_to_sql(field, metadata)?;
187 Ok(WindowFunctionType::Lag {
188 field: sql_field,
189 offset: *offset,
190 default: default.clone(),
191 })
192 },
193 WindowFunctionSpec::Lead {
194 field,
195 offset,
196 default,
197 } => {
198 let sql_field = Self::resolve_field_to_sql(field, metadata)?;
199 Ok(WindowFunctionType::Lead {
200 field: sql_field,
201 offset: *offset,
202 default: default.clone(),
203 })
204 },
205 WindowFunctionSpec::FirstValue { field } => {
206 let sql_field = Self::resolve_field_to_sql(field, metadata)?;
207 Ok(WindowFunctionType::FirstValue { field: sql_field })
208 },
209 WindowFunctionSpec::LastValue { field } => {
210 let sql_field = Self::resolve_field_to_sql(field, metadata)?;
211 Ok(WindowFunctionType::LastValue { field: sql_field })
212 },
213 WindowFunctionSpec::NthValue { field, n } => {
214 let sql_field = Self::resolve_field_to_sql(field, metadata)?;
215 Ok(WindowFunctionType::NthValue {
216 field: sql_field,
217 n: *n,
218 })
219 },
220
221 WindowFunctionSpec::RunningSum { measure } => {
223 Self::validate_measure(measure, metadata)?;
224 Ok(WindowFunctionType::Sum {
225 field: measure.clone(),
226 })
227 },
228 WindowFunctionSpec::RunningAvg { measure } => {
229 Self::validate_measure(measure, metadata)?;
230 Ok(WindowFunctionType::Avg {
231 field: measure.clone(),
232 })
233 },
234 WindowFunctionSpec::RunningCount => Ok(WindowFunctionType::Count { field: None }),
235 WindowFunctionSpec::RunningCountField { field } => {
236 let sql_field = Self::resolve_field_to_sql(field, metadata)?;
237 Ok(WindowFunctionType::Count {
238 field: Some(sql_field),
239 })
240 },
241 WindowFunctionSpec::RunningMin { measure } => {
242 Self::validate_measure(measure, metadata)?;
243 Ok(WindowFunctionType::Min {
244 field: measure.clone(),
245 })
246 },
247 WindowFunctionSpec::RunningMax { measure } => {
248 Self::validate_measure(measure, metadata)?;
249 Ok(WindowFunctionType::Max {
250 field: measure.clone(),
251 })
252 },
253 WindowFunctionSpec::RunningStddev { measure } => {
254 Self::validate_measure(measure, metadata)?;
255 Ok(WindowFunctionType::Stddev {
256 field: measure.clone(),
257 })
258 },
259 WindowFunctionSpec::RunningVariance { measure } => {
260 Self::validate_measure(measure, metadata)?;
261 Ok(WindowFunctionType::Variance {
262 field: measure.clone(),
263 })
264 },
265 }
266 }
267
268 fn convert_partition_by(
270 partition: &PartitionByColumn,
271 metadata: &FactTableMetadata,
272 ) -> Result<String> {
273 match partition {
274 PartitionByColumn::Dimension { path } => {
275 Ok(format!("{}->>'{}'", metadata.dimensions.name, path))
276 },
277 PartitionByColumn::Filter { name } => {
278 if !metadata.denormalized_filters.iter().any(|f| f.name == *name) {
279 return Err(FraiseQLError::Validation {
280 message: format!(
281 "Filter column '{}' not found in fact table '{}'",
282 name, metadata.table_name
283 ),
284 path: None,
285 });
286 }
287 Ok(name.clone())
288 },
289 PartitionByColumn::Measure { name } => {
290 Self::validate_measure(name, metadata)?;
291 Ok(name.clone())
292 },
293 }
294 }
295
296 fn convert_window_order_by(
298 order: &WindowOrderBy,
299 metadata: &FactTableMetadata,
300 ) -> Result<OrderByClause> {
301 let field = Self::resolve_field_to_sql(&order.field, metadata)?;
302 Ok(OrderByClause {
303 field,
304 direction: order.direction,
305 })
306 }
307
308 fn convert_order_by(
310 orders: &[WindowOrderBy],
311 metadata: &FactTableMetadata,
312 ) -> Result<Vec<OrderByClause>> {
313 orders.iter().map(|o| Self::convert_window_order_by(o, metadata)).collect()
314 }
315
316 fn resolve_field_to_sql(field: &str, metadata: &FactTableMetadata) -> Result<String> {
325 if metadata.measures.iter().any(|m| m.name == field) {
327 return Ok(field.to_string());
328 }
329
330 if metadata.denormalized_filters.iter().any(|f| f.name == field) {
332 return Ok(field.to_string());
333 }
334
335 Self::validate_field_identifier(field)?;
339
340 Ok(format!("{}->>'{}'", metadata.dimensions.name, field))
342 }
343
344 fn validate_field_identifier(field: &str) -> Result<()> {
349 let mut chars = field.chars();
350 let first_ok = chars.next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
351 let rest_ok = chars.all(|c| c.is_ascii_alphanumeric() || c == '_');
352 if first_ok && rest_ok {
353 Ok(())
354 } else {
355 Err(crate::error::FraiseQLError::Validation {
356 message: format!(
357 "window field '{field}' contains invalid characters; \
358 only [_A-Za-z][_0-9A-Za-z]* is allowed"
359 ),
360 path: None,
361 })
362 }
363 }
364
365 fn validate_measure(measure: &str, metadata: &FactTableMetadata) -> Result<()> {
367 if !metadata.measures.iter().any(|m| m.name == *measure) {
368 return Err(FraiseQLError::Validation {
369 message: format!(
370 "Measure '{}' not found in fact table '{}'",
371 measure, metadata.table_name
372 ),
373 path: None,
374 });
375 }
376 Ok(())
377 }
378}