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<(&'static str, String)>,
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(("NOTICE", format!("drop cascades to {object}"))),
62        objects => notices.push((
63            "NOTICE",
64            format!("drop cascades to {} other objects", objects.len()),
65        )),
66    }
67}
68
69pub fn routine_schema_relation_kind(foreign: bool) -> &'static str {
70    if foreign {
71        "foreign table"
72    } else {
73        "table"
74    }
75}
76
77pub fn ensure_no_function_dependencies(
78    target: &RoutineDropTarget,
79    dependents: &RoutineObjectDependents,
80) -> Result<(), SQLError> {
81    if dependents.columns.is_empty()
82        && dependents.defaults.is_empty()
83        && dependents.checks.is_empty()
84        && dependents.views.is_empty()
85        && dependents.triggers.is_empty()
86        && dependents.rules.is_empty()
87        && dependents.indexes.is_empty()
88    {
89        return Ok(());
90    }
91    let mut dependency_kinds = Vec::new();
92    if !dependents.columns.is_empty() {
93        dependency_kinds.push(format!(
94            "generated column(s) `{}`",
95            dependents
96                .columns
97                .iter()
98                .map(|(table, column, _)| format!("{table}.{column}"))
99                .collect::<Vec<_>>()
100                .join("`, `")
101        ));
102    }
103    if !dependents.defaults.is_empty() {
104        dependency_kinds.push(format!(
105            "default value(s) `{}`",
106            dependents
107                .defaults
108                .iter()
109                .map(|(table, column, _)| format!("{table}.{column}"))
110                .collect::<Vec<_>>()
111                .join("`, `")
112        ));
113    }
114    if !dependents.checks.is_empty() {
115        dependency_kinds.push(format!(
116            "CHECK constraint(s) `{}`",
117            dependents
118                .checks
119                .iter()
120                .map(|(table, constraint, _)| format!("{constraint} on {table}"))
121                .collect::<Vec<_>>()
122                .join("`, `")
123        ));
124    }
125    if !dependents.views.is_empty() {
126        dependency_kinds.push(format!("view(s) `{}`", dependents.views.join("`, `")));
127    }
128    if !dependents.triggers.is_empty() {
129        dependency_kinds.push(format!(
130            "trigger(s) `{}`",
131            dependents
132                .triggers
133                .iter()
134                .map(|(table, trigger)| format!("{trigger} on {table}"))
135                .collect::<Vec<_>>()
136                .join("`, `")
137        ));
138    }
139    if !dependents.indexes.is_empty() {
140        dependency_kinds.push(format!(
141            "indexes {}",
142            dependents
143                .indexes
144                .iter()
145                .map(uqa_core::RelationIdentity::qualified_name)
146                .collect::<Vec<_>>()
147                .join(", ")
148        ));
149    }
150    if !dependents.rules.is_empty() {
151        dependency_kinds.push(format!(
152            "rule(s) `{}`",
153            dependents
154                .rules
155                .iter()
156                .map(|(table, rule)| format!("{rule} on {table}"))
157                .collect::<Vec<_>>()
158                .join("`, `")
159        ));
160    }
161    Err(SQLError::Routine {
162        sqlstate: "2BP01".into(),
163        message: format!(
164            "cannot drop function {} because {} depend on it",
165            target.label(),
166            dependency_kinds.join(" and ")
167        ),
168    })
169}