Skip to main content

rupg_catalog/
drop.rs

1//! The removal of objects: the walk of `findDependentObjects` and the work of `deleteOneObject` and `doDeletion` in `dependency.c`. The analyzer finds the objects that a statement names, writes the messages of `reportDependentObjects` with the names of the objects, and then calls [`Catalog::remove`].
2
3use std::cmp::Ordering;
4use std::sync::Arc;
5
6use rupg_common::{Error, Result};
7
8use super::{
9    Catalog, Column, ConKind, DepKind, FIRST_NORMAL_OID, ObjRef, PG_AMOP, PG_AMPROC, PG_ATTRDEF,
10    PG_CAST, PG_CLASS, PG_CONSTRAINT, PG_DEFAULT_ACL, PG_NAMESPACE, PG_OPCLASS, PG_OPERATOR,
11    PG_OPFAMILY, PG_PROC, PG_REWRITE, PG_STATISTIC_EXT, PG_TRIGGER, PG_TS_CONFIG, PG_TS_DICT,
12    PG_TS_PARSER, PG_TS_TEMPLATE, PG_TYPE, is_pinned,
13};
14
15/// The reasons that the walk reached an object, the bits `DEPFLAG_*` of `dependency.c`.
16pub mod flag {
17    /// `DEPFLAG_ORIGINAL`: the statement names the object.
18    pub const ORIGINAL: u32 = 0x0001;
19    /// `DEPFLAG_NORMAL`: a normal dependency reached the object.
20    pub const NORMAL: u32 = 0x0002;
21    /// `DEPFLAG_AUTO`: an automatic dependency reached the object.
22    pub const AUTO: u32 = 0x0004;
23    /// `DEPFLAG_INTERNAL`: an internal dependency reached the object.
24    pub const INTERNAL: u32 = 0x0008;
25    /// `DEPFLAG_EXTENSION`: the object is a member of an extension that the walk removes.
26    pub const EXTENSION: u32 = 0x0010;
27    /// `DEPFLAG_PARTITION`: a partition dependency reached the object.
28    pub const PARTITION: u32 = 0x0020;
29    /// `DEPFLAG_REVERSE`: the walk came to the object from a part of it.
30    pub const REVERSE: u32 = 0x0040;
31    /// `DEPFLAG_IS_PART`: the object has a partition dependency, so the walk must also remove the object that it is a partition of.
32    pub const IS_PART: u32 = 0x0080;
33    /// `DEPFLAG_SUBOBJECT`: the object is a column of a relation that the walk also removes.
34    pub const SUBOBJECT: u32 = 0x0100;
35}
36
37/// An object that a removal takes away, `ObjectAddressExtra` with its address.
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub struct Target {
40    /// The object.
41    pub object: ObjRef,
42    /// The bits of [`flag`].
43    pub flags: u32,
44    /// The object that the walk came from, or `None` for an object that the statement names. For an object with [`flag::IS_PART`], the object that it is a partition of.
45    pub dependee: Option<ObjRef>,
46}
47
48/// The reason that a removal cannot start.
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum Refusal {
51    /// The database system needs the object.
52    Pinned(ObjRef),
53    /// The object is a part of `owner`, and the statement does not remove `owner`.
54    Owned {
55        /// The object.
56        object: ObjRef,
57        /// The object that it is a part of, or the object that it is a partition of.
58        owner: ObjRef,
59    },
60}
61
62/// One level of the walk, `ObjectAddressStack`.
63struct Frame {
64    object: ObjRef,
65    flags: u32,
66}
67
68/// `object_address_comparator`: the highest OID first, then the catalog, then the column number as an unsigned number.
69fn compare(a: ObjRef, b: ObjRef) -> Ordering {
70    b.oid
71        .cmp(&a.oid)
72        .then(a.class.cmp(&b.class))
73        .then(a.sub.cast_unsigned().cmp(&b.sub.cast_unsigned()))
74}
75
76/// True when `found` is the object or, for a whole object, one of its columns.
77fn covers(object: ObjRef, found: ObjRef) -> bool {
78    found.class == object.class
79        && found.oid == object.oid
80        && (object.sub == 0 || found.sub == object.sub)
81}
82
83/// True when a row of `pg_depend` joins a whole object to one of its own columns. The walk skips these rows.
84fn is_self(object: ObjRef, other: ObjRef) -> bool {
85    object.sub == 0 && other.class == object.class && other.oid == object.oid
86}
87
88/// `object_address_present_add_flags`, for the list of targets or for the stack of the walk. The result is true when the list has the object or its whole relation. When the list has a column of the object, the column gets the flags and `SUBOBJECT`.
89fn present_add<'a>(
90    object: ObjRef,
91    flags: u32,
92    entries: impl DoubleEndedIterator<Item = (ObjRef, &'a mut u32)>,
93) -> bool {
94    let mut result = false;
95    for (found, found_flags) in entries.rev() {
96        if found.class != object.class || found.oid != object.oid {
97            continue;
98        }
99        if found.sub == object.sub {
100            *found_flags |= flags;
101            result = true;
102        } else if found.sub == 0 {
103            result = true;
104        } else if object.sub == 0 && flags != 0 {
105            *found_flags |= flags | flag::SUBOBJECT;
106        }
107    }
108    result
109}
110
111/// `object_address_present`: true when `objects` has the object or its whole relation.
112fn present(object: ObjRef, objects: &[ObjRef]) -> bool {
113    objects.iter().any(|o| {
114        o.class == object.class && o.oid == object.oid && (o.sub == object.sub || o.sub == 0)
115    })
116}
117
118impl Catalog {
119    /// `performMultipleDeletions` up to the report: the objects that a removal of `objects` takes away, in the order of the removal. An object comes after the objects that depend on it.
120    ///
121    /// # Errors
122    ///
123    /// A [`Refusal`] for a pinned object, and for a part of an object that `objects` does not have.
124    pub fn deletions(&self, objects: &[ObjRef]) -> std::result::Result<Vec<Target>, Refusal> {
125        let mut targets = Vec::new();
126        for &object in objects {
127            self.find_dependents(object, flag::ORIGINAL, &mut Vec::new(), &mut targets, objects)?;
128        }
129        Ok(targets)
130    }
131
132    /// `findDependentObjects`: adds the object to `targets` after the objects that depend on it. A partition dependency of the object on a primary object, else on a secondary object, gives the object [`flag::IS_PART`] and the object that it is a partition of.
133    fn find_dependents(
134        &self,
135        object: ObjRef,
136        mut flags: u32,
137        stack: &mut Vec<Frame>,
138        targets: &mut Vec<Target>,
139        pending: &[ObjRef],
140    ) -> std::result::Result<(), Refusal> {
141        if present_add(object, flags, stack.iter_mut().map(|f| (f.object, &mut f.flags))) {
142            return Ok(());
143        }
144        if present_add(object, flags, targets.iter_mut().map(|t| (t.object, &mut t.flags))) {
145            return Ok(());
146        }
147        if is_pinned(object) {
148            return Err(Refusal::Pinned(object));
149        }
150        let mut owners = Vec::new();
151        let mut partition = None;
152        for d in self.depends.iter().filter(|d| covers(object, d.object)) {
153            if is_self(object, d.referenced) {
154                continue;
155            }
156            match d.kind {
157                DepKind::Internal | DepKind::Extension => owners.push(d.referenced),
158                DepKind::PartitionPri => {
159                    flags |= flag::IS_PART;
160                    partition = Some(d.referenced);
161                }
162                DepKind::PartitionSec => {
163                    if flags & flag::IS_PART == 0 {
164                        partition = Some(d.referenced);
165                    }
166                    flags |= flag::IS_PART;
167                }
168                DepKind::Normal | DepKind::Auto => {}
169            }
170        }
171        let mut owner = None;
172        for other in owners {
173            if stack.is_empty() {
174                if present(other, pending) {
175                    return Ok(());
176                }
177                owner.get_or_insert(other);
178                continue;
179            }
180            if present_add(other, 0, stack.iter_mut().map(|f| (f.object, &mut f.flags))) {
181                continue;
182            }
183            self.find_dependents(other, flag::REVERSE, stack, targets, pending)?;
184            present_add(object, flags, targets.iter_mut().map(|t| (t.object, &mut t.flags)));
185            return Ok(());
186        }
187        if let Some(owner) = owner {
188            return Err(Refusal::Owned { object, owner: partition.unwrap_or(owner) });
189        }
190        let mut dependents: Vec<(ObjRef, u32)> = self
191            .depends
192            .iter()
193            .filter(|d| covers(object, d.referenced) && !is_self(object, d.object))
194            .map(|d| {
195                let flags = match d.kind {
196                    DepKind::Normal => flag::NORMAL,
197                    DepKind::Auto => flag::AUTO,
198                    DepKind::Internal => flag::INTERNAL,
199                    DepKind::Extension => flag::EXTENSION,
200                    DepKind::PartitionPri | DepKind::PartitionSec => flag::PARTITION,
201                };
202                (d.object, flags)
203            })
204            .collect();
205        dependents.sort_by(|a, b| compare(a.0, b.0));
206        stack.push(Frame { object, flags });
207        for (dependent, flags) in dependents {
208            self.find_dependents(dependent, flags, stack, targets, pending)?;
209        }
210        let frame = stack.pop().unwrap_or(Frame { object, flags });
211        let dependee = if frame.flags & flag::IS_PART != 0 {
212            partition
213        } else {
214            stack.last().map(|f| f.object)
215        };
216        targets.push(Target { object, flags: frame.flags, dependee });
217        Ok(())
218    }
219
220    /// `deleteObjectsInList`: removes the targets in order, each with its rows of `pg_depend` and its comments, as `deleteOneObject` does. After an error the caller discards the catalog.
221    ///
222    /// # Errors
223    ///
224    /// An internal error for an object of a class that the catalog cannot remove.
225    pub fn remove(&mut self, targets: &[Target]) -> Result<()> {
226        for target in targets {
227            let object = target.object;
228            self.remove_one(object)?;
229            self.depends.retain(|d| !covers(object, d.object));
230            if self.descriptions.keys().any(|found| covers(object, *found)) {
231                Arc::make_mut(&mut self.descriptions).retain(|found, _| !covers(object, *found));
232            }
233            if self.acls.keys().any(|found| covers(object, *found)) {
234                Arc::make_mut(&mut self.acls).retain(|found, _| !covers(object, *found));
235            }
236        }
237        Ok(())
238    }
239
240    /// `doDeletion` for one object. A rule has no row of its own: it goes with its view.
241    fn remove_one(&mut self, object: ObjRef) -> Result<()> {
242        match object.class {
243            // `RemoveAttributeById`: the row of `pg_attribute` stays with `attisdropped`, so the later columns keep their numbers.
244            PG_CLASS if object.sub != 0 => {
245                if let Ok(number) = i16::try_from(object.sub)
246                    && let Ok(column) = self.column_mut(object.oid, number)
247                {
248                    *column = Column {
249                        name: format!("........pg.dropped.{}........", object.sub),
250                        ty: 0,
251                        typmod: -1,
252                        ndims: 0,
253                        collation: 0,
254                        not_null: false,
255                        not_null_invalid: false,
256                        has_default: false,
257                        identity: 0,
258                        generated: 0,
259                        dropped: true,
260                        stat_target: None,
261                        storage: column.storage,
262                        compression: column.compression,
263                        options: Vec::new(),
264                        local: column.local,
265                        inhcount: column.inhcount,
266                    };
267                }
268            }
269            PG_CLASS => {
270                self.relations.remove(&object.oid);
271            }
272            PG_TYPE => {
273                self.types.remove(&object.oid);
274            }
275            PG_ATTRDEF => {
276                if let Some(default) = self.defaults.remove(&object.oid)
277                    && let Ok(column) = self.column_mut(default.relation, default.column)
278                {
279                    column.has_default = false;
280                }
281            }
282            PG_CONSTRAINT => {
283                // `dropconstraint_internal` clears `attnotnull` with the not-null constraint of the column.
284                if let Some(con) = self.constraints.remove(&object.oid)
285                    && con.kind == ConKind::NotNull
286                    && let [number] = con.keys[..]
287                    && let Ok(column) = self.column_mut(con.relation, number)
288                {
289                    column.not_null = false;
290                    column.not_null_invalid = false;
291                }
292            }
293            PG_NAMESPACE => {
294                if self.schemas.remove(&object.oid).is_none() && object.oid < FIRST_NORMAL_OID {
295                    self.dropped_builtins.insert(object);
296                }
297            }
298            PG_PROC => {
299                self.functions.remove(&object.oid);
300                self.aggregates.remove(&object.oid);
301            }
302            PG_TRIGGER => {
303                self.triggers.remove(&object.oid);
304            }
305            PG_CAST => {
306                self.casts.remove(&object.oid);
307            }
308            PG_OPERATOR => self.remove_operator(object.oid),
309            PG_OPFAMILY => {
310                self.opfamilies.remove(&object.oid);
311            }
312            PG_OPCLASS => {
313                self.opclasses.remove(&object.oid);
314            }
315            PG_AMOP => {
316                self.amops.remove(&object.oid);
317            }
318            PG_AMPROC => {
319                self.amprocs.remove(&object.oid);
320            }
321            PG_TS_TEMPLATE => {
322                self.ts_templates.remove(&object.oid);
323            }
324            PG_TS_PARSER => {
325                self.ts_parsers.remove(&object.oid);
326            }
327            PG_TS_DICT => {
328                self.ts_dictionaries.remove(&object.oid);
329            }
330            PG_TS_CONFIG => {
331                self.ts_configs.remove(&object.oid);
332                self.ts_mappings.retain(|m| m.config != object.oid);
333            }
334            PG_REWRITE => {
335                self.rules.remove(&object.oid);
336            }
337            PG_STATISTIC_EXT => {
338                self.stat_exts.remove(&object.oid);
339            }
340            PG_DEFAULT_ACL => self.remove_default_acl(object.oid),
341            class => {
342                return Err(Error::internal(format!("unsupported object class: {class}")));
343            }
344        }
345        Ok(())
346    }
347
348    /// The view of a `_RETURN` rule.
349    pub fn view_of_rule(&self, rule: u32) -> Option<&super::Relation> {
350        self.relations.values().find(|r| r.view.as_ref().is_some_and(|v| v.rule == rule))
351    }
352}