use crate::error::{TViewError, TViewResult};
use pgrx::datum::DatumWithOid;
use pgrx::prelude::*;
pub fn drop_tview(tview_name: &str, if_exists: bool) -> TViewResult<()> {
let entity_name = tview_name.trim_start_matches("tv_");
let view_name = format!("v_{entity_name}");
let exists = tview_exists_in_metadata(entity_name)?;
if !exists && !if_exists {
return Err(TViewError::MetadataNotFound {
entity: entity_name.to_string(),
});
}
if !exists {
return Ok(());
}
let meta = crate::catalog::TviewMeta::load_by_entity(entity_name).map_err(|e| {
TViewError::SpiError {
query: "Load TviewMeta by entity".to_string(),
error: e.to_string(),
}
})?;
match crate::dependency::find_base_tables(&view_name, None) {
Ok(dep_graph) => {
if !dep_graph.base_tables.is_empty() {
crate::dependency::remove_triggers(&dep_graph.base_tables, entity_name)?;
}
}
Err(e) => {
warning!("Could not find dependencies for cleanup: {}", e);
}
}
if let Some(ref m) = meta {
drop_by_oid(m.tview_oid, "TABLE")?;
}
if let Some(ref m) = meta {
drop_by_oid(m.view_oid, "VIEW")?;
}
drop_metadata(entity_name)?;
crate::queue::cache::invalidate_all_caches();
crate::audit::log_drop(entity_name);
if let Err(e) = crate::audit::flush_audit_buffer() {
warning!("Failed to flush audit after DROP: {}", e);
}
Ok(())
}
fn drop_by_oid(oid: pg_sys::Oid, kind: &str) -> 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 sql = format!("DROP {kind} IF EXISTS {qname}");
crate::utils::spi_run_ddl(&sql).map_err(|e| TViewError::SpiError {
query: sql,
error: e,
})?;
}
Ok(())
}
fn tview_exists_in_metadata(entity_name: &str) -> TViewResult<bool> {
let args = vec![unsafe {
DatumWithOid::new(entity_name, PgOid::BuiltIn(PgBuiltInOids::TEXTOID).value())
}];
Spi::get_one_with_args::<bool>(
"SELECT COUNT(*) > 0 FROM pg_tview_meta WHERE entity = $1",
&args,
)
.map_err(|e| TViewError::CatalogError {
operation: format!("Check TVIEW metadata: {entity_name}"),
pg_error: format!("{e:?}"),
})
.map(|opt| opt.unwrap_or(false))
}
fn drop_metadata(entity_name: &str) -> TViewResult<()> {
let args =
[
unsafe {
DatumWithOid::new(entity_name, PgOid::BuiltIn(PgBuiltInOids::TEXTOID).value())
},
];
Spi::run_with_args("DELETE FROM pg_tview_meta WHERE entity = $1", &args).map_err(|e| {
TViewError::SpiError {
query: "DELETE FROM pg_tview_meta WHERE entity = $1".to_string(),
error: e.to_string(),
}
})?;
Ok(())
}
#[cfg(any(test, feature = "pg_test"))]
#[pg_schema]
mod tests {
use pgrx::prelude::*;
#[pg_test]
fn test_drop_tview_nonexistent_if_exists() {
let result = Spi::run("SELECT pg_tviews_drop('nonexistent', true)");
assert!(
result.is_ok(),
"IF EXISTS drop of non-existent TVIEW should succeed"
);
}
#[pg_test]
fn test_drop_tview_nonexistent_strict() {
let result = Spi::run("SELECT pg_tviews_drop('nonexistent', false)");
assert!(
result.is_err(),
"Strict drop of non-existent TVIEW should fail"
);
}
}