use std::cmp::Ordering;
use std::sync::Arc;
use rupg_common::{Error, Result};
use super::{
Catalog, Column, ConKind, DepKind, FIRST_NORMAL_OID, ObjRef, PG_AMOP, PG_AMPROC, PG_ATTRDEF,
PG_CAST, PG_CLASS, PG_CONSTRAINT, PG_DEFAULT_ACL, PG_NAMESPACE, PG_OPCLASS, PG_OPERATOR,
PG_OPFAMILY, PG_PROC, PG_REWRITE, PG_STATISTIC_EXT, PG_TRIGGER, PG_TS_CONFIG, PG_TS_DICT,
PG_TS_PARSER, PG_TS_TEMPLATE, PG_TYPE, is_pinned,
};
pub mod flag {
pub const ORIGINAL: u32 = 0x0001;
pub const NORMAL: u32 = 0x0002;
pub const AUTO: u32 = 0x0004;
pub const INTERNAL: u32 = 0x0008;
pub const EXTENSION: u32 = 0x0010;
pub const PARTITION: u32 = 0x0020;
pub const REVERSE: u32 = 0x0040;
pub const IS_PART: u32 = 0x0080;
pub const SUBOBJECT: u32 = 0x0100;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Target {
pub object: ObjRef,
pub flags: u32,
pub dependee: Option<ObjRef>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Refusal {
Pinned(ObjRef),
Owned {
object: ObjRef,
owner: ObjRef,
},
}
struct Frame {
object: ObjRef,
flags: u32,
}
fn compare(a: ObjRef, b: ObjRef) -> Ordering {
b.oid
.cmp(&a.oid)
.then(a.class.cmp(&b.class))
.then(a.sub.cast_unsigned().cmp(&b.sub.cast_unsigned()))
}
fn covers(object: ObjRef, found: ObjRef) -> bool {
found.class == object.class
&& found.oid == object.oid
&& (object.sub == 0 || found.sub == object.sub)
}
fn is_self(object: ObjRef, other: ObjRef) -> bool {
object.sub == 0 && other.class == object.class && other.oid == object.oid
}
fn present_add<'a>(
object: ObjRef,
flags: u32,
entries: impl DoubleEndedIterator<Item = (ObjRef, &'a mut u32)>,
) -> bool {
let mut result = false;
for (found, found_flags) in entries.rev() {
if found.class != object.class || found.oid != object.oid {
continue;
}
if found.sub == object.sub {
*found_flags |= flags;
result = true;
} else if found.sub == 0 {
result = true;
} else if object.sub == 0 && flags != 0 {
*found_flags |= flags | flag::SUBOBJECT;
}
}
result
}
fn present(object: ObjRef, objects: &[ObjRef]) -> bool {
objects.iter().any(|o| {
o.class == object.class && o.oid == object.oid && (o.sub == object.sub || o.sub == 0)
})
}
impl Catalog {
pub fn deletions(&self, objects: &[ObjRef]) -> std::result::Result<Vec<Target>, Refusal> {
let mut targets = Vec::new();
for &object in objects {
self.find_dependents(object, flag::ORIGINAL, &mut Vec::new(), &mut targets, objects)?;
}
Ok(targets)
}
fn find_dependents(
&self,
object: ObjRef,
mut flags: u32,
stack: &mut Vec<Frame>,
targets: &mut Vec<Target>,
pending: &[ObjRef],
) -> std::result::Result<(), Refusal> {
if present_add(object, flags, stack.iter_mut().map(|f| (f.object, &mut f.flags))) {
return Ok(());
}
if present_add(object, flags, targets.iter_mut().map(|t| (t.object, &mut t.flags))) {
return Ok(());
}
if is_pinned(object) {
return Err(Refusal::Pinned(object));
}
let mut owners = Vec::new();
let mut partition = None;
for d in self.depends.iter().filter(|d| covers(object, d.object)) {
if is_self(object, d.referenced) {
continue;
}
match d.kind {
DepKind::Internal | DepKind::Extension => owners.push(d.referenced),
DepKind::PartitionPri => {
flags |= flag::IS_PART;
partition = Some(d.referenced);
}
DepKind::PartitionSec => {
if flags & flag::IS_PART == 0 {
partition = Some(d.referenced);
}
flags |= flag::IS_PART;
}
DepKind::Normal | DepKind::Auto => {}
}
}
let mut owner = None;
for other in owners {
if stack.is_empty() {
if present(other, pending) {
return Ok(());
}
owner.get_or_insert(other);
continue;
}
if present_add(other, 0, stack.iter_mut().map(|f| (f.object, &mut f.flags))) {
continue;
}
self.find_dependents(other, flag::REVERSE, stack, targets, pending)?;
present_add(object, flags, targets.iter_mut().map(|t| (t.object, &mut t.flags)));
return Ok(());
}
if let Some(owner) = owner {
return Err(Refusal::Owned { object, owner: partition.unwrap_or(owner) });
}
let mut dependents: Vec<(ObjRef, u32)> = self
.depends
.iter()
.filter(|d| covers(object, d.referenced) && !is_self(object, d.object))
.map(|d| {
let flags = match d.kind {
DepKind::Normal => flag::NORMAL,
DepKind::Auto => flag::AUTO,
DepKind::Internal => flag::INTERNAL,
DepKind::Extension => flag::EXTENSION,
DepKind::PartitionPri | DepKind::PartitionSec => flag::PARTITION,
};
(d.object, flags)
})
.collect();
dependents.sort_by(|a, b| compare(a.0, b.0));
stack.push(Frame { object, flags });
for (dependent, flags) in dependents {
self.find_dependents(dependent, flags, stack, targets, pending)?;
}
let frame = stack.pop().unwrap_or(Frame { object, flags });
let dependee = if frame.flags & flag::IS_PART != 0 {
partition
} else {
stack.last().map(|f| f.object)
};
targets.push(Target { object, flags: frame.flags, dependee });
Ok(())
}
pub fn remove(&mut self, targets: &[Target]) -> Result<()> {
for target in targets {
let object = target.object;
self.remove_one(object)?;
self.depends.retain(|d| !covers(object, d.object));
if self.descriptions.keys().any(|found| covers(object, *found)) {
Arc::make_mut(&mut self.descriptions).retain(|found, _| !covers(object, *found));
}
if self.acls.keys().any(|found| covers(object, *found)) {
Arc::make_mut(&mut self.acls).retain(|found, _| !covers(object, *found));
}
}
Ok(())
}
fn remove_one(&mut self, object: ObjRef) -> Result<()> {
match object.class {
PG_CLASS if object.sub != 0 => {
if let Ok(number) = i16::try_from(object.sub)
&& let Ok(column) = self.column_mut(object.oid, number)
{
*column = Column {
name: format!("........pg.dropped.{}........", object.sub),
ty: 0,
typmod: -1,
ndims: 0,
collation: 0,
not_null: false,
not_null_invalid: false,
has_default: false,
identity: 0,
generated: 0,
dropped: true,
stat_target: None,
storage: column.storage,
compression: column.compression,
options: Vec::new(),
local: column.local,
inhcount: column.inhcount,
};
}
}
PG_CLASS => {
self.relations.remove(&object.oid);
}
PG_TYPE => {
self.types.remove(&object.oid);
}
PG_ATTRDEF => {
if let Some(default) = self.defaults.remove(&object.oid)
&& let Ok(column) = self.column_mut(default.relation, default.column)
{
column.has_default = false;
}
}
PG_CONSTRAINT => {
if let Some(con) = self.constraints.remove(&object.oid)
&& con.kind == ConKind::NotNull
&& let [number] = con.keys[..]
&& let Ok(column) = self.column_mut(con.relation, number)
{
column.not_null = false;
column.not_null_invalid = false;
}
}
PG_NAMESPACE => {
if self.schemas.remove(&object.oid).is_none() && object.oid < FIRST_NORMAL_OID {
self.dropped_builtins.insert(object);
}
}
PG_PROC => {
self.functions.remove(&object.oid);
self.aggregates.remove(&object.oid);
}
PG_TRIGGER => {
self.triggers.remove(&object.oid);
}
PG_CAST => {
self.casts.remove(&object.oid);
}
PG_OPERATOR => self.remove_operator(object.oid),
PG_OPFAMILY => {
self.opfamilies.remove(&object.oid);
}
PG_OPCLASS => {
self.opclasses.remove(&object.oid);
}
PG_AMOP => {
self.amops.remove(&object.oid);
}
PG_AMPROC => {
self.amprocs.remove(&object.oid);
}
PG_TS_TEMPLATE => {
self.ts_templates.remove(&object.oid);
}
PG_TS_PARSER => {
self.ts_parsers.remove(&object.oid);
}
PG_TS_DICT => {
self.ts_dictionaries.remove(&object.oid);
}
PG_TS_CONFIG => {
self.ts_configs.remove(&object.oid);
self.ts_mappings.retain(|m| m.config != object.oid);
}
PG_REWRITE => {
self.rules.remove(&object.oid);
}
PG_STATISTIC_EXT => {
self.stat_exts.remove(&object.oid);
}
PG_DEFAULT_ACL => self.remove_default_acl(object.oid),
class => {
return Err(Error::internal(format!("unsupported object class: {class}")));
}
}
Ok(())
}
pub fn view_of_rule(&self, rule: u32) -> Option<&super::Relation> {
self.relations.values().find(|r| r.view.as_ref().is_some_and(|v| v.rule == rule))
}
}