qail-core 3.0.1

QAIL AST Kernel - typed query AST, schema validation, RLS context, and native access policy
//! CTE (Common Table Expression) builder methods.

use crate::ast::{Action, CTEDef, CteCycle, CteMaterialization, CteSearch, Expr, Qail};

impl Qail {
    /// Convert this query into a reusable CTE definition.
    pub fn to_cte(self, name: impl Into<String>) -> CTEDef {
        let cte_name = name.into();
        // A write body's `columns` are its target columns, not its output:
        // the relation it exposes is its RETURNING list, named by PostgreSQL.
        let columns: Vec<String> = if matches!(self.action, Action::Add | Action::Set | Action::Del)
        {
            Vec::new()
        } else {
            self.columns
                .iter()
                .filter_map(|c| match c {
                    Expr::Named(n) => Some(n.clone()),
                    Expr::Aliased { alias, .. } => Some(alias.clone()),
                    _ => None,
                })
                .collect()
        };

        CTEDef {
            name: cte_name,
            recursive: false,
            columns,
            base_query: Box::new(self),
            recursive_query: None,
            source_table: None,
            materialization: None,
            search: None,
            cycle: None,
        }
    }

    /// Add an inline CTE from another query.
    pub fn with(self, name: impl Into<String>, query: Qail) -> Self {
        self.with_cte(query.to_cte(name))
    }

    /// Mark the last CTE as recursive and attach the recursive query.
    pub fn recursive(mut self, recursive_part: Qail) -> Self {
        if let Some(cte) = self.ctes.last_mut() {
            cte.recursive = true;
            cte.recursive_query = Some(Box::new(recursive_part));
        }
        self
    }

    /// Set the source table of the last CTE.
    pub fn from_cte(mut self, cte_name: impl Into<String>) -> Self {
        if let Some(cte) = self.ctes.last_mut() {
            cte.source_table = Some(cte_name.into());
        }
        self
    }

    /// Replace the column list with named columns (for selecting from a CTE).
    pub fn select_from_cte(mut self, columns: &[&str]) -> Self {
        self.columns = columns.iter().map(|c| Expr::Named(c.to_string())).collect();
        self
    }

    /// Append a pre-built CTE definition.
    pub fn with_cte(mut self, cte: CTEDef) -> Self {
        self.ctes.push(cte);
        self
    }

    /// Mark the last CTE `AS MATERIALIZED`.
    pub fn cte_materialized(mut self) -> Self {
        if let Some(cte) = self.ctes.last_mut() {
            cte.materialization = Some(CteMaterialization::Materialized);
        }
        self
    }

    /// Mark the last CTE `AS NOT MATERIALIZED`.
    pub fn cte_not_materialized(mut self) -> Self {
        if let Some(cte) = self.ctes.last_mut() {
            cte.materialization = Some(CteMaterialization::NotMaterialized);
        }
        self
    }

    /// Add `SEARCH ... FIRST BY ... SET ...` to the last CTE, which must be recursive.
    pub fn cte_search(mut self, search: CteSearch) -> Self {
        if let Some(cte) = self.ctes.last_mut() {
            cte.search = Some(search);
        }
        self
    }

    /// Add `CYCLE ... SET ... USING ...` to the last CTE, which must be recursive.
    pub fn cte_cycle(mut self, cycle: CteCycle) -> Self {
        if let Some(cte) = self.ctes.last_mut() {
            cte.cycle = Some(cycle);
        }
        self
    }
}