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            resolution_error: None,
57        },
58    })
59}
60
61fn routine_rename_destination(
62    stmt: &RenameRoutineStmt,
63    old_identity: &RelationIdentity,
64    signature: &[String],
65    registry: &BTreeMap<String, Vec<Arc<SQLUserFunction>>>,
66) -> Result<String, SQLError> {
67    let (new_schema, new_local_name) =
68        RelationIdentity::parse_reference(&stmt.new_name).map_err(|error| SQLError::Routine {
69            sqlstate: "42602".into(),
70            message: format!("invalid routine name `{}`: {error}", stmt.new_name),
71        })?;
72    if new_schema.is_some() {
73        return Err(SQLError::Routine {
74            sqlstate: "42601".into(),
75            message: "ALTER ROUTINE RENAME TO requires an unqualified new name".into(),
76        });
77    }
78    let new_name = RelationIdentity::new(&old_identity.schema, new_local_name).qualified_name();
79    if registry.get(&new_name).is_some_and(|overloads| {
80        overloads
81            .iter()
82            .any(|function| routine_signature_types(&function.def) == signature)
83    }) {
84        return Err(SQLError::Routine {
85            sqlstate: "42723".into(),
86            message: format!(
87                "function \"{}\" already exists with same argument types",
88                RelationIdentity::from_legacy_name(&new_name)
89                    .map_or_else(|_| new_name.clone(), |identity| identity.name)
90            ),
91        });
92    }
93    Ok(new_name)
94}
95
96pub fn move_routine_registry_entry(
97    mut registry: BTreeMap<String, Vec<Arc<SQLUserFunction>>>,
98    target: &RoutineRenameTarget,
99) -> Result<BTreeMap<String, Vec<Arc<SQLUserFunction>>>, SQLError> {
100    let old_overloads = registry.get_mut(&target.old_name).ok_or_else(|| {
101        SQLError::Internal(format!(
102            "resolved ALTER routine registry entry `{}` disappeared before rename",
103            target.old_name
104        ))
105    })?;
106    let renamed = old_overloads.remove(target.position);
107    if old_overloads.is_empty() {
108        registry.remove(&target.old_name);
109    }
110    let mut renamed_definition = renamed.def.clone();
111    renamed_definition.name.clone_from(&target.new_name);
112    let new_overloads = registry.entry(target.new_name.clone()).or_default();
113    new_overloads.push(Arc::new(SQLUserFunction {
114        def: renamed_definition,
115        compiled: renamed.compiled.clone(),
116    }));
117    new_overloads.sort_by(|left, right| {
118        routine_signature_types(&left.def)
119            .cmp(&routine_signature_types(&right.def))
120            .then_with(|| left.def.is_procedure.cmp(&right.def.is_procedure))
121    });
122    Ok(registry)
123}
124
125pub fn rewrite_routine_owned_dependency_identity(
126    def: &mut CreateFunction,
127    target: &FunctionBinding,
128    new_name: &str,
129) -> Result<bool, SQLError> {
130    let mut changed = false;
131    for default in def
132        .params
133        .iter_mut()
134        .filter_map(|parameter| parameter.default.as_mut())
135    {
136        changed |= crate::catalog::stored_ast::rewrite_expression_routine_identity(
137            default, target, new_name,
138        )?;
139    }
140    if let FunctionBody::Statements(statements) = &mut def.body {
141        for statement in statements {
142            changed |= crate::catalog::stored_ast::rewrite_statement_routine_identity(
143                statement, target, new_name,
144            )?;
145        }
146    }
147    Ok(changed)
148}