use crate::{
ast::CteMaterialization,
plan::{CtePlan, QueryPlan},
semantics::{
cte_definition_references, cte_references_own_name, ordered_plan_ctes,
reachable_plan_cte_names, single_reference_plan_cte_names,
volatility::{query_contains_volatile_function, VolatilityCatalog},
},
SQLError,
};
use std::collections::{BTreeMap, BTreeSet};
pub struct ScheduledCte<'a> {
pub plan: &'a CtePlan,
pub deferred: bool,
}
pub fn schedule_plan_ctes<'a>(
catalog: &dyn VolatilityCatalog,
plan: &'a QueryPlan,
) -> Result<Vec<ScheduledCte<'a>>, SQLError> {
let ordered = ordered_plan_ctes(plan)?;
let reachable = reachable_plan_cte_names(plan);
let single_reference = single_reference_plan_cte_names(plan);
Ok(ordered
.into_iter()
.filter(|cte| reachable.contains(&cte.name))
.map(|cte| ScheduledCte {
plan: cte,
deferred: !cte.body.modifies_data()
&& !cte_references_own_name(cte)
&& match cte.materialization {
CteMaterialization::Default => single_reference.contains(&cte.name),
CteMaterialization::Materialized => false,
CteMaterialization::NotMaterialized => true,
}
&& matches!(
cte.body
.query()
.map_or(Ok(true), |query| query_contains_volatile_function(
catalog, query
)),
Ok(false)
),
})
.collect())
}
pub struct StatementCteOrder<'a> {
pub primary: Vec<&'a CtePlan>,
pub postponed: Vec<&'a CtePlan>,
}
pub fn order_statement_ctes<'a>(
ctes: &'a [CtePlan],
scheduled: Vec<&'a CtePlan>,
primary_references: &BTreeSet<String>,
) -> StatementCteOrder<'a> {
if !scheduled.iter().any(|cte| cte.body.modifies_data()) {
return StatementCteOrder {
primary: scheduled,
postponed: Vec::new(),
};
}
let references = ctes
.iter()
.enumerate()
.map(|(index, cte)| (cte.name.as_str(), cte_definition_references(ctes, index)))
.collect::<BTreeMap<_, _>>();
let read_through = |roots: Vec<String>| {
let mut reached = BTreeSet::new();
let mut pending = roots;
while let Some(name) = pending.pop() {
if let Some(names) = references.get(name.as_str()) {
pending.extend(
names
.iter()
.filter(|name| !reached.contains(*name))
.cloned(),
);
}
reached.insert(name);
}
reached
};
let read = read_through(primary_references.iter().cloned().collect());
let mut postponed_names = BTreeSet::new();
let mut postponed = Vec::new();
for item in ctes
.iter()
.rev()
.filter(|cte| cte.body.modifies_data() && !read.contains(&cte.name))
{
let needed = read_through(vec![item.name.clone()]);
for cte in &scheduled {
if needed.contains(&cte.name)
&& !read.contains(&cte.name)
&& postponed_names.insert(cte.name.clone())
{
postponed.push(*cte);
}
}
}
let primary = scheduled
.into_iter()
.filter(|cte| !postponed_names.contains(&cte.name))
.collect();
StatementCteOrder { primary, postponed }
}
#[cfg(test)]
mod tests;