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