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