uqa_sql/catalog/roles/
dependencies.rs1use crate::{
10 catalog::{
11 security::{BoundSchemaSecurity, BoundSequenceSecurity},
12 view::StoredViewKind,
13 },
14 SQLError,
15};
16use std::collections::BTreeMap;
17use uqa_core::RelationIdentity;
18pub mod context;
19pub mod temporary;
20use context::RoleDependencyCatalog;
21
22pub fn ensure_roles_have_no_object_dependencies(
23 catalog: &dyn RoleDependencyCatalog,
24 names: &[String],
25 roles: &BTreeMap<String, super::RoleDefinition>,
26) -> Result<(), SQLError> {
27 let database_security = catalog.database();
28 for name in names {
29 if roles
30 .get(name)
31 .is_some_and(|role| database_security.depends_on(role.identity()))
32 {
33 return Err(SQLError::Routine {
34 sqlstate: "2BP01".into(),
35 message: format!(
36 "role \"{name}\" cannot be dropped because some objects depend on it: database uqa"
37 ),
38 });
39 }
40 }
41 drop(database_security);
42
43 let schema_security = catalog.schemas();
44 for name in names {
45 if let Some(schema) = roles
46 .get(name)
47 .and_then(|role| dependent_schema_for_role(&schema_security, role.identity()))
48 {
49 return Err(SQLError::Routine {
50 sqlstate: "2BP01".into(),
51 message: format!(
52 "role \"{name}\" cannot be dropped because some objects depend on it: schema {schema}"
53 ),
54 });
55 }
56 }
57 drop(schema_security);
58
59 ensure_roles_have_no_table_dependencies(catalog, names, roles)?;
60
61 ensure_roles_have_no_system_relation_dependencies(catalog, names, roles)?;
62
63 let views = catalog.views();
64 for name in names {
65 if let Some((relation, view)) = views.iter().find(|(_, view)| {
66 roles
67 .get(name)
68 .is_some_and(|role| view.security.depends_on(role.identity()))
69 }) {
70 let kind = match view.kind {
71 StoredViewKind::View => "view",
72 StoredViewKind::Materialized => "materialized view",
73 };
74 return Err(SQLError::Routine {
75 sqlstate: "2BP01".into(),
76 message: format!(
77 "role \"{name}\" cannot be dropped because some objects depend on it: {kind} {}",
78 relation.qualified_name()
79 ),
80 });
81 }
82 }
83 drop(views);
84
85 ensure_roles_have_no_foreign_table_dependencies(catalog, names, roles)?;
86
87 let sequence_security = catalog.sequences();
88 for name in names {
89 if let Some(relation) = roles
90 .get(name)
91 .and_then(|role| dependent_sequence_for_role(&sequence_security, role.identity()))
92 {
93 return Err(SQLError::Routine {
94 sqlstate: "2BP01".into(),
95 message: format!(
96 "role \"{name}\" cannot be dropped because some objects depend on it: sequence {}",
97 relation.qualified_name()
98 ),
99 });
100 }
101 }
102 drop(sequence_security);
103
104 ensure_roles_have_no_domain_dependencies(catalog, names, roles)?;
105
106 let routines = catalog.routines();
107 for name in names {
108 if let Some(dependent) = routines.values().flatten().find_map(|function| {
109 let identity = roles.get(name)?.identity();
110 let owns = function.def.owner == Some(identity);
111 let has_acl = function.def.execute_acl.as_ref().is_some_and(|acl| {
112 acl.iter()
113 .any(|entry| entry.role == Some(identity) || entry.grantor == identity)
114 });
115 (owns || has_acl).then(|| format!("routine {}", function.def.name))
116 }) {
117 return Err(SQLError::Routine {
118 sqlstate: "2BP01".into(),
119 message: format!("role \"{name}\" cannot be dropped because some objects depend on it: {dependent}"),
120 });
121 }
122 }
123 Ok(())
124}
125
126fn ensure_roles_have_no_domain_dependencies(
127 catalog: &dyn RoleDependencyCatalog,
128 names: &[String],
129 roles: &BTreeMap<String, super::RoleDefinition>,
130) -> Result<(), SQLError> {
131 let domains = catalog.domains();
132 for name in names {
133 if let Some(domain) = domains.values().find(|domain| {
134 roles
135 .get(name)
136 .is_some_and(|role| domain.owner == role.identity())
137 }) {
138 return Err(SQLError::Routine {
139 sqlstate: "2BP01".into(),
140 message: format!(
141 "role \"{name}\" cannot be dropped because some objects depend on it: type {}",
142 domain.identity.qualified_name()
143 ),
144 });
145 }
146 }
147 drop(domains);
148 Ok(())
149}
150
151fn ensure_roles_have_no_table_dependencies(
152 catalog: &dyn RoleDependencyCatalog,
153 names: &[String],
154 roles: &BTreeMap<String, super::RoleDefinition>,
155) -> Result<(), SQLError> {
156 let tables = catalog.tables();
157 for name in names {
158 if let Some(relation) = tables.iter().find_map(|(relation, table)| {
159 roles
160 .get(name)
161 .is_some_and(|role| table.security().depends_on(role.identity()))
162 .then_some(relation)
163 }) {
164 return Err(SQLError::Routine {
165 sqlstate: "2BP01".into(),
166 message: format!(
167 "role \"{name}\" cannot be dropped because some objects depend on it: table {}",
168 relation.qualified_name()
169 ),
170 });
171 }
172 }
173 drop(tables);
174
175 Ok(())
176}
177
178fn ensure_roles_have_no_system_relation_dependencies(
179 catalog: &dyn RoleDependencyCatalog,
180 names: &[String],
181 roles: &BTreeMap<String, super::RoleDefinition>,
182) -> Result<(), SQLError> {
183 let system_relations = catalog.system_relation_securities();
184 for name in names {
185 if let Some((relation, _)) = system_relations.iter().find(|(identity, entry)| {
186 crate::catalog::SystemRelation::at(&identity.schema, &identity.name).is_some_and(
187 |relation| {
188 roles
189 .get(name)
190 .is_some_and(|role| entry.security(relation).depends_on(role.identity()))
191 },
192 )
193 }) {
194 return Err(SQLError::Routine {
195 sqlstate: "2BP01".into(),
196 message: format!(
197 "role \"{name}\" cannot be dropped because some objects depend on it: table {}",
198 relation.qualified_name()
199 ),
200 });
201 }
202 }
203 drop(system_relations);
204
205 Ok(())
206}
207
208fn ensure_roles_have_no_foreign_table_dependencies(
209 catalog: &dyn RoleDependencyCatalog,
210 names: &[String],
211 roles: &BTreeMap<String, super::RoleDefinition>,
212) -> Result<(), SQLError> {
213 let foreign_tables = catalog.foreign_tables();
214 for name in names {
215 if let Some((relation, _)) = foreign_tables.iter().find(|(_, security)| {
216 roles
217 .get(name)
218 .is_some_and(|role| security.depends_on(role.identity()))
219 }) {
220 return Err(SQLError::Routine {
221 sqlstate: "2BP01".into(),
222 message: format!(
223 "role \"{name}\" cannot be dropped because some objects depend on it: foreign table {}",
224 relation.qualified_name()
225 ),
226 });
227 }
228 }
229 Ok(())
230}
231
232fn dependent_sequence_for_role(
233 sequences: &BTreeMap<RelationIdentity, BoundSequenceSecurity>,
234 role: super::RoleIdentity,
235) -> Option<&RelationIdentity> {
236 sequences
237 .iter()
238 .find_map(|(relation, security)| security.depends_on(role).then_some(relation))
239}
240
241fn dependent_schema_for_role(
242 schemas: &BTreeMap<String, BoundSchemaSecurity>,
243 role: super::RoleIdentity,
244) -> Option<&String> {
245 schemas
246 .iter()
247 .find_map(|(name, security)| security.depends_on(role).then_some(name))
248}
249
250#[cfg(test)]
251mod tests;