use crate::{ScalarFrameBound, ScalarWindowSpec};
use uqa_sql::ir::ScalarExpr;
use uqa_sql::plan::{QueryBlockPlan, QueryPlan};
use super::{quote_ident, Deparser, SQLError, Scope};
#[derive(Clone)]
pub(super) struct RenderedWindow {
name: Option<String>,
specification: String,
}
impl Deparser<'_> {
pub(super) fn prepare_windows(
&self,
block: &QueryBlockPlan,
scope: &mut Scope,
) -> Result<(), SQLError> {
scope.windows.clear();
for (position, definition) in block.windows.iter().enumerate() {
let own = self.window(&definition.spec, scope, &block.subqueries)?;
let specification = if let Some(inherited) = definition.inherited {
let name = block
.windows
.get(inherited)
.filter(|_| inherited < position)
.and_then(|parent| parent.name.as_deref())
.ok_or_else(|| {
SQLError::Internal(
"stored window inheritance has no preceding named definition".into(),
)
})?;
if own.is_empty() {
quote_ident(name)
} else {
format!("{} {own}", quote_ident(name))
}
} else {
own
};
scope.windows.push(RenderedWindow {
name: definition.name.clone(),
specification,
});
}
Ok(())
}
pub(super) fn window_clause(&self, rendered: &mut String, scope: &Scope) {
let declarations = scope
.windows
.iter()
.filter_map(|definition| {
definition
.name
.as_ref()
.map(|name| format!("{} AS ({})", quote_ident(name), definition.specification))
})
.collect::<Vec<_>>();
if !declarations.is_empty() {
self.clause(
rendered,
" WINDOW ",
&declarations.join(", "),
scope.indent,
);
}
}
pub(super) fn window_call(
&self,
name: &str,
args: &[ScalarExpr],
(filter, spec): (Option<&ScalarExpr>, &ScalarWindowSpec),
scope: &Scope,
subqueries: &[QueryPlan],
) -> Result<String, SQLError> {
let filter = filter
.map(|filter| self.expression(filter, scope, subqueries))
.transpose()?
.map(|filter| format!(" FILTER (WHERE {filter})"))
.unwrap_or_default();
let window = if let Some(slot) = spec.definition {
let definition = scope.windows.get(slot).ok_or_else(|| {
SQLError::Internal("stored window call has no query-local definition".into())
})?;
definition.name.as_ref().map_or_else(
|| format!("({})", definition.specification),
|name| quote_ident(name),
)
} else {
format!("({})", self.window(spec, scope, subqueries)?)
};
Ok(format!(
"{}{filter} OVER {window}",
self.function(name, None, args, scope, subqueries)?,
))
}
fn window(
&self,
spec: &ScalarWindowSpec,
scope: &Scope,
subqueries: &[QueryPlan],
) -> Result<String, SQLError> {
let mut parts = Vec::new();
if !spec.partition_by.is_empty() {
parts.push(format!(
"PARTITION BY {}",
self.expressions(&spec.partition_by, scope, subqueries)?
));
}
if !spec.order_by.is_empty() {
let order = spec
.order_by
.iter()
.map(|order| {
self.order_expression(
&order.expr,
order.descending,
order.nulls,
scope,
subqueries,
)
})
.collect::<Result<Vec<_>, _>>()?;
parts.push(format!("ORDER BY {}", order.join(", ")));
}
if let Some(frame) = &spec.frame {
parts.push(uqa_sql::render::frame_clause_sql(
frame.mode,
&self.frame_bound(&frame.start, scope, subqueries)?,
&self.frame_bound(&frame.end, scope, subqueries)?,
frame.between,
frame.exclusion,
));
}
Ok(parts.join(" "))
}
fn frame_bound(
&self,
bound: &ScalarFrameBound,
scope: &Scope,
subqueries: &[QueryPlan],
) -> Result<String, SQLError> {
Ok(match bound {
ScalarFrameBound::UnboundedPreceding => "UNBOUNDED PRECEDING".into(),
ScalarFrameBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".into(),
ScalarFrameBound::CurrentRow => "CURRENT ROW".into(),
ScalarFrameBound::Preceding(value) => {
format!("{} PRECEDING", self.expression(value, scope, subqueries)?)
}
ScalarFrameBound::Following(value) => {
format!("{} FOLLOWING", self.expression(value, scope, subqueries)?)
}
})
}
}