Skip to main content

uqa_sql/routines/lifecycle/
diagnostics.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Stable dependency diagnostics and cascade notices for routine removal.
8
9use super::{RoutineDropTarget, RoutineObjectDependents};
10use crate::SQLError;
11
12pub fn append_routine_cascade_notice(
13    notices: &mut Vec<crate::SQLNotice>,
14    cascaded_routines: &[RoutineDropTarget],
15    dependents: &RoutineObjectDependents,
16) {
17    let mut cascaded = cascaded_routines
18        .iter()
19        .map(|target| format!("{} {}", target.kind(), target.label()))
20        .collect::<Vec<_>>();
21    cascaded.extend(dependents.columns.iter().map(|(table, column, foreign)| {
22        format!(
23            "column {column} of {} {table}",
24            routine_schema_relation_kind(*foreign)
25        )
26    }));
27    cascaded.extend(dependents.defaults.iter().map(|(table, column, foreign)| {
28        format!(
29            "default value for column {column} of {} {table}",
30            routine_schema_relation_kind(*foreign)
31        )
32    }));
33    cascaded.extend(
34        dependents
35            .checks
36            .iter()
37            .map(|(table, constraint, foreign)| {
38                format!(
39                    "constraint {constraint} on {} {table}",
40                    routine_schema_relation_kind(*foreign)
41                )
42            }),
43    );
44    cascaded.extend(dependents.views.iter().map(|view| format!("view {view}")));
45    cascaded.extend(
46        dependents
47            .triggers
48            .iter()
49            .map(|(table, trigger)| format!("trigger {trigger} on table {table}")),
50    );
51    cascaded.extend(
52        dependents
53            .rules
54            .iter()
55            .map(|(table, rule)| format!("rule {rule} on table {table}")),
56    );
57    cascaded.sort();
58    cascaded.dedup();
59    match cascaded.as_slice() {
60        [] => {}
61        [object] => notices.push(crate::SQLNotice::notice(format!(
62            "drop cascades to {object}"
63        ))),
64        objects => notices.push(crate::SQLNotice::notice(format!(
65            "drop cascades to {} other objects",
66            objects.len()
67        ))),
68    }
69}
70
71pub fn routine_schema_relation_kind(foreign: bool) -> &'static str {
72    if foreign {
73        "foreign table"
74    } else {
75        "table"
76    }
77}
78
79pub fn ensure_no_function_dependencies(
80    target: &RoutineDropTarget,
81    dependents: &RoutineObjectDependents,
82) -> Result<(), SQLError> {
83    if dependents.columns.is_empty()
84        && dependents.defaults.is_empty()
85        && dependents.checks.is_empty()
86        && dependents.views.is_empty()
87        && dependents.triggers.is_empty()
88        && dependents.rules.is_empty()
89        && dependents.indexes.is_empty()
90    {
91        return Ok(());
92    }
93    let mut dependency_kinds = Vec::new();
94    if !dependents.columns.is_empty() {
95        dependency_kinds.push(format!(
96            "generated column(s) `{}`",
97            dependents
98                .columns
99                .iter()
100                .map(|(table, column, _)| format!("{table}.{column}"))
101                .collect::<Vec<_>>()
102                .join("`, `")
103        ));
104    }
105    if !dependents.defaults.is_empty() {
106        dependency_kinds.push(format!(
107            "default value(s) `{}`",
108            dependents
109                .defaults
110                .iter()
111                .map(|(table, column, _)| format!("{table}.{column}"))
112                .collect::<Vec<_>>()
113                .join("`, `")
114        ));
115    }
116    if !dependents.checks.is_empty() {
117        dependency_kinds.push(format!(
118            "CHECK constraint(s) `{}`",
119            dependents
120                .checks
121                .iter()
122                .map(|(table, constraint, _)| format!("{constraint} on {table}"))
123                .collect::<Vec<_>>()
124                .join("`, `")
125        ));
126    }
127    if !dependents.views.is_empty() {
128        dependency_kinds.push(format!("view(s) `{}`", dependents.views.join("`, `")));
129    }
130    if !dependents.triggers.is_empty() {
131        dependency_kinds.push(format!(
132            "trigger(s) `{}`",
133            dependents
134                .triggers
135                .iter()
136                .map(|(table, trigger)| format!("{trigger} on {table}"))
137                .collect::<Vec<_>>()
138                .join("`, `")
139        ));
140    }
141    if !dependents.indexes.is_empty() {
142        dependency_kinds.push(format!(
143            "indexes {}",
144            dependents
145                .indexes
146                .iter()
147                .map(uqa_core::RelationIdentity::qualified_name)
148                .collect::<Vec<_>>()
149                .join(", ")
150        ));
151    }
152    if !dependents.rules.is_empty() {
153        dependency_kinds.push(format!(
154            "rule(s) `{}`",
155            dependents
156                .rules
157                .iter()
158                .map(|(table, rule)| format!("{rule} on {table}"))
159                .collect::<Vec<_>>()
160                .join("`, `")
161        ));
162    }
163    Err(SQLError::Routine {
164        sqlstate: "2BP01".into(),
165        message: format!(
166            "cannot drop function {} because {} depend on it",
167            target.label(),
168            dependency_kinds.join(" and ")
169        ),
170    })
171}