Skip to main content

rupg_analyze/
cte.rs

1//! `parse_cte.c`: the queries of `WITH` and `WITH RECURSIVE`. The queries of `WITH RECURSIVE` get an order with no forward reference, and each recursive query must have the form `non-recursive-term UNION [ALL] recursive-term` with one reference to itself in the recursive term.
2
3use rupg_common::{Error, Result, SqlState};
4use rupg_sql::nodes::{
5    CTEMaterialize, CommonTableExpr, JoinType, List, Node, RangeVar, SelectStmt, SetOperation,
6    WithClause,
7};
8use rupg_types::oid;
9
10use crate::Analyzer;
11use crate::coerce::AtOpt;
12use crate::expr::Expr;
13use crate::from::{Column, Item, Relation};
14use crate::searchcycle::{self, CycleClause, SearchClause};
15use crate::select::Query;
16use crate::sort::{list_location, raw_location};
17use crate::typename::place;
18use crate::types;
19use crate::walk::{self, Walker};
20
21/// A query of `WITH` after the analysis, as a `CommonTableExpr` of the list `cteList` of a query.
22#[derive(Clone, Debug, PartialEq)]
23pub struct Cte {
24    /// `ctename`.
25    pub name: String,
26    /// The names of the columns after the name of the query, as `aliascolnames`.
27    pub aliases: Vec<String>,
28    /// The columns of the query, with the names of [`Cte::aliases`] first, then the columns of [`Cte::search`] and [`Cte::cycle`].
29    pub columns: Vec<Column>,
30    /// The query as the statement gives it. With `SEARCH` or `CYCLE`, each reference reads the query after the rewrite of the clauses.
31    pub query: Box<Query>,
32    /// `cterecursive`: the query reads itself.
33    pub recursive: bool,
34    /// `Some(true)` for `MATERIALIZED`, `Some(false)` for `NOT MATERIALIZED`, `None` when the query gives neither.
35    pub materialized: Option<bool>,
36    /// `search_clause`.
37    pub search: Option<SearchClause>,
38    /// `cycle_clause`.
39    pub cycle: Option<CycleClause>,
40    /// For an `INSERT`, an `UPDATE`, a `DELETE` or a `MERGE`, the number of the statement among the statements of `WITH` of the statement that change data, in the order of their analysis. Then [`Cte::query`] is the statement, and a reference reads the rows of its `RETURNING`.
41    pub write: Option<usize>,
42}
43
44/// The query of `WITH` that a relation of `FROM` reads, as a `RangeTblEntry` of the kind `RTE_CTE`.
45#[derive(Clone, Debug, PartialEq)]
46pub struct CteRef {
47    /// `ctename`.
48    pub name: String,
49    /// For the reference of a recursive query to itself, as `self_reference`, the number of the work table that the recursive `UNION` fills at each step. The relation then has no subquery. Another reference has a copy of the query of `WITH` as its subquery.
50    pub work: Option<usize>,
51    /// For a reference to a statement that changes data, the number of [`Cte::write`]. The relation then has no subquery, and its rows are the rows of `RETURNING` of the statement.
52    pub write: Option<usize>,
53    /// The table and the column of each column of the reference to a statement that changes data, from the targets of its `RETURNING`.
54    pub origins: Vec<Option<(u32, i16)>>,
55    /// True for a transition table of the trigger that runs, as a `RangeTblEntry` of the kind `RTE_NAMEDTUPLESTORE`. The relation then has no subquery, and its rows are the old or the new rows of the write that fired the trigger.
56    pub transition: bool,
57}
58
59/// A query of `WITH` in the scope of a query, as an item of `p_ctenamespace`.
60#[derive(Clone, Debug)]
61pub(crate) struct CteItem {
62    name: String,
63    /// The place of the name of the query.
64    location: Option<usize>,
65    aliases: Vec<String>,
66    recursive: bool,
67    materialized: Option<bool>,
68    /// False for a query of `WITH` with no `RECURSIVE` before the analysis of its query ends. Then a name cannot find it, but the error of the name tells about it, as `p_future_ctes` does.
69    visible: bool,
70    /// The columns, after the analysis of the query, or after the non-recursive term of a recursive query.
71    columns: Option<Vec<Column>>,
72    /// The query, after its analysis, and after the rewrite of `SEARCH` and `CYCLE`.
73    query: Option<Box<Query>>,
74    /// The query before the rewrite of `SEARCH` and `CYCLE`.
75    written: Option<Box<Query>>,
76    /// The number of the work table of a recursive query.
77    work: Option<usize>,
78    search: Option<SearchClause>,
79    cycle: Option<CycleClause>,
80    /// The number of a statement that changes data, as [`Cte::write`].
81    write: Option<usize>,
82    /// True when the statement that changes data has `RETURNING`.
83    returning: bool,
84    /// The origins of the columns of `RETURNING`, as [`CteRef::origins`].
85    origins: Vec<Option<(u32, i16)>>,
86}
87
88impl CteItem {
89    fn new(cte: &CommonTableExpr, recursive: bool, visible: bool) -> CteItem {
90        let materialized = match cte.ctematerialized {
91            CTEMaterialize::CTEMaterializeAlways => Some(true),
92            CTEMaterialize::CTEMaterializeNever => Some(false),
93            _ => None,
94        };
95        CteItem {
96            name: cte_name(cte).to_string(),
97            location: place(cte.location),
98            aliases: strings(&cte.aliascolnames),
99            recursive,
100            materialized,
101            visible,
102            columns: None,
103            query: None,
104            written: None,
105            work: None,
106            search: None,
107            cycle: None,
108            write: None,
109            returning: false,
110            origins: Vec::new(),
111        }
112    }
113
114    /// The columns of a reference to the query: the columns of the query, then the columns of `SEARCH` and `CYCLE`.
115    fn reference_columns(&self) -> Option<Vec<Column>> {
116        let mut columns = self.columns.clone()?;
117        columns.extend(searchcycle::extra_columns(self.search.as_ref(), self.cycle.as_ref()));
118        Some(columns)
119    }
120
121    /// The query of `WITH` of the query tree.
122    fn into_cte(self) -> Result<Cte> {
123        let (Some(columns), Some(query)) = (self.reference_columns(), self.query) else {
124            return Err(Error::internal("a query of WITH with no analysis"));
125        };
126        Ok(Cte {
127            name: self.name,
128            aliases: self.aliases,
129            columns,
130            query: self.written.unwrap_or(query),
131            recursive: self.recursive,
132            materialized: self.materialized,
133            search: self.search,
134            cycle: self.cycle,
135            write: self.write,
136        })
137    }
138
139    /// `analyzeCTETargetList`: the columns of the query from the name, the type and the typmod of each column of its result. The names of the alias come first. A column of type `unknown` of a recursive query becomes `text`, because the recursive term must know the type.
140    fn columns(&self, shown: Vec<(String, u32, i32)>) -> Result<Vec<Column>> {
141        let mut columns = Vec::with_capacity(shown.len());
142        for (i, (name, ty, typmod)) in shown.into_iter().enumerate() {
143            let name = self.aliases.get(i).cloned().unwrap_or(name);
144            let (ty, typmod) =
145                if self.recursive && ty == oid::UNKNOWN { (oid::TEXT, -1) } else { (ty, typmod) };
146            columns.push(Column { name, ty, typmod, not_null: false });
147        }
148        if columns.len() < self.aliases.len() {
149            return Err(Error::new(
150                SqlState::INVALID_COLUMN_REFERENCE,
151                format!(
152                    "WITH query \"{}\" has {} columns available but {} columns specified",
153                    self.name,
154                    columns.len(),
155                    self.aliases.len()
156                ),
157            )
158            .at_opt(self.location));
159        }
160        Ok(columns)
161    }
162}
163
164/// The name of a query of `WITH`.
165fn cte_name(cte: &CommonTableExpr) -> &str {
166    cte.ctename.as_deref().unwrap_or_default()
167}
168
169/// The strings of a list of `String` nodes.
170fn strings(list: &List) -> Vec<String> {
171    list.iter()
172        .filter_map(|n| match n {
173            Some(Node::String(s)) => Some(s.to_string()),
174            _ => None,
175        })
176        .collect()
177}
178
179/// The queries of a `WITH` clause.
180fn ctes(with: &WithClause) -> Vec<&CommonTableExpr> {
181    with.ctes
182        .iter()
183        .filter_map(|n| match n {
184            Some(Node::CommonTableExpr(cte)) => Some(&**cte),
185            _ => None,
186        })
187        .collect()
188}
189
190/// The name of a relation of `FROM` that can be a query of `WITH`: a name with no schema.
191fn unqualified(rv: &RangeVar) -> Option<&str> {
192    match rv.schemaname {
193        Some(_) => None,
194        None => Some(rv.relname.as_deref().unwrap_or_default()),
195    }
196}
197
198/// The names of the queries of the `WITH` clauses inside the query that the walk reads, which hide the queries of the outer `WITH`, as `innerwiths`.
199#[derive(Default)]
200struct Inner(Vec<Vec<String>>);
201
202impl Inner {
203    fn hides(&self, name: &str) -> bool {
204        self.0.iter().flatten().any(|n| n == name)
205    }
206}
207
208/// `makeDependencyGraphWalker`: the queries of the same `WITH RECURSIVE` that a query reads, and if it reads itself.
209struct Deps<'a> {
210    names: &'a [&'a str],
211    current: usize,
212    inner: Inner,
213    depends: Vec<usize>,
214    recursive: bool,
215}
216
217impl Deps<'_> {
218    /// `WalkInnerWith`: a `SELECT` with a `WITH` clause. With `RECURSIVE`, all its queries see all its names. Else each query sees the names before it, and the `SELECT` sees all of them.
219    fn inner_with(&mut self, s: &SelectStmt, with: &WithClause) -> bool {
220        let list = ctes(with);
221        if with.recursive {
222            self.inner.0.push(list.iter().map(|c| cte_name(c).to_string()).collect());
223            for cte in &list {
224                walk::one(self, cte.ctequery.as_ref());
225            }
226        } else {
227            self.inner.0.push(Vec::new());
228            for cte in &list {
229                walk::one(self, cte.ctequery.as_ref());
230                if let Some(level) = self.inner.0.last_mut() {
231                    level.push(cte_name(cte).to_string());
232                }
233            }
234        }
235        walk::select(self, s);
236        self.inner.0.pop();
237        false
238    }
239}
240
241impl Walker for Deps<'_> {
242    fn node(&mut self, node: &Node) -> bool {
243        match node {
244            Node::RangeVar(rv) => {
245                if let Some(name) = unqualified(rv)
246                    && !self.inner.hides(name)
247                    && let Some(i) = self.names.iter().position(|n| *n == name)
248                {
249                    if i == self.current {
250                        self.recursive = true;
251                    } else if !self.depends.contains(&i) {
252                        self.depends.push(i);
253                    }
254                }
255                false
256            }
257            Node::SelectStmt(s) => self.select(s),
258            Node::WithClause(_) => false,
259            _ => walk::walk(self, node),
260        }
261    }
262
263    fn select(&mut self, s: &SelectStmt) -> bool {
264        match s.withClause.as_deref() {
265            Some(with) => self.inner_with(s, with),
266            None => walk::select(self, s),
267        }
268    }
269}
270
271/// `RecursionContext`: the place of the walk in the query, where a reference to the recursive query can be an error.
272#[derive(Clone, Copy, PartialEq, Eq)]
273enum Context {
274    Ok,
275    NonRecursiveTerm,
276    SubLink,
277    OuterJoin,
278    Intersect,
279    Except,
280}
281
282impl Context {
283    /// `recursion_errormsgs`.
284    fn message(self, name: &str) -> String {
285        let place = match self {
286            Context::Ok => "",
287            Context::NonRecursiveTerm => "its non-recursive term",
288            Context::SubLink => "a subquery",
289            Context::OuterJoin => "an outer join",
290            Context::Intersect => "INTERSECT",
291            Context::Except => "EXCEPT",
292        };
293        format!("recursive reference to query \"{name}\" must not appear within {place}")
294    }
295}
296
297/// `checkWellFormedRecursionWalker`: the references of a recursive query to itself, which must be one, in the recursive term, not in a subquery or on the null side of an outer join.
298struct Recursion<'a> {
299    name: &'a str,
300    inner: Inner,
301    count: usize,
302    context: Context,
303    error: Option<Error>,
304}
305
306impl Recursion<'_> {
307    fn new(name: &str, context: Context) -> Recursion<'_> {
308        Recursion { name, inner: Inner::default(), count: 0, context, error: None }
309    }
310
311    /// Keeps the error and stops the walk.
312    fn fail(&mut self, error: Error) -> bool {
313        self.error = Some(error);
314        true
315    }
316
317    /// `checkWellFormedSelectStmt`: in the recursive term, the reference cannot be on the right side of `EXCEPT`, or in `INTERSECT ALL` or `EXCEPT ALL`.
318    fn select_stmt(&mut self, s: &SelectStmt) -> bool {
319        let save = self.context;
320        if save != Context::Ok {
321            return walk::select(self, s);
322        }
323        let (left, right) = match s.op {
324            SetOperation::SETOP_INTERSECT if s.all => (Context::Intersect, Context::Intersect),
325            SetOperation::SETOP_INTERSECT => (save, save),
326            SetOperation::SETOP_EXCEPT if s.all => (Context::Except, Context::Except),
327            SetOperation::SETOP_EXCEPT => (save, Context::Except),
328            _ => return walk::select(self, s),
329        };
330        self.context = left;
331        if s.larg.as_deref().is_some_and(|l| self.select(l)) {
332            return true;
333        }
334        self.context = right;
335        if s.rarg.as_deref().is_some_and(|r| self.select(r)) {
336            return true;
337        }
338        self.context = save;
339        walk::list(self, &s.sortClause)
340            || walk::one(self, s.limitOffset.as_ref())
341            || walk::one(self, s.limitCount.as_ref())
342            || walk::list(self, &s.lockingClause)
343    }
344
345    /// A walk of `node` with the context `context`, after which the context is `save` again.
346    fn within(&mut self, context: Context, save: Context, node: Option<&Node>) -> bool {
347        self.context = context;
348        let stop = walk::one(self, node);
349        self.context = save;
350        stop
351    }
352}
353
354impl Walker for Recursion<'_> {
355    fn node(&mut self, node: &Node) -> bool {
356        let save = self.context;
357        let outer = if save == Context::Ok { Context::OuterJoin } else { save };
358        match node {
359            Node::RangeVar(rv) => {
360                let Some(name) = unqualified(rv) else { return false };
361                if self.inner.hides(name) || name != self.name {
362                    return false;
363                }
364                let at = place(rv.location);
365                if self.context != Context::Ok {
366                    let message = self.context.message(self.name);
367                    return self.fail(Error::new(SqlState::INVALID_RECURSION, message).at_opt(at));
368                }
369                self.count += 1;
370                if self.count > 1 {
371                    let message = format!(
372                        "recursive reference to query \"{}\" must not appear more than once",
373                        self.name
374                    );
375                    return self.fail(Error::new(SqlState::INVALID_RECURSION, message).at_opt(at));
376                }
377                false
378            }
379            Node::SelectStmt(s) => self.select(s),
380            Node::WithClause(_) => false,
381            Node::JoinExpr(j) => {
382                let (larg, rarg) = (j.larg.as_ref(), j.rarg.as_ref());
383                let sides = match j.jointype {
384                    JoinType::JOIN_LEFT => walk::one(self, larg) || self.within(outer, save, rarg),
385                    JoinType::JOIN_FULL => {
386                        self.within(outer, save, larg) || self.within(outer, save, rarg)
387                    }
388                    JoinType::JOIN_RIGHT => self.within(outer, save, larg) || walk::one(self, rarg),
389                    _ => walk::one(self, larg) || walk::one(self, rarg),
390                };
391                sides || walk::one(self, j.quals.as_ref())
392            }
393            Node::SubLink(s) => {
394                self.within(Context::SubLink, save, s.subselect.as_ref())
395                    || walk::one(self, s.testexpr.as_ref())
396            }
397            _ => walk::walk(self, node),
398        }
399    }
400
401    fn select(&mut self, s: &SelectStmt) -> bool {
402        let Some(with) = s.withClause.as_deref() else { return self.select_stmt(s) };
403        let list = ctes(with);
404        if with.recursive {
405            self.inner.0.push(list.iter().map(|c| cte_name(c).to_string()).collect());
406            for cte in &list {
407                if walk::one(self, cte.ctequery.as_ref()) {
408                    return true;
409                }
410            }
411        } else {
412            self.inner.0.push(Vec::new());
413            for cte in &list {
414                if walk::one(self, cte.ctequery.as_ref()) {
415                    return true;
416                }
417                if let Some(level) = self.inner.0.last_mut() {
418                    level.push(cte_name(cte).to_string());
419                }
420            }
421        }
422        let stop = self.select_stmt(s);
423        self.inner.0.pop();
424        stop
425    }
426}
427
428/// Runs a walk of the recursion check and gives its error.
429fn check_walk(
430    name: &str,
431    context: Context,
432    f: impl FnOnce(&mut Recursion<'_>) -> bool,
433) -> Result<usize> {
434    let mut walker = Recursion::new(name, context);
435    f(&mut walker);
436    match walker.error {
437        Some(error) => Err(error),
438        None => Ok(walker.count),
439    }
440}
441
442/// `checkWellFormedRecursion` for one recursive query.
443fn check_recursion(cte: &CommonTableExpr) -> Result<()> {
444    let name = cte_name(cte);
445    let Some(Node::SelectStmt(stmt)) = &cte.ctequery else {
446        return Err(Error::new(
447            SqlState::INVALID_RECURSION,
448            format!("recursive query \"{name}\" must not contain data-modifying statements"),
449        )
450        .at_opt(place(cte.location)));
451    };
452    if stmt.op != SetOperation::SETOP_UNION {
453        return Err(Error::new(
454            SqlState::INVALID_RECURSION,
455            format!(
456                "recursive query \"{name}\" does not have the form non-recursive-term UNION [ALL] recursive-term"
457            ),
458        )
459        .at_opt(place(cte.location)));
460    }
461    // The query of a `WITH` clause on the `UNION` must not read the recursive query.
462    if let Some(with) = stmt.withClause.as_deref() {
463        check_walk(name, Context::SubLink, |w| walk::list(w, &with.ctes))?;
464    }
465    let not_implemented = |what: &str, at: Option<usize>| {
466        Err(Error::new(
467            SqlState::FEATURE_NOT_SUPPORTED,
468            format!("{what} in a recursive query is not implemented"),
469        )
470        .at_opt(at))
471    };
472    if !stmt.sortClause.is_empty() {
473        return not_implemented("ORDER BY", list_location(&stmt.sortClause));
474    }
475    if stmt.limitOffset.is_some() {
476        return not_implemented("OFFSET", raw_location(stmt.limitOffset.as_ref()));
477    }
478    if stmt.limitCount.is_some() {
479        return not_implemented("LIMIT", raw_location(stmt.limitCount.as_ref()));
480    }
481    if !stmt.lockingClause.is_empty() {
482        return not_implemented("FOR UPDATE/SHARE", None);
483    }
484    let larg = stmt.larg.as_deref();
485    check_walk(name, Context::NonRecursiveTerm, |w| larg.is_some_and(|l| w.select(l)))?;
486    let rarg = stmt.rarg.as_deref();
487    let count = check_walk(name, Context::Ok, |w| rarg.is_some_and(|r| w.select(r)))?;
488    if count != 1 {
489        return Err(Error::internal("missing recursive reference"));
490    }
491    Ok(())
492}
493
494/// `IncrementVarSublevelsUp` for a copy of the query of `WITH` that a query `levels` levels below the `WITH` reads: the columns of the queries outside the query of `WITH` are `levels` more levels up.
495fn raise_outer(query: &mut Query, levels: usize) {
496    query.each_expr_mut(0, &mut |expr, depth| expr.raise_from(levels, depth + 1));
497}
498
499impl Analyzer<'_> {
500    /// `transformWithClause`: the queries of a `WITH` clause, which the query and the queries after them can read. The queries of `WITH RECURSIVE` all see each other, and their analysis has an order with no forward reference.
501    pub(crate) fn transform_with(&mut self, with: &WithClause) -> Result<()> {
502        let list = ctes(with);
503        for (i, cte) in list.iter().enumerate() {
504            if let Some(again) = list[i + 1..].iter().find(|c| cte_name(c) == cte_name(cte)) {
505                return Err(Error::new(
506                    SqlState::DUPLICATE_ALIAS,
507                    format!("WITH query name \"{}\" specified more than once", cte_name(again)),
508                )
509                .at_opt(place(again.location)));
510            }
511            // A statement that changes data runs once, so only the statement at the top can have it.
512            if !matches!(cte.ctequery, Some(Node::SelectStmt(_))) && !self.outer.is_empty() {
513                return Err(Error::new(
514                    SqlState::FEATURE_NOT_SUPPORTED,
515                    "WITH clause containing a data-modifying statement must be at the top level",
516                )
517                .at_opt(place(cte.location)));
518            }
519        }
520        let base = self.scope.ctes.len();
521        if !with.recursive {
522            self.scope.ctes.extend(list.iter().map(|c| CteItem::new(c, false, false)));
523            for (i, cte) in list.iter().enumerate() {
524                self.analyze_cte(base + i, cte)?;
525                self.scope.ctes[base + i].visible = true;
526            }
527            return Ok(());
528        }
529        // makeDependencyGraph and TopologicalSort.
530        let names: Vec<&str> = list.iter().map(|c| cte_name(c)).collect();
531        let mut order = Vec::with_capacity(list.len());
532        for (i, cte) in list.iter().enumerate() {
533            let mut deps = Deps {
534                names: &names,
535                current: i,
536                inner: Inner::default(),
537                depends: Vec::new(),
538                recursive: false,
539            };
540            walk::one(&mut deps, cte.ctequery.as_ref());
541            order.push((i, deps.depends, deps.recursive));
542        }
543        for i in 0..order.len() {
544            let Some(j) = (i..order.len()).find(|&j| order[j].1.is_empty()) else {
545                return Err(Error::new(
546                    SqlState::FEATURE_NOT_SUPPORTED,
547                    "mutual recursion between WITH items is not implemented",
548                )
549                .at_opt(place(list[order[i].0].location)));
550            };
551            order.swap(i, j);
552            let id = order[i].0;
553            for later in &mut order[i + 1..] {
554                later.1.retain(|d| *d != id);
555            }
556        }
557        for (id, _, recursive) in &order {
558            if *recursive {
559                check_recursion(list[*id])?;
560            }
561        }
562        self.scope
563            .ctes
564            .extend(order.iter().map(|(id, _, rec)| CteItem::new(list[*id], *rec, true)));
565        for (k, (id, _, _)) in order.iter().enumerate() {
566            self.analyze_cte(base + k, list[*id])?;
567        }
568        Ok(())
569    }
570
571    /// `analyzeCTE`: the analysis of the query of `WITH` in the slot `slot` of the scope. The query of a recursive query must give the types of its non-recursive term.
572    fn analyze_cte(&mut self, slot: usize, cte: &CommonTableExpr) -> Result<()> {
573        let stmt = match &cte.ctequery {
574            Some(Node::SelectStmt(stmt)) => stmt,
575            Some(node) => return self.analyze_write_cte(slot, cte, node),
576            None => return Err(Error::internal("a query of WITH with no statement")),
577        };
578        // The type of the mark comes before the analysis of the query, which can read the mark.
579        self.scope.ctes[slot].search = cte.search_clause.as_deref().map(searchcycle::search_clause);
580        if let Some(raw) = cte.cycle_clause.as_deref() {
581            self.scope.ctes[slot].cycle = Some(self.cycle_clause(raw)?);
582        }
583        if self.scope.ctes[slot].recursive {
584            self.scope.ctes[slot].work = Some(self.works);
585            self.works += 1;
586        }
587        self.parent_cte = Some(slot);
588        // The query sees the namespace of this query, as `parse_sub_analyze` with no `p_lateral_only`. The namespace is empty before `FROM`, except for `old` and `new` of a rule action.
589        let query = self.sub_select(stmt, true);
590        self.parent_cte = None;
591        let query = query?;
592        let item = &self.scope.ctes[slot];
593        let shown = query.targets.iter().filter(|t| !t.junk);
594        if item.recursive {
595            let Some(columns) = &item.columns else {
596                return Err(Error::internal("a recursive query with no columns"));
597            };
598            for (i, (target, column)) in shown.zip(columns).enumerate() {
599                let expr = &target.expr;
600                if expr.ty != column.ty || expr.typmod != column.typmod {
601                    return Err(Error::new(
602                        SqlState::DATATYPE_MISMATCH,
603                        format!(
604                            "recursive query \"{}\" column {} has type {} in non-recursive term but type {} overall",
605                            item.name,
606                            i + 1,
607                            types::with_typmod(column.ty, column.typmod),
608                            types::with_typmod(expr.ty, expr.typmod)
609                        ),
610                    )
611                    .with_hint("Cast the output of the non-recursive term to the correct type.")
612                    .at_opt(expr.place()));
613                }
614            }
615        } else {
616            let shown = shown.map(|t| (t.name.clone(), t.expr.ty, t.expr.typmod)).collect();
617            let columns = item.columns(shown)?;
618            self.scope.ctes[slot].columns = Some(columns);
619        }
620        let item = &mut self.scope.ctes[slot];
621        let (search, cycle) = (item.search.as_ref(), item.cycle.as_ref());
622        if search.is_some() || cycle.is_some() {
623            let columns = item.columns.as_deref().unwrap_or_default();
624            searchcycle::check(columns, item.recursive, item.location, search, cycle, &query)?;
625            let rewritten =
626                searchcycle::rewrite(&item.name, columns, item.work, search, cycle, &query)?;
627            item.written = Some(Box::new(query));
628            item.query = Some(Box::new(rewritten));
629            return Ok(());
630        }
631        item.query = Some(Box::new(query));
632        Ok(())
633    }
634
635    /// `analyzeCTE` for an `INSERT`, an `UPDATE`, a `DELETE` or a `MERGE` of `WITH`. The columns are the columns of `RETURNING`, and a statement with no `RETURNING` has no columns.
636    fn analyze_write_cte(&mut self, slot: usize, cte: &CommonTableExpr, node: &Node) -> Result<()> {
637        let query = self.sub_level(true, |analyzer| match node {
638            Node::InsertStmt(s) => analyzer.insert(s),
639            Node::UpdateStmt(s) => analyzer.update(s),
640            Node::DeleteStmt(s) => analyzer.delete(s),
641            Node::MergeStmt(s) => analyzer.merge(s),
642            _ => Err(Error::internal("a query of WITH that is not a statement of a query")),
643        })?;
644        let returning = query.write.as_ref().and_then(|w| w.returning.as_deref());
645        let shown: Vec<&crate::select::Target> =
646            returning.map(|r| r.targets.iter().filter(|t| !t.junk).collect()).unwrap_or_default();
647        let item = &self.scope.ctes[slot];
648        let columns = item
649            .columns(shown.iter().map(|t| (t.name.clone(), t.expr.ty, t.expr.typmod)).collect())?;
650        if cte.search_clause.is_some() || cte.cycle_clause.is_some() {
651            return Err(Error::new(SqlState::SYNTAX_ERROR, "WITH query is not recursive")
652                .at_opt(item.location));
653        }
654        let item = &mut self.scope.ctes[slot];
655        item.origins = shown.iter().map(|t| t.origin).collect();
656        item.returning = returning.is_some();
657        item.columns = Some(columns);
658        item.query = Some(Box::new(query));
659        item.write = Some(self.writes);
660        self.writes += 1;
661        Ok(())
662    }
663
664    /// True when the query of a set operation is the query of a recursive query of `WITH`, whose slot in the scope just outside the query is `parent`.
665    pub(crate) fn recursive_parent(&self, parent: Option<usize>) -> Option<&CteItem> {
666        let item = self.outer.last()?.ctes.get(parent?)?;
667        item.recursive.then_some(item)
668    }
669
670    /// The number of the work table of the recursive query of `parent`, for the top `UNION` of its query.
671    pub(crate) fn work_table(&self, parent: Option<usize>) -> Option<usize> {
672        self.recursive_parent(parent).and_then(|item| item.work)
673    }
674
675    /// `determineRecursiveColTypes`: the columns of a recursive query from its non-recursive term, before the analysis of the recursive term. The names come from the leftmost `SELECT`, and the types from the left side of the `UNION`.
676    pub(crate) fn recursive_columns(
677        &mut self,
678        parent: usize,
679        leftmost: usize,
680        exprs: &[Expr],
681    ) -> Result<()> {
682        let Some(query) = self.scope.relations[leftmost].subquery.as_deref() else {
683            return Err(Error::internal("a set operation with no leftmost SELECT"));
684        };
685        let names = query.targets.iter().filter(|t| !t.junk).map(|t| t.name.clone());
686        let shown = names.zip(exprs).map(|(n, e)| (n, e.ty, e.typmod)).collect();
687        let Some(item) = self.outer.last_mut().and_then(|o| o.ctes.get_mut(parent)) else {
688            return Err(Error::internal("a recursive query that is not in the scope"));
689        };
690        item.columns = Some(item.columns(shown)?);
691        Ok(())
692    }
693
694    /// `scanNameSpaceForCTE` and `addRangeTableEntryForCTE`: a relation of `FROM` that names a query of `WITH` of the query or of a query outside it, or `None`. The relation has a copy of the query of `WITH` as its subquery, or no subquery and a work table for the reference of a recursive query to itself.
695    pub(crate) fn cte_reference(&mut self, rv: &RangeVar) -> Result<Option<Item>> {
696        let Some(name) = unqualified(rv) else { return Ok(None) };
697        let found = self.levels().find_map(|(level, scope)| {
698            scope.ctes.iter().find(|c| c.visible && c.name == name).map(|c| (level, c))
699        });
700        let Some((level, item)) = found else { return Ok(None) };
701        let Some(columns) = item.reference_columns() else {
702            return Err(Error::internal("a reference to a query of WITH with no columns"));
703        };
704        if let Some(write) = item.write {
705            if !item.returning {
706                return Err(Error::new(
707                    SqlState::FEATURE_NOT_SUPPORTED,
708                    format!("WITH query \"{name}\" does not have a RETURNING clause"),
709                )
710                .at_opt(place(rv.location)));
711            }
712            let mut relation = Relation::new(0, columns, None, None, None);
713            relation.cte = Some(CteRef {
714                name: name.to_string(),
715                work: None,
716                write: Some(write),
717                origins: item.origins.clone(),
718                transition: false,
719            });
720            let alias = rv.alias.as_deref();
721            let refname = alias.and_then(|a| a.aliasname.as_deref()).unwrap_or(name).to_string();
722            return self.add_relation(relation, refname, alias).map(Some);
723        }
724        let available = item.columns.as_ref().map_or(0, Vec::len);
725        let extra = columns.len() - available;
726        let mut relation = match &item.query {
727            Some(query) => {
728                let mut query = (**query).clone();
729                if level > 0 {
730                    raise_outer(&mut query, level);
731                }
732                Relation::new(0, columns, Some(Box::new(query)), None, None)
733            }
734            None => Relation::new(0, columns, None, None, None),
735        };
736        let work = if item.query.is_none() { item.work } else { None };
737        relation.cte = Some(CteRef {
738            name: name.to_string(),
739            work,
740            write: None,
741            origins: Vec::new(),
742            transition: false,
743        });
744        let alias = rv.alias.as_deref();
745        let refname = alias.and_then(|a| a.aliasname.as_deref()).unwrap_or(name).to_string();
746        // The alias cannot name the columns of `SEARCH` and `CYCLE`.
747        let given = alias.map_or(0, |a| a.colnames.len());
748        if extra > 0 && given > available {
749            return Err(Error::new(
750                SqlState::INVALID_COLUMN_REFERENCE,
751                format!(
752                    "table \"{refname}\" has {available} columns available but {given} columns specified"
753                ),
754            ));
755        }
756        // A query inside the query of `WITH` does not see the columns of `SEARCH` and `CYCLE` in `*`.
757        let hidden = if level > 0 { extra } else { 0 };
758        let mut item = self.add_relation(relation, refname, alias)?;
759        item.hide_last(hidden);
760        Ok(Some(item))
761    }
762
763    /// `scanNameSpaceForENR` and `addRangeTableEntryForENR`: a reference to a transition table of the trigger that runs, which has a name with no schema. Its columns are the columns of the table of the trigger.
764    pub(crate) fn transition_reference(&mut self, rv: &RangeVar) -> Result<Option<Item>> {
765        let Some(name) = unqualified(rv) else { return Ok(None) };
766        let Some(table) = self.env.transition_table(name) else { return Ok(None) };
767        let Some(rel) = self.env.catalog().and_then(|c| c.relation(table)) else {
768            return Ok(None);
769        };
770        let columns = rel
771            .columns
772            .iter()
773            .map(|c| Column {
774                name: if c.dropped { String::new() } else { c.name.clone() },
775                ty: if c.dropped { 0 } else { c.ty },
776                typmod: c.typmod,
777                not_null: false,
778            })
779            .collect();
780        let mut relation = Relation::new(0, columns, None, None, None);
781        relation.cte = Some(CteRef {
782            name: name.to_string(),
783            work: None,
784            write: None,
785            origins: Vec::new(),
786            transition: true,
787        });
788        let alias = rv.alias.as_deref();
789        let refname = alias.and_then(|a| a.aliasname.as_deref()).unwrap_or(name).to_string();
790        self.add_relation(relation, refname, alias).map(Some)
791    }
792
793    /// `isFutureCTE`: true when a query of `WITH` that the query cannot read yet has the name.
794    pub(crate) fn future_cte(&self, name: &str) -> bool {
795        self.levels().any(|(_, scope)| scope.ctes.iter().any(|c| !c.visible && c.name == name))
796    }
797
798    /// `transformWithClause` for an `INSERT`, an `UPDATE`, a `DELETE` or a `MERGE`: the queries of `WITH` that all the parts of the statement can read.
799    pub(crate) fn write_with(&mut self, with: Option<&WithClause>) -> Result<()> {
800        match with {
801            Some(with) => self.transform_with(with),
802            None => Ok(()),
803        }
804    }
805
806    /// The queries of `WITH` of a write, at the end of its analysis. Each reference has a copy of its query, so the list only names them.
807    pub(crate) fn end_write_with(
808        &mut self,
809        query: &mut Query,
810        with: Option<&WithClause>,
811    ) -> Result<()> {
812        self.finish_auto_view(query);
813        query.recursive = with.is_some_and(|w| w.recursive);
814        query.ctes = self.take_ctes()?;
815        Ok(())
816    }
817
818    /// The queries of `WITH` of the query, at the end of its analysis.
819    pub(crate) fn take_ctes(&mut self) -> Result<Vec<Cte>> {
820        std::mem::take(&mut self.scope.ctes).into_iter().map(CteItem::into_cte).collect()
821    }
822
823    /// The check at the end of `parseCheckAggregates`: the recursive term of a recursive query cannot call an aggregate.
824    pub(crate) fn check_recursive_aggs<'e>(
825        &self,
826        exprs: impl Iterator<Item = &'e Expr>,
827    ) -> Result<()> {
828        let self_reference =
829            self.scope.relations.iter().any(|r| r.cte.as_ref().is_some_and(|c| c.work.is_some()));
830        if !self.has_aggs || !self_reference {
831            return Ok(());
832        }
833        let at = exprs.into_iter().find_map(Expr::first_agg).and_then(Expr::place);
834        Err(Error::new(
835            SqlState::INVALID_RECURSION,
836            "aggregate functions are not allowed in a recursive query's recursive term",
837        )
838        .at_opt(at))
839    }
840}