1use super::{expr_list, ident, order_by_sql, render_expr};
10use crate::ast::{FrameBound, FrameMode, WindowDefinition, WindowReferenceKind, WindowSpec};
11use crate::SQLError;
12
13pub(super) fn window_sql(
14 spec: &WindowSpec,
15 windows: &[WindowDefinition],
16) -> Result<String, SQLError> {
17 if let Some(slot) = spec.definition.filter(|_| !windows.is_empty()) {
18 let definition = windows.get(slot).ok_or_else(|| {
19 SQLError::Internal("window call has no query-local definition".into())
20 })?;
21 return definition
22 .name
23 .as_ref()
24 .map_or_else(|| definition_sql(slot, windows), |name| Ok(ident(name)));
25 }
26 if let Some(reference) = &spec.reference {
27 if reference.kind == WindowReferenceKind::Direct
28 && spec.partition_by.is_empty()
29 && spec.order_by.is_empty()
30 && spec.frame.is_none()
31 {
32 return Ok(ident(&reference.name));
33 }
34 }
35 specification_sql(
36 spec,
37 spec.reference
38 .as_ref()
39 .map(|reference| reference.name.as_str()),
40 )
41}
42
43pub(super) fn window_clause_sql(windows: &[WindowDefinition]) -> Result<String, SQLError> {
44 let declarations = windows
45 .iter()
46 .enumerate()
47 .filter_map(|(slot, definition)| {
48 definition.name.as_ref().map(|name| {
49 Ok(format!(
50 "{} AS {}",
51 ident(name),
52 definition_sql(slot, windows)?
53 ))
54 })
55 })
56 .collect::<Result<Vec<_>, SQLError>>()?;
57 Ok(if declarations.is_empty() {
58 String::new()
59 } else {
60 format!(" WINDOW {}", declarations.join(", "))
61 })
62}
63
64fn definition_sql(slot: usize, windows: &[WindowDefinition]) -> Result<String, SQLError> {
65 let definition = &windows[slot];
66 let inherited = definition
67 .inherited
68 .map(|parent| {
69 windows
70 .get(parent)
71 .filter(|_| parent < slot)
72 .and_then(|window| window.name.as_deref())
73 .ok_or_else(|| {
74 SQLError::Internal("window inherits no preceding named definition".into())
75 })
76 })
77 .transpose()?;
78 specification_sql(&definition.spec, inherited)
79}
80
81fn specification_sql(spec: &WindowSpec, reference: Option<&str>) -> Result<String, SQLError> {
82 let mut parts = Vec::new();
83 if let Some(reference) = reference {
84 parts.push(ident(reference));
85 }
86 if !spec.partition_by.is_empty() {
87 parts.push(format!("PARTITION BY {}", expr_list(&spec.partition_by)?));
88 }
89 if !spec.order_by.is_empty() {
90 parts.push(format!("ORDER BY {}", order_by_sql(&spec.order_by)?));
91 }
92 if let Some(frame) = &spec.frame {
93 parts.push(frame_clause_sql(
94 frame.mode,
95 &frame_bound_sql(&frame.start)?,
96 &frame_bound_sql(&frame.end)?,
97 frame.between,
98 frame.exclusion,
99 ));
100 }
101 Ok(format!("({})", parts.join(" ")))
102}
103
104#[must_use]
106pub fn frame_clause_sql(
107 mode: FrameMode,
108 start: &str,
109 end: &str,
110 between: bool,
111 exclusion: crate::ast::FrameExclusion,
112) -> String {
113 let mode = match mode {
114 FrameMode::Rows => "ROWS",
115 FrameMode::Range => "RANGE",
116 FrameMode::Groups => "GROUPS",
117 };
118 let mut clause = if between {
119 format!("{mode} BETWEEN {start} AND {end}")
120 } else {
121 format!("{mode} {start}")
122 };
123 if let Some(exclusion) = exclusion.sql() {
124 clause.push(' ');
125 clause.push_str(exclusion);
126 }
127 clause
128}
129
130fn frame_bound_sql(bound: &FrameBound) -> Result<String, SQLError> {
131 Ok(match bound {
132 FrameBound::UnboundedPreceding => "UNBOUNDED PRECEDING".into(),
133 FrameBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".into(),
134 FrameBound::CurrentRow => "CURRENT ROW".into(),
135 FrameBound::Preceding(expression) => format!("{} PRECEDING", render_expr(expression)?),
136 FrameBound::Following(expression) => format!("{} FOLLOWING", render_expr(expression)?),
137 })
138}