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            name,
395            binding,
396            order_syntax,
397            order_by,
398            ..
399        } = expression
400        {
401            legacy |= binding
402                .as_ref()
403                .is_some_and(function_binding_needs_object_identity);
404            // Legacy ordered-set nodes lack the selected combined signature and
405            // written syntax; restore both in the existing initial-open transaction.
406            legacy |= *order_syntax == crate::ast::FunctionOrderSyntax::Legacy
407                && super::ordered_calls::uses_ordered_arguments(
408                    *order_syntax,
409                    name,
410                    binding.as_ref(),
411                    order_by.len(),
412                );
413        }
414    });
415    legacy || query_plan_sources_have_legacy_routine_identity(plan)
416}
417
418fn source_plan_references_function(source: &SourcePlan, target: &FunctionBinding) -> bool {
419    match source {
420        SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
421        SourcePlan::Join { left, right, .. } => {
422            source_plan_references_function(left, target)
423                || source_plan_references_function(right, target)
424        }
425        SourcePlan::Subquery { body, .. } => query_plan_sources_reference_function(body, target),
426        SourcePlan::Function { binding, .. } => binding
427            .as_ref()
428            .is_some_and(|binding| function_binding_matches(binding, target)),
429        SourcePlan::FunctionGroup { functions, .. } => functions.iter().any(|function| {
430            function
431                .binding
432                .as_ref()
433                .is_some_and(|binding| function_binding_matches(binding, target))
434        }),
435    }
436}
437
438fn relational_plan_references_function(plan: &RelationalPlan, target: &FunctionBinding) -> bool {
439    match plan {
440        RelationalPlan::QueryBlock(block) => {
441            block
442                .from
443                .as_ref()
444                .is_some_and(|source| source_plan_references_function(source, target))
445                || block
446                    .subqueries
447                    .iter()
448                    .any(|query| query_plan_sources_reference_function(query, target))
449        }
450        RelationalPlan::SetOp {
451            left,
452            right,
453            subqueries,
454            ..
455        } => {
456            query_plan_sources_reference_function(left, target)
457                || query_plan_sources_reference_function(right, target)
458                || subqueries
459                    .iter()
460                    .any(|query| query_plan_sources_reference_function(query, target))
461        }
462        RelationalPlan::Values { subqueries, .. } => subqueries
463            .iter()
464            .any(|query| query_plan_sources_reference_function(query, target)),
465    }
466}
467
468fn query_plan_sources_reference_function(plan: &QueryPlan, target: &FunctionBinding) -> bool {
469    plan.ctes.iter().any(|cte| {
470        cte_relational_inputs_any(
471            &cte.body,
472            &|query| query_plan_sources_reference_function(query, target),
473            &|source| source_plan_references_function(source, target),
474        )
475    }) || relational_plan_references_function(&plan.root, target)
476}
477
478pub fn query_plan_references_function(plan: &QueryPlan, target: &FunctionBinding) -> bool {
479    let mut scalar_plan = plan.clone();
480    let mut referenced = false;
481    scalar_plan.rewrite_scalar_expressions(&mut |expression| {
482        if let ScalarExpr::Func {
483            binding: Some(binding),
484            ..
485        } = expression
486        {
487            referenced |= function_binding_matches(binding, target);
488        }
489    });
490    referenced || query_plan_sources_reference_function(plan, target)
491}
492
493fn rewrite_source_plan_routine_identity(
494    source: &mut SourcePlan,
495    target: &FunctionBinding,
496    new_name: &str,
497) -> bool {
498    match source {
499        SourcePlan::Table { .. } | SourcePlan::Values { .. } => false,
500        SourcePlan::Join { left, right, .. } => {
501            rewrite_source_plan_routine_identity(left, target, new_name)
502                | rewrite_source_plan_routine_identity(right, target, new_name)
503        }
504        SourcePlan::Subquery { body, .. } => {
505            rewrite_query_source_routine_identity(body, target, new_name)
506        }
507        SourcePlan::Function { name, binding, .. } => {
508            let Some(binding) = binding.as_mut() else {
509                return false;
510            };
511            if !function_binding_matches(binding, target) {
512                return false;
513            }
514            *name = new_name.to_string();
515            binding.name = new_name.to_string();
516            true
517        }
518        SourcePlan::FunctionGroup { functions, .. } => {
519            let mut changed = false;
520            for function in functions {
521                let Some(binding) = function.binding.as_mut() else {
522                    continue;
523                };
524                if function_binding_matches(binding, target) {
525                    function.name = new_name.to_string();
526                    binding.name = new_name.to_string();
527                    changed = true;
528                }
529            }
530            changed
531        }
532    }
533}
534
535fn rewrite_relational_plan_source_routine_identity(
536    plan: &mut RelationalPlan,
537    target: &FunctionBinding,
538    new_name: &str,
539) -> bool {
540    match plan {
541        RelationalPlan::QueryBlock(block) => {
542            let mut changed = block.from.as_mut().is_some_and(|source| {
543                rewrite_source_plan_routine_identity(source, target, new_name)
544            });
545            for subquery in &mut block.subqueries {
546                changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
547            }
548            changed
549        }
550        RelationalPlan::SetOp {
551            left,
552            right,
553            subqueries,
554            ..
555        } => {
556            let mut changed = rewrite_query_source_routine_identity(left, target, new_name)
557                | rewrite_query_source_routine_identity(right, target, new_name);
558            for subquery in subqueries {
559                changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
560            }
561            changed
562        }
563        RelationalPlan::Values { subqueries, .. } => {
564            let mut changed = false;
565            for subquery in subqueries {
566                changed |= rewrite_query_source_routine_identity(subquery, target, new_name);
567            }
568            changed
569        }
570    }
571}
572
573fn rewrite_query_source_routine_identity(
574    plan: &mut QueryPlan,
575    target: &FunctionBinding,
576    new_name: &str,
577) -> bool {
578    let mut changed = false;
579    for cte in &mut plan.ctes {
580        changed |= rewrite_cte_source_routine_identity(&mut cte.body, target, new_name);
581    }
582    changed | rewrite_relational_plan_source_routine_identity(&mut plan.root, target, new_name)
583}
584
585pub fn rewrite_query_plan_routine_identity(
586    plan: &mut QueryPlan,
587    target: &FunctionBinding,
588    new_name: &str,
589) -> bool {
590    let mut changed = false;
591    plan.rewrite_scalar_expressions(&mut |expression| {
592        let ScalarExpr::Func {
593            name,
594            binding: Some(binding),
595            ..
596        } = expression
597        else {
598            return;
599        };
600        if function_binding_matches(binding, target) {
601            *name = new_name.to_string();
602            binding.name = new_name.to_string();
603            changed = true;
604        }
605    });
606    changed | rewrite_query_source_routine_identity(plan, target, new_name)
607}
608
609pub fn bind_cte_plan_relations<E>(
610    body: &mut crate::plan::CtePlanBody,
611    inherited: &std::collections::BTreeSet<String>,
612    resolve: &mut impl FnMut(&str) -> Result<String, E>,
613) -> Result<(), E> {
614    bind_cte_relation_targets(body, inherited, &mut |name, _| resolve(name))
615}
616
617fn bind_cte_relation_targets<E>(
618    body: &mut crate::plan::CtePlanBody,
619    inherited: &std::collections::BTreeSet<String>,
620    resolve: &mut impl FnMut(&str, bool) -> Result<String, E>,
621) -> Result<(), E> {
622    let crate::plan::CtePlanBody::Command(command) = body else {
623        let crate::plan::CtePlanBody::Query(query) = body else {
624            unreachable!()
625        };
626        return bind_query_plan_relation_targets(query, inherited, resolve);
627    };
628    let include_descendants = match command.as_ref() {
629        crate::plan::CommandPlan::Update(plan) => plan.include_descendants,
630        crate::plan::CommandPlan::Delete(plan) => plan.include_descendants,
631        _ => false,
632    };
633    if let Some(target) = command.mutation_target_mut() {
634        *target = resolve(target, include_descendants)?;
635    }
636    match command.as_mut() {
637        crate::plan::CommandPlan::Insert(plan) => {
638            plan.target_relation_bound = true;
639            plan.relations_bound = true;
640        }
641        crate::plan::CommandPlan::Update(plan) => {
642            plan.target_relation_bound = true;
643            plan.relations_bound = true;
644        }
645        crate::plan::CommandPlan::Delete(plan) => {
646            plan.target_relation_bound = true;
647            plan.relations_bound = true;
648        }
649        _ => {}
650    }
651    let mut visible = inherited.clone();
652    if let Some(ctes) = command.ctes_mut() {
653        let recursive = ctes.iter().any(|cte| cte.recursive).then(|| {
654            ctes.iter()
655                .map(|cte| cte.name.clone())
656                .collect::<std::collections::BTreeSet<_>>()
657        });
658        for cte in ctes {
659            let scope = recursive.as_ref().map_or_else(
660                || visible.clone(),
661                |names| inherited.union(names).cloned().collect(),
662            );
663            bind_cte_relation_targets(&mut cte.body, &scope, resolve)?;
664            visible.insert(cte.name.clone());
665        }
666    }
667    if let Some(source) = command.source_input_mut() {
668        bind_source_targets(source, &visible, resolve)?;
669    }
670    for query in command.query_inputs_mut() {
671        bind_query_plan_relation_targets(query, &visible, resolve)?;
672    }
673    Ok(())
674}
675
676fn cte_plan_references_relation(
677    body: &crate::plan::CtePlanBody,
678    target: &RelationIdentity,
679    inherited: &std::collections::BTreeSet<String>,
680) -> bool {
681    match body {
682        crate::plan::CtePlanBody::Query(query) => {
683            query_plan_references_relation(query, target, inherited)
684        }
685        crate::plan::CtePlanBody::Command(command) => {
686            let mut visible = inherited.clone();
687            visible.extend(command.ctes().iter().map(|cte| cte.name.clone()));
688            command
689                .mutation_target()
690                .is_some_and(|name| relation_reference_matches(name, target))
691                || command
692                    .ctes()
693                    .iter()
694                    .any(|cte| cte_plan_references_relation(&cte.body, target, &visible))
695                || command
696                    .source_input()
697                    .is_some_and(|source| source_plan_references_relation(source, target, &visible))
698                || command
699                    .query_inputs()
700                    .iter()
701                    .any(|query| query_plan_references_relation(query, target, &visible))
702        }
703    }
704}
705
706fn cte_relational_inputs_any(
707    body: &crate::plan::CtePlanBody,
708    query: &dyn Fn(&QueryPlan) -> bool,
709    source: &dyn Fn(&crate::plan::SourcePlan) -> bool,
710) -> bool {
711    match body {
712        crate::plan::CtePlanBody::Query(plan) => query(plan),
713        crate::plan::CtePlanBody::Command(command) => {
714            command
715                .ctes()
716                .iter()
717                .any(|cte| cte_relational_inputs_any(&cte.body, query, source))
718                || command.query_inputs().iter().any(|plan| query(plan))
719                || command.source_input().is_some_and(source)
720        }
721    }
722}
723
724fn rewrite_cte_source_routine_identity(
725    body: &mut crate::plan::CtePlanBody,
726    target: &FunctionBinding,
727    new_name: &str,
728) -> bool {
729    match body {
730        crate::plan::CtePlanBody::Query(query) => {
731            rewrite_query_source_routine_identity(query, target, new_name)
732        }
733        crate::plan::CtePlanBody::Command(command) => {
734            let mut changed = false;
735            if let Some(ctes) = command.ctes_mut() {
736                for cte in ctes {
737                    changed |= rewrite_cte_source_routine_identity(&mut cte.body, target, new_name);
738                }
739            }
740            if let Some(source) = command.source_input_mut() {
741                changed |= rewrite_source_plan_routine_identity(source, target, new_name);
742            }
743            for query in command.query_inputs_mut() {
744                changed |= rewrite_query_source_routine_identity(query, target, new_name);
745            }
746            changed
747        }
748    }
749}
750
751pub mod restoration;