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 = RelationIdentity::parse_reference(name).is_ok_and(|(schema, relation)| {
171                schema.is_none() && visible_ctes.contains(&relation)
172            });
173            if !is_cte {
174                *name = resolve(name, *include_descendants)?;
175            }
176        }
177        SourcePlan::Join { left, right, .. } => {
178            bind_source_targets(left, visible_ctes, resolve)?;
179            bind_source_targets(right, visible_ctes, resolve)?;
180        }
181        SourcePlan::Subquery { body, .. } => {
182            bind_query_plan_relation_targets(body, visible_ctes, resolve)?;
183        }
184        SourcePlan::Function {
185            name,
186            output_name,
187            relations,
188            ..
189        } => {
190            if output_name.is_empty() {
191                *output_name = RelationIdentity::parse_reference(name)
192                    .map_or_else(|_| name.clone(), |(_, function)| function);
193            }
194            if let Some(relations) = relations {
195                relations.left = resolve(&relations.left, false)?;
196                relations.right = resolve(&relations.right, false)?;
197            }
198        }
199        SourcePlan::FunctionGroup { functions, .. } => {
200            for function in functions {
201                if function.output_name.is_empty() {
202                    function.output_name = RelationIdentity::parse_reference(&function.name)
203                        .map_or_else(|_| function.name.clone(), |(_, name)| name);
204                }
205                if let Some(relations) = &mut function.relations {
206                    relations.left = resolve(&relations.left, false)?;
207                    relations.right = resolve(&relations.right, false)?;
208                }
209            }
210        }
211        SourcePlan::Values { .. } => {}
212    }
213    Ok(())
214}
215
216pub fn relation_reference_matches(reference: &str, target: &RelationIdentity) -> bool {
217    match RelationIdentity::parse_reference(reference) {
218        Ok((Some(schema), name)) => schema == target.schema && name == target.name,
219        Ok((None, name)) => name == target.name,
220        // A malformed stored plan must fail closed: treating it as unrelated
221        // could permit DDL to leave an unexecutable view behind.
222        Err(_) => true,
223    }
224}
225
226pub fn source_plan_references_relation(
227    source: &crate::plan::SourcePlan,
228    target: &RelationIdentity,
229    ctes: &std::collections::BTreeSet<String>,
230) -> bool {
231    match source {
232        crate::plan::SourcePlan::Table { name, .. } => {
233            let is_cte = RelationIdentity::parse_reference(name)
234                .is_ok_and(|(schema, relation)| schema.is_none() && ctes.contains(&relation));
235            !is_cte && relation_reference_matches(name, target)
236        }
237        crate::plan::SourcePlan::Join { left, right, .. } => {
238            source_plan_references_relation(left, target, ctes)
239                || source_plan_references_relation(right, target, ctes)
240        }
241        crate::plan::SourcePlan::Subquery { body, .. } => {
242            query_plan_references_relation(body, target, ctes)
243        }
244        crate::plan::SourcePlan::Function { relations, .. } => {
245            relations.as_ref().is_some_and(|relations| {
246                relation_reference_matches(&relations.left, target)
247                    || relation_reference_matches(&relations.right, target)
248            })
249        }
250        crate::plan::SourcePlan::FunctionGroup { functions, .. } => {
251            functions.iter().any(|function| {
252                function.relations.as_ref().is_some_and(|relations| {
253                    relation_reference_matches(&relations.left, target)
254                        || relation_reference_matches(&relations.right, target)
255                })
256            })
257        }
258        crate::plan::SourcePlan::Values { .. } => false,
259    }
260}
261
262pub fn query_plan_references_relation(
263    query: &crate::plan::QueryPlan,
264    target: &RelationIdentity,
265    inherited_ctes: &std::collections::BTreeSet<String>,
266) -> bool {
267    let mut ctes = inherited_ctes.clone();
268    ctes.extend(query.ctes.iter().map(|cte| cte.name.clone()));
269    if query
270        .ctes
271        .iter()
272        .any(|cte| cte_plan_references_relation(&cte.body, target, &ctes))
273    {
274        return true;
275    }
276    match &query.root {
277        crate::plan::RelationalPlan::QueryBlock(block) => {
278            block
279                .from
280                .as_ref()
281                .is_some_and(|source| source_plan_references_relation(source, target, &ctes))
282                || block
283                    .subqueries
284                    .iter()
285                    .any(|query| query_plan_references_relation(query, target, &ctes))
286        }
287        crate::plan::RelationalPlan::SetOp {
288            left,
289            right,
290            subqueries,
291            ..
292        } => {
293            query_plan_references_relation(left, target, &ctes)
294                || query_plan_references_relation(right, target, &ctes)
295                || subqueries
296                    .iter()
297                    .any(|query| query_plan_references_relation(query, target, &ctes))
298        }
299        crate::plan::RelationalPlan::Values { subqueries, .. } => subqueries
300            .iter()
301            .any(|query| query_plan_references_relation(query, target, &ctes)),
302    }
303}
304
305pub fn query_plan_references_sequence(plan: &QueryPlan, target: &RelationIdentity) -> bool {
306    let mut plan = plan.clone();
307    let mut referenced = false;
308    plan.rewrite_scalar_expressions(&mut |expression| {
309        if let Some(reference) = sequence_function_reference_mut(expression) {
310            referenced |= relation_reference_matches(reference, target);
311        }
312    });
313    referenced
314}
315
316pub use crate::routines::function_binding_matches;
317
318fn function_binding_needs_object_identity(binding: &FunctionBinding) -> bool {
319    !binding.builtin
320        && binding.dispatch.is_none()
321        && binding.resolution_error.is_none()
322        && binding.object_id.is_none()
323}
324
325fn source_plan_has_legacy_routine_identity(source: &SourcePlan) -> bool {
326    match source {
327        SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
328        SourcePlan::Join { left, right, .. } => {
329            source_plan_has_legacy_routine_identity(left)
330                || source_plan_has_legacy_routine_identity(right)
331        }
332        SourcePlan::Subquery { body, .. } => query_plan_sources_have_legacy_routine_identity(body),
333        SourcePlan::Function { binding, .. } => binding
334            .as_ref()
335            .is_some_and(function_binding_needs_object_identity),
336        SourcePlan::FunctionGroup { functions, .. } => functions.iter().any(|function| {
337            function
338                .binding
339                .as_ref()
340                .is_some_and(function_binding_needs_object_identity)
341        }),
342    }
343}
344
345fn relational_plan_has_legacy_routine_identity(plan: &RelationalPlan) -> bool {
346    match plan {
347        RelationalPlan::QueryBlock(block) => {
348            block
349                .from
350                .as_ref()
351                .is_some_and(source_plan_has_legacy_routine_identity)
352                || block
353                    .subqueries
354                    .iter()
355                    .any(query_plan_sources_have_legacy_routine_identity)
356        }
357        RelationalPlan::SetOp {
358            left,
359            right,
360            subqueries,
361            ..
362        } => {
363            query_plan_sources_have_legacy_routine_identity(left)
364                || query_plan_sources_have_legacy_routine_identity(right)
365                || subqueries
366                    .iter()
367                    .any(query_plan_sources_have_legacy_routine_identity)
368        }
369        RelationalPlan::Values { subqueries, .. } => subqueries
370            .iter()
371            .any(query_plan_sources_have_legacy_routine_identity),
372    }
373}
374
375fn query_plan_sources_have_legacy_routine_identity(plan: &QueryPlan) -> bool {
376    plan.ctes.iter().any(|cte| {
377        cte_relational_inputs_any(
378            &cte.body,
379            &query_plan_sources_have_legacy_routine_identity,
380            &source_plan_has_legacy_routine_identity,
381        )
382    }) || relational_plan_has_legacy_routine_identity(&plan.root)
383}
384
385pub fn query_plan_has_legacy_routine_identity(plan: &QueryPlan) -> bool {
386    let mut scalar_plan = plan.clone();
387    let mut legacy = false;
388    scalar_plan.rewrite_scalar_expressions(&mut |expression| {
389        if let ScalarExpr::Func {
390            name,
391            binding,
392            order_syntax,
393            order_by,
394            ..
395        } = expression
396        {
397            legacy |= binding
398                .as_ref()
399                .is_some_and(function_binding_needs_object_identity);
400            // Legacy ordered-set nodes lack the selected combined signature and
401            // written syntax; restore both in the existing initial-open transaction.
402            legacy |= *order_syntax == crate::ast::FunctionOrderSyntax::Legacy
403                && super::ordered_calls::uses_ordered_arguments(
404                    *order_syntax,
405                    name,
406                    binding.as_ref(),
407                    order_by.len(),
408                );
409        }
410    });
411    legacy || query_plan_sources_have_legacy_routine_identity(plan)
412}
413
414fn source_plan_references_function(source: &SourcePlan, target: &FunctionBinding) -> bool {
415    match source {
416        SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
417        SourcePlan::Join { left, right, .. } => {
418            source_plan_references_function(left, target)
419                || source_plan_references_function(right, target)
420        }
421        SourcePlan::Subquery { body, .. } => query_plan_sources_reference_function(body, target),
422        SourcePlan::Function { binding, .. } => binding
423            .as_ref()
424            .is_some_and(|binding| function_binding_matches(binding, target)),
425        SourcePlan::FunctionGroup { functions, .. } => functions.iter().any(|function| {
426            function
427                .binding
428                .as_ref()
429                .is_some_and(|binding| function_binding_matches(binding, target))
430        }),
431    }
432}
433
434fn relational_plan_references_function(plan: &RelationalPlan, target: &FunctionBinding) -> bool {
435    match plan {
436        RelationalPlan::QueryBlock(block) => {
437            block
438                .from
439                .as_ref()
440                .is_some_and(|source| source_plan_references_function(source, target))
441                || block
442                    .subqueries
443                    .iter()
444                    .any(|query| query_plan_sources_reference_function(query, target))
445        }
446        RelationalPlan::SetOp {
447            left,
448            right,
449            subqueries,
450            ..
451        } => {
452            query_plan_sources_reference_function(left, target)
453                || query_plan_sources_reference_function(right, target)
454                || subqueries
455                    .iter()
456                    .any(|query| query_plan_sources_reference_function(query, target))
457        }
458        RelationalPlan::Values { subqueries, .. } => subqueries
459            .iter()
460            .any(|query| query_plan_sources_reference_function(query, target)),
461    }
462}
463
464fn query_plan_sources_reference_function(plan: &QueryPlan, target: &FunctionBinding) -> bool {
465    plan.ctes.iter().any(|cte| {
466        cte_relational_inputs_any(
467            &cte.body,
468            &|query| query_plan_sources_reference_function(query, target),
469            &|source| source_plan_references_function(source, target),
470        )
471    }) || relational_plan_references_function(&plan.root, target)
472}
473
474pub fn query_plan_references_function(plan: &QueryPlan, target: &FunctionBinding) -> bool {
475    let mut scalar_plan = plan.clone();
476    let mut referenced = false;
477    scalar_plan.rewrite_scalar_expressions(&mut |expression| {
478        if let ScalarExpr::Func {
479            binding: Some(binding),
480            ..
481        } = expression
482        {
483            referenced |= function_binding_matches(binding, target);
484        }
485    });
486    referenced || query_plan_sources_reference_function(plan, target)
487}
488
489fn rewrite_source_plan_routine_identity(
490    source: &mut SourcePlan,
491    target: &FunctionBinding,
492    new_name: &str,
493) -> bool {
494    match source {
495        SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
496        SourcePlan::Join { left, right, .. } => {
497            rewrite_source_plan_routine_identity(left, target, new_name)
498                | rewrite_source_plan_routine_identity(right, target, new_name)
499        }
500        SourcePlan::Subquery { body, .. } => {
501            rewrite_query_source_routine_identity(body, target, new_name)
502        }
503        SourcePlan::Function { name, binding, .. } => {
504            let Some(binding) = binding.as_mut() else {
505                return false;
506            };
507            if !function_binding_matches(binding, target) {
508                return false;
509            }
510            *name = new_name.to_string();
511            binding.name = new_name.to_string();
512            true
513        }
514        SourcePlan::FunctionGroup { functions, .. } => {
515            let mut changed = false;
516            for function in functions {
517                let Some(binding) = function.binding.as_mut() else {
518                    continue;
519                };
520                if function_binding_matches(binding, target) {
521                    function.name = new_name.to_string();
522                    binding.name = new_name.to_string();
523                    changed = true;
524                }
525            }
526            changed
527        }
528    }
529}
530
531fn rewrite_relational_plan_source_routine_identity(
532    plan: &mut RelationalPlan,
533    target: &FunctionBinding,
534    new_name: &str,
535) -> bool {
536    match plan {
537        RelationalPlan::QueryBlock(block) => {
538            let mut changed = block.from.as_mut().is_some_and(|source| {
539                rewrite_source_plan_routine_identity(source, target, new_name)
540            });
541            for subquery in &mut block.subqueries {
542                changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
543            }
544            changed
545        }
546        RelationalPlan::SetOp {
547            left,
548            right,
549            subqueries,
550            ..
551        } => {
552            let mut changed = rewrite_query_source_routine_identity(left, target, new_name)
553                | rewrite_query_source_routine_identity(right, target, new_name);
554            for subquery in subqueries {
555                changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
556            }
557            changed
558        }
559        RelationalPlan::Values { subqueries, .. } => {
560            let mut changed = false;
561            for subquery in subqueries {
562                changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
563            }
564            changed
565        }
566    }
567}
568
569fn rewrite_query_source_routine_identity(
570    plan: &mut QueryPlan,
571    target: &FunctionBinding,
572    new_name: &str,
573) -> bool {
574    let mut changed = false;
575    for cte in &mut plan.ctes {
576        changed |= rewrite_cte_source_routine_identity(&mut cte.body, target, new_name);
577    }
578    changed | rewrite_relational_plan_source_routine_identity(&mut plan.root, target, new_name)
579}
580
581pub fn rewrite_query_plan_routine_identity(
582    plan: &mut QueryPlan,
583    target: &FunctionBinding,
584    new_name: &str,
585) -> bool {
586    let mut changed = false;
587    plan.rewrite_scalar_expressions(&mut |expression| {
588        let ScalarExpr::Func {
589            name,
590            binding: Some(binding),
591            ..
592        } = expression
593        else {
594            return;
595        };
596        if function_binding_matches(binding, target) {
597            *name = new_name.to_string();
598            binding.name = new_name.to_string();
599            changed = true;
600        }
601    });
602    changed | rewrite_query_source_routine_identity(plan, target, new_name)
603}
604
605pub fn bind_cte_plan_relations<E>(
606    body: &mut crate::plan::CtePlanBody,
607    inherited: &std::collections::BTreeSet<String>,
608    resolve: &mut impl FnMut(&str) -> Result<String, E>,
609) -> Result<(), E> {
610    bind_cte_relation_targets(body, inherited, &mut |name, _| resolve(name))
611}
612
613fn bind_cte_relation_targets<E>(
614    body: &mut crate::plan::CtePlanBody,
615    inherited: &std::collections::BTreeSet<String>,
616    resolve: &mut impl FnMut(&str, bool) -> Result<String, E>,
617) -> Result<(), E> {
618    let crate::plan::CtePlanBody::Command(command) = body else {
619        let crate::plan::CtePlanBody::Query(query) = body else {
620            unreachable!()
621        };
622        return bind_query_plan_relation_targets(query, inherited, resolve);
623    };
624    let include_descendants = match command.as_ref() {
625        crate::plan::CommandPlan::Update(plan) => plan.include_descendants,
626        crate::plan::CommandPlan::Delete(plan) => plan.include_descendants,
627        _ => false,
628    };
629    if let Some(target) = command.mutation_target_mut() {
630        *target = resolve(target, include_descendants)?;
631    }
632    match command.as_mut() {
633        crate::plan::CommandPlan::Insert(plan) => {
634            plan.target_relation_bound = true;
635            plan.relations_bound = true;
636        }
637        crate::plan::CommandPlan::Update(plan) => {
638            plan.target_relation_bound = true;
639            plan.relations_bound = true;
640        }
641        crate::plan::CommandPlan::Delete(plan) => {
642            plan.target_relation_bound = true;
643            plan.relations_bound = true;
644        }
645        _ => {}
646    }
647    let mut visible = inherited.clone();
648    if let Some(ctes) = command.ctes_mut() {
649        let recursive = ctes.iter().any(|cte| cte.recursive).then(|| {
650            ctes.iter()
651                .map(|cte| cte.name.clone())
652                .collect::<std::collections::BTreeSet<_>>()
653        });
654        for cte in ctes {
655            let scope = recursive.as_ref().map_or_else(
656                || visible.clone(),
657                |names| inherited.union(names).cloned().collect(),
658            );
659            bind_cte_relation_targets(&mut cte.body, &scope, resolve)?;
660            visible.insert(cte.name.clone());
661        }
662    }
663    if let Some(source) = command.source_input_mut() {
664        bind_source_targets(source, &visible, resolve)?;
665    }
666    for query in command.query_inputs_mut() {
667        bind_query_plan_relation_targets(query, &visible, resolve)?;
668    }
669    Ok(())
670}
671
672fn cte_plan_references_relation(
673    body: &crate::plan::CtePlanBody,
674    target: &RelationIdentity,
675    inherited: &std::collections::BTreeSet<String>,
676) -> bool {
677    match body {
678        crate::plan::CtePlanBody::Query(query) => {
679            query_plan_references_relation(query, target, inherited)
680        }
681        crate::plan::CtePlanBody::Command(command) => {
682            let mut visible = inherited.clone();
683            visible.extend(command.ctes().iter().map(|cte| cte.name.clone()));
684            command
685                .mutation_target()
686                .is_some_and(|name| relation_reference_matches(name, target))
687                || command
688                    .ctes()
689                    .iter()
690                    .any(|cte| cte_plan_references_relation(&cte.body, target, &visible))
691                || command
692                    .source_input()
693                    .is_some_and(|source| source_plan_references_relation(source, target, &visible))
694                || command
695                    .query_inputs()
696                    .iter()
697                    .any(|query| query_plan_references_relation(query, target, &visible))
698        }
699    }
700}
701
702fn cte_relational_inputs_any(
703    body: &crate::plan::CtePlanBody,
704    query: &dyn Fn(&QueryPlan) -> bool,
705    source: &dyn Fn(&crate::plan::SourcePlan) -> bool,
706) -> bool {
707    match body {
708        crate::plan::CtePlanBody::Query(plan) => query(plan),
709        crate::plan::CtePlanBody::Command(command) => {
710            command
711                .ctes()
712                .iter()
713                .any(|cte| cte_relational_inputs_any(&cte.body, query, source))
714                || command.query_inputs().iter().any(|plan| query(plan))
715                || command.source_input().is_some_and(source)
716        }
717    }
718}
719
720fn rewrite_cte_source_routine_identity(
721    body: &mut crate::plan::CtePlanBody,
722    target: &FunctionBinding,
723    new_name: &str,
724) -> bool {
725    match body {
726        crate::plan::CtePlanBody::Query(query) => {
727            rewrite_query_source_routine_identity(query, target, new_name)
728        }
729        crate::plan::CtePlanBody::Command(command) => {
730            let mut changed = false;
731            if let Some(ctes) = command.ctes_mut() {
732                for cte in ctes {
733                    changed |= rewrite_cte_source_routine_identity(&mut cte.body, target, new_name);
734                }
735            }
736            if let Some(source) = command.source_input_mut() {
737                changed |= rewrite_source_plan_routine_identity(source, target, new_name);
738            }
739            for query in command.query_inputs_mut() {
740                changed |= rewrite_query_source_routine_identity(query, target, new_name);
741            }
742            changed
743        }
744    }
745}
746
747pub mod restoration;