Skip to main content

uqa_sql/routines/lifecycle/
rename.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Routine rename identities, collision checks, registry movement, and stored AST rewrites.
8
9use super::RoutineRegistry;
10use crate::{
11    ast::{CreateFunction, FunctionBinding, FunctionBody, RenameRoutineStmt},
12    routines::{routine_signature_types, SQLUserFunction},
13    SQLError,
14};
15use std::{collections::BTreeMap, sync::Arc};
16use uqa_core::RelationIdentity;
17
18pub struct RoutineRenameTarget {
19    pub old_name: String,
20    pub new_name: String,
21    pub position: usize,
22    pub binding: FunctionBinding,
23}
24
25pub fn routine_rename_identity(name: &str) -> Result<RelationIdentity, SQLError> {
26    RelationIdentity::from_legacy_name(name)
27        .map_err(|error| SQLError::Internal(format!("decode routine identity `{name}`: {error}")))
28}
29
30pub fn finish_routine_rename_target(
31    stmt: &RenameRoutineStmt,
32    old_name: String,
33    position: usize,
34    definition: &CreateFunction,
35    old_identity: &RelationIdentity,
36    registry: &RoutineRegistry,
37) -> Result<RoutineRenameTarget, SQLError> {
38    let signature = routine_signature_types(definition);
39    let new_name = routine_rename_destination(stmt, old_identity, &signature, registry)?;
40    let object_id = definition.object_id.ok_or_else(|| {
41        SQLError::Internal(format!(
42            "routine `{old_name}` has no catalog object identity"
43        ))
44    })?;
45    Ok(RoutineRenameTarget {
46        old_name: old_name.clone(),
47        new_name,
48        position,
49        binding: FunctionBinding {
50            object_id: Some(object_id),
51            name: old_name,
52            argument_types: signature,
53            builtin: false,
54            dispatch: None,
55            invocation: None,
56            composite_field: None,
57            resolution_error: None,
58        },
59    })
60}
61
62fn routine_rename_destination(
63    stmt: &RenameRoutineStmt,
64    old_identity: &RelationIdentity,
65    signature: &[String],
66    registry: &BTreeMap<String, Vec<Arc<SQLUserFunction>>>,
67) -> Result<String, SQLError> {
68    let (new_schema, new_local_name) =
69        RelationIdentity::parse_reference(&stmt.new_name).map_err(|error| SQLError::Routine {
70            sqlstate: "42602".into(),
71            message: format!("invalid routine name `{}`: {error}", stmt.new_name),
72        })?;
73    if new_schema.is_some() {
74        return Err(SQLError::Routine {
75            sqlstate: "42601".into(),
76            message: "ALTER ROUTINE RENAME TO requires an unqualified new name".into(),
77        });
78    }
79    let new_name = RelationIdentity::new(&old_identity.schema, new_local_name).qualified_name();
80    if registry.get(&new_name).is_some_and(|overloads| {
81        overloads
82            .iter()
83            .any(|function| routine_signature_types(&function.def) == signature)
84    }) {
85        return Err(SQLError::Routine {
86            sqlstate: "42723".into(),
87            message: format!(
88                "function \"{}\" already exists with same argument types",
89                RelationIdentity::from_legacy_name(&new_name)
90                    .map_or_else(|_| new_name.clone(), |identity| identity.name)
91            ),
92        });
93    }
94    Ok(new_name)
95}
96
97pub fn move_routine_registry_entry(
98    mut registry: BTreeMap<String, Vec<Arc<SQLUserFunction>>>,
99    target: &RoutineRenameTarget,
100) -> Result<BTreeMap<String, Vec<Arc<SQLUserFunction>>>, SQLError> {
101    let old_overloads = registry.get_mut(&target.old_name).ok_or_else(|| {
102        SQLError::Internal(format!(
103            "resolved ALTER routine registry entry `{}` disappeared before rename",
104            target.old_name
105        ))
106    })?;
107    let renamed = old_overloads.remove(target.position);
108    if old_overloads.is_empty() {
109        registry.remove(&target.old_name);
110    }
111    let mut renamed_definition = renamed.def.clone();
112    renamed_definition.name.clone_from(&target.new_name);
113    let new_overloads = registry.entry(target.new_name.clone()).or_default();
114    new_overloads.push(Arc::new(SQLUserFunction::new(
115        renamed_definition,
116        renamed.body.clone(),
117    )));
118    new_overloads.sort_by(|left, right| {
119        routine_signature_types(&left.def)
120            .cmp(&routine_signature_types(&right.def))
121            .then_with(|| left.def.is_procedure.cmp(&right.def.is_procedure))
122    });
123    Ok(registry)
124}
125
126pub fn rewrite_routine_owned_dependency_identity(
127    def: &mut CreateFunction,
128    target: &FunctionBinding,
129    new_name: &str,
130) -> Result<bool, SQLError> {
131    let mut changed = false;
132    for default in def
133        .params
134        .iter_mut()
135        .filter_map(|parameter| parameter.default.as_mut())
136    {
137        changed |= crate::catalog::stored_ast::rewrite_expression_routine_identity(
138            default, target, new_name,
139        )?;
140    }
141    if let FunctionBody::Statements(statements) = &mut def.body {
142        for statement in statements {
143            changed |= crate::catalog::stored_ast::rewrite_statement_routine_identity(
144                statement, target, new_name,
145            )?;
146        }
147    }
148    Ok(changed)
149}