use crate::error::{TViewError, TViewResult};
use pgrx::prelude::*;
pub fn drop_tview(tview_name: &str, if_exists: bool, cascade: bool) -> TViewResult<bool> {
crate::revision::check();
let crate::catalog::resolve::Name { schema, entity } =
crate::catalog::resolve::parse(tview_name)?;
let entity_name = entity.as_str();
super::lock_entity(entity_name)?;
let exists = crate::catalog::row::exists(entity_name)?
&& match &schema {
Some(schema) => {
crate::catalog::resolve::registered_schema(entity_name)?.as_ref() == Some(schema)
}
None => true,
};
if !exists && !if_exists {
return Err(TViewError::TviewNotFound {
name: tview_name.to_string(),
});
}
if !exists {
notice!("TVIEW \"{tview_name}\" does not exist, skipping");
return Ok(false);
}
let meta = crate::catalog::TviewMeta::load_to_rederive(entity_name).map_err(|e| {
TViewError::SpiError {
query: "Load TviewMeta by entity".to_string(),
error: e.to_string(),
}
})?;
if let Some(ref m) = meta
&& let Some(owned) = owned_relation(m)?
{
crate::owner::require_owner(owned, &format!("tv_{entity_name}"))?;
}
crate::dependency::remove_entity_triggers(entity_name)?;
drop_metadata(entity_name)?;
if let Some(ref m) = meta {
drop_by_oid(m.tview_oid, "TABLE", cascade)?;
crate::stats::forget(m.tview_oid);
}
if let Some(ref m) = meta {
drop_by_oid(m.view_oid, "VIEW", cascade)?;
}
crate::cache::invalidate_all();
crate::audit::log_drop(entity_name);
crate::audit::flush_audit_buffer()?;
Ok(true)
}
pub fn handle_dropped(entity: &str) -> TViewResult<()> {
let dropped = crate::catalog::queries::dropped_by_event(entity)?;
if dropped != Some(true) {
return Err(TViewError::InvalidInput {
parameter: "entity".to_string(),
reason: format!("the current statement did not drop TVIEW {entity}'s view or table"),
});
}
let (table_left, table_owned) = crate::catalog::queries::table_left_and_owned(entity)?;
if table_owned == Some(true) {
return drop_tview(entity, true, true).map(|_| ());
}
if table_left != Some(true) {
drop_backing_view(entity)?;
}
crate::dependency::remove_entity_triggers(entity)?;
drop_metadata(entity)?;
crate::cache::invalidate_all();
crate::audit::log_drop(entity);
if table_left == Some(true) {
notice!(
"pg_tviews: TVIEW {entity} deregistered; its table belongs to another role and \
was kept as a plain table"
);
}
Ok(())
}
fn drop_backing_view(entity: &str) -> TViewResult<()> {
if let Some(view) = crate::catalog::queries::backing_view(entity)? {
let _owner = crate::owner::AsOwner::of_table(view)?;
drop_by_oid(view, "VIEW", true)?;
}
Ok(())
}
pub fn drop_left_backing_views(views: &[pg_sys::Oid]) -> TViewResult<()> {
for &view in views {
let args = [crate::utils::spi::oid(view)];
let present = crate::utils::spi::one::<bool>(
"SELECT EXISTS (SELECT 1 FROM pg_catalog.pg_class WHERE oid = $1)",
&args,
)?;
if present == Some(true) {
let _owner = crate::owner::AsOwner::of_table(view)?;
drop_by_oid(view, "VIEW", true)?;
}
}
Ok(())
}
pub fn reclaim_leftover_view(schema: &str, name: &str) -> TViewResult<bool> {
let leftover = crate::catalog::queries::leftover_view(schema, name)?;
let Some(view) = leftover else {
return Ok(false);
};
{
let _owner = crate::owner::AsOwner::of_table(view)?;
drop_by_oid(view, "VIEW", false)?;
}
notice!(
"pg_tviews: dropped {schema}.{name}, a backing view no TVIEW is registered with \
(left by a dropped extension or TVIEW)"
);
Ok(true)
}
fn drop_by_oid(oid: pg_sys::Oid, kind: &str, cascade: bool) -> TViewResult<()> {
let qualified = crate::utils::spi_get_string(&format!(
"SELECT quote_ident(n.nspname::text) || '.' || quote_ident(c.relname::text) \
FROM pg_class c \
JOIN pg_namespace n ON c.relnamespace = n.oid \
WHERE c.oid = {}",
oid.to_u32()
))
.map_err(|e| TViewError::CatalogError {
operation: format!("Resolve qualified name for OID {}", oid.to_u32()),
pg_error: e.to_string(),
})?;
if let Some(qname) = qualified {
let cascade_kw = if cascade { " CASCADE" } else { "" };
let sql = format!("DROP {kind} IF EXISTS {qname}{cascade_kw}");
crate::utils::spi::run_ddl(&sql)?;
}
Ok(())
}
fn owned_relation(meta: &crate::catalog::TviewMeta) -> TViewResult<Option<pg_sys::Oid>> {
let args = [meta.tview_oid, meta.view_oid].map(crate::utils::spi::oid);
Spi::connect(|client| {
client
.select(
"SELECT COALESCE((SELECT oid FROM pg_catalog.pg_class WHERE oid = $1), \
(SELECT oid FROM pg_catalog.pg_class WHERE oid = $2))",
None,
&args,
)?
.first()
.get_one::<pg_sys::Oid>()
})
.map_err(|e| TViewError::CatalogError {
operation: format!("Find the relations of TVIEW {}", meta.entity_name),
pg_error: e.to_string(),
})
}
fn drop_metadata(entity_name: &str) -> TViewResult<()> {
crate::catalog::queries::delete(entity_name)?;
crate::lifecycle::validity::forget(entity_name)
}