Skip to main content

uqa_sql/routines/
lifecycle.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Routine removal identities, dependency results, and declaration diagnostics.
8
9pub mod binding;
10pub mod dependencies;
11pub mod lookup;
12pub mod names;
13pub mod relations;
14pub mod rename;
15pub mod restoration;
16pub mod rewrites;
17
18use super::SQLUserFunction;
19use crate::{
20    ast::{AlterRoutineKind, CreateFunction, FunctionBinding},
21    SQLError,
22};
23use std::{
24    collections::{BTreeMap, BTreeSet},
25    sync::Arc,
26};
27
28pub type RoutineRegistry = BTreeMap<String, Vec<Arc<SQLUserFunction>>>;
29
30#[derive(Default)]
31pub struct RoutineDropResolution {
32    pub targets: Vec<RoutineDropTarget>,
33    pub seen_targets: BTreeSet<RoutineDropTarget>,
34    pub notices: Vec<crate::SQLNotice>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
38pub struct RoutineDropTarget {
39    pub object_id: Option<[u8; 16]>,
40    pub name: String,
41    pub argument_types: Vec<String>,
42    pub is_procedure: bool,
43}
44
45impl RoutineDropTarget {
46    pub fn kind(&self) -> &'static str {
47        if self.is_procedure {
48            "procedure"
49        } else {
50            "function"
51        }
52    }
53
54    pub fn label(&self) -> String {
55        routine_signature_label(&self.name, &self.argument_types)
56    }
57
58    /// Whether `function`, an overload registered under the target's name, is the routine the target resolved: the same kind and signature, and the same identity when the target has one.
59    pub fn names(&self, function: &SQLUserFunction) -> bool {
60        function.def.is_procedure == self.is_procedure
61            && super::routine_signature_types(&function.def) == self.argument_types
62            && (self.object_id.is_none() || function.def.object_id == self.object_id)
63    }
64
65    pub fn binding(&self) -> FunctionBinding {
66        FunctionBinding {
67            object_id: self.object_id,
68            name: self.name.clone(),
69            argument_types: self.argument_types.clone(),
70            builtin: false,
71            dispatch: None,
72            invocation: None,
73            composite_field: None,
74            resolution_error: None,
75        }
76    }
77}
78
79pub fn routine_signature_label(name: &str, types: &[String]) -> String {
80    let display_types = types
81        .iter()
82        .map(|type_name| {
83            crate::ast::ColumnType::from_sql_name(type_name)
84                .map_or_else(|_| type_name.clone(), |column_type| column_type.sql_name())
85        })
86        .collect::<Vec<_>>();
87    format!("{name}({})", display_types.join(", "))
88}
89
90/// `func_signature_string`: the routine name as the command wrote it and each argument type as `format_type_be` spells it.
91pub fn routine_signature_display(
92    catalog: &dyn names::RoutineNameCatalog,
93    name: &str,
94    types: &[String],
95) -> String {
96    let types = types
97        .iter()
98        .map(|type_name| catalog.routine_type_display(type_name))
99        .collect::<Vec<_>>();
100    format!("{name}({})", types.join(", "))
101}
102
103/// `LookupFuncWithArgs`: the routine an argument list selects is not of the kind the command names.
104pub fn wrong_routine_kind_error(signature: &str, expected_kind: &str) -> SQLError {
105    SQLError::Routine {
106        sqlstate: "42809".into(),
107        message: format!("{signature} is not a {expected_kind}"),
108    }
109}
110
111/// `LookupFuncWithArgs`: a name without an argument list selects more than one routine of the command's kind.
112pub fn ambiguous_routine_error(kind: &str, name: &str) -> SQLError {
113    SQLError::Diagnostic {
114        sqlstate: "42725".into(),
115        message: format!("{kind} name \"{name}\" is not unique"),
116        detail: None,
117        hint: Some(format!(
118            "Specify the argument list to select the {kind} unambiguously."
119        )),
120    }
121}
122
123pub fn alter_routine_kind_name(kind: AlterRoutineKind) -> &'static str {
124    match kind {
125        AlterRoutineKind::Function => "function",
126        AlterRoutineKind::Procedure => "procedure",
127        AlterRoutineKind::Routine => "routine",
128    }
129}
130
131pub fn alter_routine_kind_matches(kind: AlterRoutineKind, def: &CreateFunction) -> bool {
132    match kind {
133        AlterRoutineKind::Function => !def.is_procedure,
134        AlterRoutineKind::Procedure => def.is_procedure,
135        AlterRoutineKind::Routine => true,
136    }
137}
138
139/// `must be owner of <kind> <name>`, which `aclcheck_error` reports for a routine whose owner the current user is not; each command names the routine its own way.
140pub fn require_routine_ownership(
141    kind: &str,
142    name: &str,
143    current_user_has_owner_privileges: bool,
144) -> Result<(), SQLError> {
145    if current_user_has_owner_privileges {
146        Ok(())
147    } else {
148        Err(SQLError::Routine {
149            sqlstate: "42501".into(),
150            message: format!("must be owner of {kind} {name}"),
151        })
152    }
153}