uqa_sql/catalog/dependencies/
deletion.rs1use super::{DependencyGraph, DependencyKind, ObjectAddress};
10use crate::SQLError;
11use std::fmt::Write as _;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub struct DeletionFlags(u16);
16
17impl DeletionFlags {
18 pub const ORIGINAL: Self = Self(0x0001);
20 pub const NORMAL: Self = Self(0x0002);
21 pub const AUTO: Self = Self(0x0004);
22 pub const INTERNAL: Self = Self(0x0008);
23 pub const PARTITION: Self = Self(0x0010);
24 pub const EXTENSION: Self = Self(0x0020);
25 pub const REVERSE: Self = Self(0x0040);
27 pub const IS_PART: Self = Self(0x0080);
29 pub const SUBOBJECT: Self = Self(0x0100);
31
32 #[must_use]
33 pub const fn contains(self, other: Self) -> bool {
34 self.0 & other.0 != 0
35 }
36
37 #[must_use]
38 pub const fn union(self, other: Self) -> Self {
39 Self(self.0 | other.0)
40 }
41
42 const fn reached_by(kind: DependencyKind) -> Self {
43 match kind {
44 DependencyKind::Normal => Self::NORMAL,
45 DependencyKind::Auto | DependencyKind::AutoExtension => Self::AUTO,
46 DependencyKind::Internal => Self::INTERNAL,
47 DependencyKind::PartitionPrimary | DependencyKind::PartitionSecondary => {
48 Self::PARTITION
49 }
50 DependencyKind::Extension => Self::EXTENSION,
51 }
52 }
53
54 const fn deleted_silently(self) -> bool {
56 self.contains(Self::AUTO)
57 || self.contains(Self::INTERNAL)
58 || self.contains(Self::PARTITION)
59 || self.contains(Self::EXTENSION)
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct DeletionTarget {
66 pub object: ObjectAddress,
67 pub flags: DeletionFlags,
68 pub dependee: Option<ObjectAddress>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct CascadeNotice {
74 pub message: String,
75 pub detail: Option<String>,
76}
77
78struct StackEntry {
79 object: ObjectAddress,
80 flags: DeletionFlags,
81}
82
83#[derive(Debug, Clone, Default)]
85pub struct DeletionTargets {
86 targets: Vec<DeletionTarget>,
87}
88
89const MAX_REPORTED_DEPENDENTS: usize = 100;
91
92impl DeletionTargets {
93 pub fn collect(
95 graph: &DependencyGraph,
96 originals: &[ObjectAddress],
97 describe: super::Describe<'_>,
98 ) -> Result<Self, SQLError> {
99 let mut search = Self::default();
100 for original in originals {
101 search.find(
102 graph,
103 *original,
104 DeletionFlags::ORIGINAL,
105 &mut Vec::new(),
106 originals,
107 describe,
108 )?;
109 }
110 Ok(search)
111 }
112
113 pub fn targets(&self) -> &[DeletionTarget] {
114 &self.targets
115 }
116
117 fn find(
118 &mut self,
119 graph: &DependencyGraph,
120 object: ObjectAddress,
121 mut flags: DeletionFlags,
122 stack: &mut Vec<StackEntry>,
123 pending: &[ObjectAddress],
124 describe: super::Describe<'_>,
125 ) -> Result<(), SQLError> {
126 if stack_present_add_flags(object, flags, stack) || self.present_add_flags(object, flags) {
127 return Ok(());
128 }
129 let mut owner = None;
130 let mut partition = None;
131 let references = graph.references_of(object).copied().collect::<Vec<_>>();
132 for dependency in references {
133 let other = dependency.referenced;
134 if other.same_object(object) && object.sub_id == 0 {
136 continue;
137 }
138 match dependency.kind {
139 DependencyKind::Normal | DependencyKind::Auto | DependencyKind::AutoExtension => {}
140 DependencyKind::Extension | DependencyKind::Internal => {
141 if stack.is_empty() {
142 if pending.iter().any(|named| {
143 named.same_object(other)
144 && (named.sub_id == other.sub_id || named.sub_id == 0)
145 }) {
146 return Ok(());
148 }
149 if owner.is_none() || dependency.kind == DependencyKind::Extension {
150 owner = Some(other);
151 }
152 continue;
153 }
154 if stack_present_add_flags(other, DeletionFlags::default(), stack) {
155 continue;
156 }
157 self.find(
159 graph,
160 other,
161 DeletionFlags::REVERSE,
162 stack,
163 pending,
164 describe,
165 )?;
166 if !self.present_add_flags(object, flags) {
167 return Err(SQLError::Internal(format!(
168 "deletion of owning object {} failed to delete {}",
169 describe_or_address(describe, other)?,
170 describe_or_address(describe, object)?
171 )));
172 }
173 return Ok(());
174 }
175 DependencyKind::PartitionPrimary => {
176 flags = flags.union(DeletionFlags::IS_PART);
177 partition = Some(other);
178 }
179 DependencyKind::PartitionSecondary => {
180 if !flags.contains(DeletionFlags::IS_PART) {
181 partition = Some(other);
182 }
183 flags = flags.union(DeletionFlags::IS_PART);
184 }
185 }
186 }
187 if let Some(owner) = owner {
188 let required_by = describe_or_address(describe, partition.unwrap_or(owner))?;
189 return Err(SQLError::Diagnostic {
190 sqlstate: "2BP01".into(),
191 message: format!(
192 "cannot drop {} because {required_by} requires it",
193 describe_or_address(describe, object)?
194 ),
195 detail: None,
196 hint: Some(format!("You can drop {required_by} instead.")),
197 });
198 }
199 let mut dependents = graph
200 .dependents_of(object)
201 .filter(|dependency| !(dependency.dependent.same_object(object) && object.sub_id == 0))
202 .map(|dependency| {
203 (
204 dependency.dependent,
205 DeletionFlags::reached_by(dependency.kind),
206 )
207 })
208 .collect::<Vec<_>>();
209 dependents.sort_by(|left, right| left.0.deletion_order(&right.0));
210 stack.push(StackEntry { object, flags });
211 for (dependent, reached) in dependents {
212 self.find(graph, dependent, reached, stack, pending, describe)?;
213 }
214 let entry = stack.pop().expect("the pushed stack entry");
215 let dependee = if entry.flags.contains(DeletionFlags::IS_PART) {
216 partition
217 } else {
218 stack.last().map(|parent| parent.object)
219 };
220 self.targets.push(DeletionTarget {
221 object,
222 flags: entry.flags,
223 dependee,
224 });
225 Ok(())
226 }
227
228 fn present_add_flags(&mut self, object: ObjectAddress, flags: DeletionFlags) -> bool {
230 let mut present = false;
231 for target in self.targets.iter_mut().rev() {
232 if !target.object.same_object(object) {
233 continue;
234 }
235 if target.object.sub_id == object.sub_id {
236 target.flags = target.flags.union(flags);
237 present = true;
238 } else if target.object.sub_id == 0 {
239 present = true;
240 } else if object.sub_id == 0 && flags != DeletionFlags::default() {
241 target.flags = target.flags.union(flags).union(DeletionFlags::SUBOBJECT);
242 }
243 }
244 present
245 }
246
247 pub fn report(
249 &self,
250 cascade: bool,
251 original: Option<ObjectAddress>,
252 describe: super::Describe<'_>,
253 ) -> Result<Option<CascadeNotice>, SQLError> {
254 for target in &self.targets {
255 if target.flags.contains(DeletionFlags::IS_PART)
256 && !target.flags.contains(DeletionFlags::PARTITION)
257 {
258 let required_by = target
259 .dependee
260 .map(|dependee| describe_or_address(describe, dependee))
261 .transpose()?
262 .unwrap_or_default();
263 return Err(SQLError::Diagnostic {
264 sqlstate: "2BP01".into(),
265 message: format!(
266 "cannot drop {} because {required_by} requires it",
267 describe_or_address(describe, target.object)?
268 ),
269 detail: None,
270 hint: Some(format!("You can drop {required_by} instead.")),
271 });
272 }
273 }
274 let mut lines = Vec::new();
275 let mut unreported = 0_usize;
276 let mut allowed = true;
277 for target in self.targets.iter().rev() {
278 if target.flags.contains(DeletionFlags::ORIGINAL)
279 || target.flags.contains(DeletionFlags::SUBOBJECT)
280 {
281 continue;
282 }
283 let Some(description) = describe(target.object)? else {
284 continue;
285 };
286 if target.flags.deleted_silently() {
287 continue;
288 }
289 let line = if cascade {
290 Some(format!("drop cascades to {description}"))
291 } else {
292 allowed = false;
293 match target.dependee {
294 Some(dependee) => describe(dependee)?
295 .map(|dependee| format!("{description} depends on {dependee}")),
296 None => None,
297 }
298 };
299 match line {
300 Some(line) if lines.len() < MAX_REPORTED_DEPENDENTS => lines.push(line),
301 _ => unreported += 1,
302 }
303 }
304 let reported = lines.len();
305 let mut detail = lines.join("\n");
306 if unreported > 0 {
307 write!(
308 detail,
309 "\nand {unreported} other object{} (see server log for list)",
310 if unreported == 1 { "" } else { "s" }
311 )
312 .expect("writing to a String cannot fail");
313 }
314 if !allowed {
315 return Err(SQLError::Diagnostic {
316 sqlstate: "2BP01".into(),
317 message: match original {
318 Some(original) => format!(
319 "cannot drop {} because other objects depend on it",
320 describe_or_address(describe, original)?
321 ),
322 None => {
323 "cannot drop desired object(s) because other objects depend on them".into()
324 }
325 },
326 detail: Some(detail),
327 hint: Some("Use DROP ... CASCADE to drop the dependent objects too.".into()),
328 });
329 }
330 Ok(match reported {
331 0 => None,
332 1 => Some(CascadeNotice {
333 message: detail,
334 detail: None,
335 }),
336 _ => {
337 let total = reported + unreported;
338 Some(CascadeNotice {
339 message: format!(
340 "drop cascades to {total} other object{}",
341 if total == 1 { "" } else { "s" }
342 ),
343 detail: Some(detail),
344 })
345 }
346 })
347 }
348}
349
350fn stack_present_add_flags(
352 object: ObjectAddress,
353 flags: DeletionFlags,
354 stack: &mut [StackEntry],
355) -> bool {
356 let mut present = false;
357 for entry in stack.iter_mut() {
358 if !entry.object.same_object(object) {
359 continue;
360 }
361 if entry.object.sub_id == object.sub_id {
362 entry.flags = entry.flags.union(flags);
363 present = true;
364 } else if entry.object.sub_id == 0 {
365 present = true;
366 } else if object.sub_id == 0 && flags != DeletionFlags::default() {
367 entry.flags = entry.flags.union(flags).union(DeletionFlags::SUBOBJECT);
368 }
369 }
370 present
371}
372
373fn describe_or_address(
374 describe: super::Describe<'_>,
375 object: ObjectAddress,
376) -> Result<String, SQLError> {
377 Ok(describe(object)?.unwrap_or_else(|| {
378 format!(
379 "object {} of class {} column {}",
380 object.object_id, object.class_id, object.sub_id
381 )
382 }))
383}
384
385#[cfg(test)]
386mod tests;