Skip to main content

uqa_sql/binding/
view_dependencies.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Stored-view relation and sequence binding/dependency analysis.
8
9use crate::ast::FunctionBinding;
10use crate::{
11    plan::{QueryPlan, RelationalPlan, SourcePlan},
12    ScalarExpr,
13};
14use uqa_core::{RelationIdentity, Value};
15
16pub fn canonical_virtual_relation_reference(reference: &str) -> Option<String> {
17    crate::catalog::resolve_virtual_relation(&[], reference)
18        .map(crate::catalog::VirtualRelation::qualified_name)
19}
20
21pub fn sequence_function_reference_mut(expression: &mut ScalarExpr) -> Option<&mut String> {
22    let ScalarExpr::Func { name, args, .. } = expression else {
23        return None;
24    };
25    let lower = name.to_ascii_lowercase();
26    let local = lower.strip_prefix("pg_catalog.").unwrap_or(&lower);
27    if !matches!(local, "nextval" | "currval" | "setval")
28        || (lower.contains('.') && !lower.starts_with("pg_catalog."))
29    {
30        return None;
31    }
32    regclass_literal_mut(args.first_mut()?)
33}
34
35pub fn regclass_literal_mut(expression: &mut ScalarExpr) -> Option<&mut String> {
36    match expression {
37        ScalarExpr::Literal(Value::Str(reference)) => Some(reference),
38        ScalarExpr::Cast { expr, ty }
39            if ty.eq_ignore_ascii_case("regclass")
40                || ty.eq_ignore_ascii_case("pg_catalog.regclass") =>
41        {
42            regclass_literal_mut(expr)
43        }
44        _ => None,
45    }
46}
47
48pub fn bind_query_plan_sequence_references<E>(
49    plan: &mut QueryPlan,
50    resolve: &mut impl FnMut(&str) -> Result<String, E>,
51) -> Result<(), E> {
52    let mut error = None;
53    plan.rewrite_scalar_expressions(&mut |expression| {
54        if error.is_some() {
55            return;
56        }
57        let Some(reference) = sequence_function_reference_mut(expression) else {
58            return;
59        };
60        match resolve(reference) {
61            Ok(canonical) => *reference = canonical,
62            Err(binding_error) => error = Some(binding_error),
63        }
64    });
65    error.map_or(Ok(()), Err)
66}
67
68pub fn bind_query_plan_relations<E>(
69    plan: &mut QueryPlan,
70    inherited_ctes: &std::collections::BTreeSet<String>,
71    resolve: &mut impl FnMut(&str) -> Result<String, E>,
72) -> Result<(), E> {
73    bind_query_plan_relation_targets(plan, inherited_ctes, &mut |name, _| resolve(name))
74}
75
76/// Bind concrete relation references with their inheritance scope, excluding CTE aliases at every query boundary.
77pub fn bind_query_plan_relation_targets<E>(
78    plan: &mut QueryPlan,
79    inherited_ctes: &std::collections::BTreeSet<String>,
80    resolve: &mut impl FnMut(&str, bool) -> Result<String, E>,
81) -> Result<(), E> {
82    // Non-recursive CTEs see outer and preceding CTEs. WITH RECURSIVE makes every sibling visible while each body is bound, after which execution orders dependencies before their consumers.
83    let mut visible_ctes = inherited_ctes.clone();
84    let recursive_ctes = plan.ctes.iter().any(|cte| cte.recursive).then(|| {
85        plan.ctes
86            .iter()
87            .map(|cte| cte.name.clone())
88            .collect::<std::collections::BTreeSet<_>>()
89    });
90    for cte in &mut plan.ctes {
91        let body_ctes = recursive_ctes.as_ref().map_or_else(
92            || visible_ctes.clone(),
93            |ctes| inherited_ctes.union(ctes).cloned().collect(),
94        );
95        bind_cte_relation_targets(&mut cte.body, &body_ctes, resolve)?;
96        visible_ctes.insert(cte.name.clone());
97    }
98    bind_relational_targets(&mut plan.root, &visible_ctes, resolve)?;
99    plan.relations_bound = true;
100    Ok(())
101}
102
103pub fn bind_relational_plan_relations<E>(
104    plan: &mut RelationalPlan,
105    visible_ctes: &std::collections::BTreeSet<String>,
106    resolve: &mut impl FnMut(&str) -> Result<String, E>,
107) -> Result<(), E> {
108    bind_relational_targets(plan, visible_ctes, &mut |name, _| resolve(name))
109}
110
111fn bind_relational_targets<E>(
112    plan: &mut RelationalPlan,
113    visible_ctes: &std::collections::BTreeSet<String>,
114    resolve: &mut impl FnMut(&str, bool) -> Result<String, E>,
115) -> Result<(), E> {
116    match plan {
117        RelationalPlan::QueryBlock(block) => {
118            if let Some(source) = &mut block.from {
119                bind_source_targets(source, visible_ctes, resolve)?;
120            }
121            for subquery in &mut block.subqueries {
122                bind_query_plan_relation_targets(subquery, visible_ctes, resolve)?;
123            }
124        }
125        RelationalPlan::SetOp {
126            left,
127            right,
128            subqueries,
129            ..
130        } => {
131            bind_query_plan_relation_targets(left, visible_ctes, resolve)?;
132            bind_query_plan_relation_targets(right, visible_ctes, resolve)?;
133            for subquery in subqueries {
134                bind_query_plan_relation_targets(subquery, visible_ctes, resolve)?;
135            }
136        }
137        RelationalPlan::Values { subqueries, .. } => {
138            for subquery in subqueries {
139                bind_query_plan_relation_targets(subquery, visible_ctes, resolve)?;
140            }
141        }
142    }
143    Ok(())
144}
145
146pub fn bind_source_plan_relations<E>(
147    source: &mut SourcePlan,
148    visible_ctes: &std::collections::BTreeSet<String>,
149    resolve: &mut impl FnMut(&str) -> Result<String, E>,
150) -> Result<(), E> {
151    bind_source_targets(source, visible_ctes, &mut |name, _| resolve(name))
152}
153
154fn bind_source_targets<E>(
155    source: &mut SourcePlan,
156    visible_ctes: &std::collections::BTreeSet<String>,
157    resolve: &mut impl FnMut(&str, bool) -> Result<String, E>,
158) -> Result<(), E> {
159    match source {
160        SourcePlan::Table {
161            name,
162            qualifier,
163            include_descendants,
164            ..
165        } => {
166            if qualifier.is_empty() {
167                *qualifier = RelationIdentity::parse_reference(name)
168                    .map_or_else(|_| name.clone(), |(_, relation)| relation);
169            }
170            let is_cte =
171                RelationIdentity::parse_reference(name)
172                    .ok()
173                    .is_some_and(|(schema, relation)| {
174                        schema.is_none() && visible_ctes.contains(&relation)
175                    });
176            if !is_cte {
177                *name = resolve(name, *include_descendants)?;
178            }
179        }
180        SourcePlan::Join { left, right, .. } => {
181            bind_source_targets(left, visible_ctes, resolve)?;
182            bind_source_targets(right, visible_ctes, resolve)?;
183        }
184        SourcePlan::Subquery { body, .. } => {
185            bind_query_plan_relation_targets(body, visible_ctes, resolve)?;
186        }
187        SourcePlan::Function {
188            name,
189            output_name,
190            relations,
191            ..
192        } => {
193            if output_name.is_empty() {
194                *output_name = RelationIdentity::parse_reference(name)
195                    .map_or_else(|_| name.clone(), |(_, function)| function);
196            }
197            if let Some(relations) = relations {
198                relations.left = resolve(&relations.left, false)?;
199                relations.right = resolve(&relations.right, false)?;
200            }
201        }
202        SourcePlan::FunctionGroup { functions, .. } => {
203            for function in functions {
204                if function.output_name.is_empty() {
205                    function.output_name = RelationIdentity::parse_reference(&function.name)
206                        .map_or_else(|_| function.name.clone(), |(_, name)| name);
207                }
208                if let Some(relations) = &mut function.relations {
209                    relations.left = resolve(&relations.left, false)?;
210                    relations.right = resolve(&relations.right, false)?;
211                }
212            }
213        }
214        SourcePlan::Values { .. } => {}
215    }
216    Ok(())
217}
218
219pub fn relation_reference_matches(reference: &str, target: &RelationIdentity) -> bool {
220    match RelationIdentity::parse_reference(reference) {
221        Ok((Some(schema), name)) => schema == target.schema && name == target.name,
222        Ok((None, name)) => name == target.name,
223        // A malformed stored plan must fail closed: treating it as unrelated
224        // could permit DDL to leave an unexecutable view behind.
225        Err(_) => true,
226    }
227}
228
229pub fn source_plan_references_relation(
230    source: &crate::plan::SourcePlan,
231    target: &RelationIdentity,
232    ctes: &std::collections::BTreeSet<String>,
233) -> bool {
234    match source {
235        crate::plan::SourcePlan::Table { name, .. } => {
236            let is_cte = RelationIdentity::parse_reference(name)
237                .ok()
238                .is_some_and(|(schema, relation)| schema.is_none() && ctes.contains(&relation));
239            !is_cte && relation_reference_matches(name, target)
240        }
241        crate::plan::SourcePlan::Join { left, right, .. } => {
242            source_plan_references_relation(left, target, ctes)
243                || source_plan_references_relation(right, target, ctes)
244        }
245        crate::plan::SourcePlan::Subquery { body, .. } => {
246            query_plan_references_relation(body, target, ctes)
247        }
248        crate::plan::SourcePlan::Function { relations, .. } => {
249            relations.as_ref().is_some_and(|relations| {
250                relation_reference_matches(&relations.left, target)
251                    || relation_reference_matches(&relations.right, target)
252            })
253        }
254        crate::plan::SourcePlan::FunctionGroup { functions, .. } => {
255            functions.iter().any(|function| {
256                function.relations.as_ref().is_some_and(|relations| {
257                    relation_reference_matches(&relations.left, target)
258                        || relation_reference_matches(&relations.right, target)
259                })
260            })
261        }
262        crate::plan::SourcePlan::Values { .. } => false,
263    }
264}
265
266pub fn query_plan_references_relation(
267    query: &crate::plan::QueryPlan,
268    target: &RelationIdentity,
269    inherited_ctes: &std::collections::BTreeSet<String>,
270) -> bool {
271    let mut ctes = inherited_ctes.clone();
272    ctes.extend(query.ctes.iter().map(|cte| cte.name.clone()));
273    if query
274        .ctes
275        .iter()
276        .any(|cte| cte_plan_references_relation(&cte.body, target, &ctes))
277    {
278        return true;
279    }
280    match &query.root {
281        crate::plan::RelationalPlan::QueryBlock(block) => {
282            block
283                .from
284                .as_ref()
285                .is_some_and(|source| source_plan_references_relation(source, target, &ctes))
286                || block
287                    .subqueries
288                    .iter()
289                    .any(|query| query_plan_references_relation(query, target, &ctes))
290        }
291        crate::plan::RelationalPlan::SetOp {
292            left,
293            right,
294            subqueries,
295            ..
296        } => {
297            query_plan_references_relation(left, target, &ctes)
298                || query_plan_references_relation(right, target, &ctes)
299                || subqueries
300                    .iter()
301                    .any(|query| query_plan_references_relation(query, target, &ctes))
302        }
303        crate::plan::RelationalPlan::Values { subqueries, .. } => subqueries
304            .iter()
305            .any(|query| query_plan_references_relation(query, target, &ctes)),
306    }
307}
308
309pub fn query_plan_references_sequence(plan: &QueryPlan, target: &RelationIdentity) -> bool {
310    let mut plan = plan.clone();
311    let mut referenced = false;
312    plan.rewrite_scalar_expressions(&mut |expression| {
313        if let Some(reference) = sequence_function_reference_mut(expression) {
314            referenced |= relation_reference_matches(reference, target);
315        }
316    });
317    referenced
318}
319
320pub use crate::routines::function_binding_matches;
321
322fn function_binding_needs_object_identity(binding: &FunctionBinding) -> bool {
323    !binding.builtin
324        && binding.dispatch.is_none()
325        && binding.resolution_error.is_none()
326        && binding.object_id.is_none()
327}
328
329fn source_plan_has_legacy_routine_identity(source: &SourcePlan) -> bool {
330    match source {
331        SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
332        SourcePlan::Join { left, right, .. } => {
333            source_plan_has_legacy_routine_identity(left)
334                || source_plan_has_legacy_routine_identity(right)
335        }
336        SourcePlan::Subquery { body, .. } => query_plan_sources_have_legacy_routine_identity(body),
337        SourcePlan::Function { binding, .. } => binding
338            .as_ref()
339            .is_some_and(function_binding_needs_object_identity),
340        SourcePlan::FunctionGroup { functions, .. } => functions.iter().any(|function| {
341            function
342                .binding
343                .as_ref()
344                .is_some_and(function_binding_needs_object_identity)
345        }),
346    }
347}
348
349fn relational_plan_has_legacy_routine_identity(plan: &RelationalPlan) -> bool {
350    match plan {
351        RelationalPlan::QueryBlock(block) => {
352            block
353                .from
354                .as_ref()
355                .is_some_and(source_plan_has_legacy_routine_identity)
356                || block
357                    .subqueries
358                    .iter()
359                    .any(query_plan_sources_have_legacy_routine_identity)
360        }
361        RelationalPlan::SetOp {
362            left,
363            right,
364            subqueries,
365            ..
366        } => {
367            query_plan_sources_have_legacy_routine_identity(left)
368                || query_plan_sources_have_legacy_routine_identity(right)
369                || subqueries
370                    .iter()
371                    .any(query_plan_sources_have_legacy_routine_identity)
372        }
373        RelationalPlan::Values { subqueries, .. } => subqueries
374            .iter()
375            .any(query_plan_sources_have_legacy_routine_identity),
376    }
377}
378
379fn query_plan_sources_have_legacy_routine_identity(plan: &QueryPlan) -> bool {
380    plan.ctes.iter().any(|cte| {
381        cte_relational_inputs_any(
382            &cte.body,
383            &query_plan_sources_have_legacy_routine_identity,
384            &source_plan_has_legacy_routine_identity,
385        )
386    }) || relational_plan_has_legacy_routine_identity(&plan.root)
387}
388
389pub fn query_plan_has_legacy_routine_identity(plan: &QueryPlan) -> bool {
390    let mut scalar_plan = plan.clone();
391    let mut legacy = false;
392    scalar_plan.rewrite_scalar_expressions(&mut |expression| {
393        if let ScalarExpr::Func {
394            binding: Some(binding),
395            ..
396        } = expression
397        {
398            legacy |= function_binding_needs_object_identity(binding);
399        }
400    });
401    legacy || query_plan_sources_have_legacy_routine_identity(plan)
402}
403
404fn source_plan_references_function(source: &SourcePlan, target: &FunctionBinding) -> bool {
405    match source {
406        SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
407        SourcePlan::Join { left, right, .. } => {
408            source_plan_references_function(left, target)
409                || source_plan_references_function(right, target)
410        }
411        SourcePlan::Subquery { body, .. } => query_plan_sources_reference_function(body, target),
412        SourcePlan::Function { binding, .. } => binding
413            .as_ref()
414            .is_some_and(|binding| function_binding_matches(binding, target)),
415        SourcePlan::FunctionGroup { functions, .. } => functions.iter().any(|function| {
416            function
417                .binding
418                .as_ref()
419                .is_some_and(|binding| function_binding_matches(binding, target))
420        }),
421    }
422}
423
424fn relational_plan_references_function(plan: &RelationalPlan, target: &FunctionBinding) -> bool {
425    match plan {
426        RelationalPlan::QueryBlock(block) => {
427            block
428                .from
429                .as_ref()
430                .is_some_and(|source| source_plan_references_function(source, target))
431                || block
432                    .subqueries
433                    .iter()
434                    .any(|query| query_plan_sources_reference_function(query, target))
435        }
436        RelationalPlan::SetOp {
437            left,
438            right,
439            subqueries,
440            ..
441        } => {
442            query_plan_sources_reference_function(left, target)
443                || query_plan_sources_reference_function(right, target)
444                || subqueries
445                    .iter()
446                    .any(|query| query_plan_sources_reference_function(query, target))
447        }
448        RelationalPlan::Values { subqueries, .. } => subqueries
449            .iter()
450            .any(|query| query_plan_sources_reference_function(query, target)),
451    }
452}
453
454fn query_plan_sources_reference_function(plan: &QueryPlan, target: &FunctionBinding) -> bool {
455    plan.ctes.iter().any(|cte| {
456        cte_relational_inputs_any(
457            &cte.body,
458            &|query| query_plan_sources_reference_function(query, target),
459            &|source| source_plan_references_function(source, target),
460        )
461    }) || relational_plan_references_function(&plan.root, target)
462}
463
464pub fn query_plan_references_function(plan: &QueryPlan, target: &FunctionBinding) -> bool {
465    let mut scalar_plan = plan.clone();
466    let mut referenced = false;
467    scalar_plan.rewrite_scalar_expressions(&mut |expression| {
468        if let ScalarExpr::Func {
469            binding: Some(binding),
470            ..
471        } = expression
472        {
473            referenced |= function_binding_matches(binding, target);
474        }
475    });
476    referenced || query_plan_sources_reference_function(plan, target)
477}
478
479fn rewrite_source_plan_routine_identity(
480    source: &mut SourcePlan,
481    target: &FunctionBinding,
482    new_name: &str,
483) -> bool {
484    match source {
485        SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
486        SourcePlan::Join { left, right, .. } => {
487            rewrite_source_plan_routine_identity(left, target, new_name)
488                | rewrite_source_plan_routine_identity(right, target, new_name)
489        }
490        SourcePlan::Subquery { body, .. } => {
491            rewrite_query_source_routine_identity(body, target, new_name)
492        }
493        SourcePlan::Function { name, binding, .. } => {
494            let Some(binding) = binding.as_mut() else {
495                return false;
496            };
497            if !function_binding_matches(binding, target) {
498                return false;
499            }
500            *name = new_name.to_string();
501            binding.name = new_name.to_string();
502            true
503        }
504        SourcePlan::FunctionGroup { functions, .. } => {
505            let mut changed = false;
506            for function in functions {
507                let Some(binding) = function.binding.as_mut() else {
508                    continue;
509                };
510                if function_binding_matches(binding, target) {
511                    function.name = new_name.to_string();
512                    binding.name = new_name.to_string();
513                    changed = true;
514                }
515            }
516            changed
517        }
518    }
519}
520
521fn rewrite_relational_plan_source_routine_identity(
522    plan: &mut RelationalPlan,
523    target: &FunctionBinding,
524    new_name: &str,
525) -> bool {
526    match plan {
527        RelationalPlan::QueryBlock(block) => {
528            let mut changed = block.from.as_mut().is_some_and(|source| {
529                rewrite_source_plan_routine_identity(source, target, new_name)
530            });
531            for subquery in &mut block.subqueries {
532                changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
533            }
534            changed
535        }
536        RelationalPlan::SetOp {
537            left,
538            right,
539            subqueries,
540            ..
541        } => {
542            let mut changed = rewrite_query_source_routine_identity(left, target, new_name)
543                | rewrite_query_source_routine_identity(right, target, new_name);
544            for subquery in subqueries {
545                changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
546            }
547            changed
548        }
549        RelationalPlan::Values { subqueries, .. } => {
550            let mut changed = false;
551            for subquery in subqueries {
552                changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
553            }
554            changed
555        }
556    }
557}
558
559fn rewrite_query_source_routine_identity(
560    plan: &mut QueryPlan,
561    target: &FunctionBinding,
562    new_name: &str,
563) -> bool {
564    let mut changed = false;
565    for cte in &mut plan.ctes {
566        changed |= rewrite_cte_source_routine_identity(&mut cte.body, target, new_name);
567    }
568    changed | rewrite_relational_plan_source_routine_identity(&mut plan.root, target, new_name)
569}
570
571pub fn rewrite_query_plan_routine_identity(
572    plan: &mut QueryPlan,
573    target: &FunctionBinding,
574    new_name: &str,
575) -> bool {
576    let mut changed = false;
577    plan.rewrite_scalar_expressions(&mut |expression| {
578        let ScalarExpr::Func {
579            name,
580            binding: Some(binding),
581            ..
582        } = expression
583        else {
584            return;
585        };
586        if function_binding_matches(binding, target) {
587            *name = new_name.to_string();
588            binding.name = new_name.to_string();
589            changed = true;
590        }
591    });
592    changed | rewrite_query_source_routine_identity(plan, target, new_name)
593}
594
595pub fn bind_cte_plan_relations<E>(
596    body: &mut crate::plan::CtePlanBody,
597    inherited: &std::collections::BTreeSet<String>,
598    resolve: &mut impl FnMut(&str) -> Result<String, E>,
599) -> Result<(), E> {
600    bind_cte_relation_targets(body, inherited, &mut |name, _| resolve(name))
601}
602
603fn bind_cte_relation_targets<E>(
604    body: &mut crate::plan::CtePlanBody,
605    inherited: &std::collections::BTreeSet<String>,
606    resolve: &mut impl FnMut(&str, bool) -> Result<String, E>,
607) -> Result<(), E> {
608    let crate::plan::CtePlanBody::Command(command) = body else {
609        let crate::plan::CtePlanBody::Query(query) = body else {
610            unreachable!()
611        };
612        return bind_query_plan_relation_targets(query, inherited, resolve);
613    };
614    let include_descendants = match command.as_ref() {
615        crate::plan::CommandPlan::Update(plan) => plan.include_descendants,
616        crate::plan::CommandPlan::Delete(plan) => plan.include_descendants,
617        _ => false,
618    };
619    if let Some(target) = command.mutation_target_mut() {
620        *target = resolve(target, include_descendants)?;
621    }
622    match command.as_mut() {
623        crate::plan::CommandPlan::Insert(plan) => {
624            plan.target_relation_bound = true;
625            plan.relations_bound = true;
626        }
627        crate::plan::CommandPlan::Update(plan) => {
628            plan.target_relation_bound = true;
629            plan.relations_bound = true;
630        }
631        crate::plan::CommandPlan::Delete(plan) => {
632            plan.target_relation_bound = true;
633            plan.relations_bound = true;
634        }
635        _ => {}
636    }
637    let mut visible = inherited.clone();
638    if let Some(ctes) = command.ctes_mut() {
639        let recursive = ctes.iter().any(|cte| cte.recursive).then(|| {
640            ctes.iter()
641                .map(|cte| cte.name.clone())
642                .collect::<std::collections::BTreeSet<_>>()
643        });
644        for cte in ctes {
645            let scope = recursive.as_ref().map_or_else(
646                || visible.clone(),
647                |names| inherited.union(names).cloned().collect(),
648            );
649            bind_cte_relation_targets(&mut cte.body, &scope, resolve)?;
650            visible.insert(cte.name.clone());
651        }
652    }
653    if let Some(source) = command.source_input_mut() {
654        bind_source_targets(source, &visible, resolve)?;
655    }
656    for query in command.query_inputs_mut() {
657        bind_query_plan_relation_targets(query, &visible, resolve)?;
658    }
659    Ok(())
660}
661
662fn cte_plan_references_relation(
663    body: &crate::plan::CtePlanBody,
664    target: &RelationIdentity,
665    inherited: &std::collections::BTreeSet<String>,
666) -> bool {
667    match body {
668        crate::plan::CtePlanBody::Query(query) => {
669            query_plan_references_relation(query, target, inherited)
670        }
671        crate::plan::CtePlanBody::Command(command) => {
672            let mut visible = inherited.clone();
673            visible.extend(command.ctes().iter().map(|cte| cte.name.clone()));
674            command
675                .mutation_target()
676                .is_some_and(|name| relation_reference_matches(name, target))
677                || command
678                    .ctes()
679                    .iter()
680                    .any(|cte| cte_plan_references_relation(&cte.body, target, &visible))
681                || command
682                    .source_input()
683                    .is_some_and(|source| source_plan_references_relation(source, target, &visible))
684                || command
685                    .query_inputs()
686                    .iter()
687                    .any(|query| query_plan_references_relation(query, target, &visible))
688        }
689    }
690}
691
692fn cte_relational_inputs_any(
693    body: &crate::plan::CtePlanBody,
694    query: &dyn Fn(&QueryPlan) -> bool,
695    source: &dyn Fn(&crate::plan::SourcePlan) -> bool,
696) -> bool {
697    match body {
698        crate::plan::CtePlanBody::Query(plan) => query(plan),
699        crate::plan::CtePlanBody::Command(command) => {
700            command
701                .ctes()
702                .iter()
703                .any(|cte| cte_relational_inputs_any(&cte.body, query, source))
704                || command.query_inputs().iter().any(|plan| query(plan))
705                || command.source_input().is_some_and(source)
706        }
707    }
708}
709
710fn rewrite_cte_source_routine_identity(
711    body: &mut crate::plan::CtePlanBody,
712    target: &FunctionBinding,
713    new_name: &str,
714) -> bool {
715    match body {
716        crate::plan::CtePlanBody::Query(query) => {
717            rewrite_query_source_routine_identity(query, target, new_name)
718        }
719        crate::plan::CtePlanBody::Command(command) => {
720            let mut changed = false;
721            if let Some(ctes) = command.ctes_mut() {
722                for cte in ctes {
723                    changed |= rewrite_cte_source_routine_identity(&mut cte.body, target, new_name);
724                }
725            }
726            if let Some(source) = command.source_input_mut() {
727                changed |= rewrite_source_plan_routine_identity(source, target, new_name);
728            }
729            for query in command.query_inputs_mut() {
730                changed |= rewrite_query_source_routine_identity(query, target, new_name);
731            }
732            changed
733        }
734    }
735}
736
737pub mod restoration;