use pgrx::pg_sys::panic::ErrorReport;
use pgrx::prelude::*;
#[pg_extern]
#[allow(clippy::missing_const_for_fn)] fn pg_tviews_version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[pg_extern]
pub fn pg_tviews_recover_after_crash(entity_name: &str) -> Result<bool, ErrorReport> {
crate::revision::check();
let meta = crate::catalog::TviewMeta::load_by_entity(entity_name)?.ok_or_else(|| {
crate::TViewError::MetadataNotFound {
entity: entity_name.to_string(),
}
})?;
crate::owner::require_owner(meta.tview_oid, &format!("tv_{entity_name}"))?;
Ok(validity::fill_if_reset(entity_name)?)
}
pub mod validity {
use crate::TViewResult;
use crate::catalog::TviewMeta;
use pgrx::pg_sys::{self, Oid};
use pgrx::prelude::*;
thread_local! {
static CHECKED: std::cell::RefCell<std::collections::HashSet<u32>> =
std::cell::RefCell::new(std::collections::HashSet::new());
}
fn table() -> String {
format!("{}.pg_tview_valid", crate::utils::ext_schema())
}
fn unlogged(table_oid: Oid) -> bool {
let persistence = unsafe { pg_sys::get_rel_persistence(table_oid) };
u8::try_from(persistence).ok() == Some(pg_sys::RELPERSISTENCE_UNLOGGED)
}
pub fn needs_fill(table_oid: Oid) -> TViewResult<bool> {
if !unlogged(table_oid) {
return Ok(false);
}
let sql = format!(
"SELECT EXISTS (SELECT 1 FROM {} WHERE table_oid = $1)",
table()
);
let marked = Spi::connect(|client| {
client
.select(&sql, Some(1), &[crate::utils::spi::oid(table_oid)])?
.first()
.get_one::<bool>()
})
.map_err(|e| crate::utils::spi::error(&sql, &e))?;
Ok(marked != Some(true))
}
pub fn mark(table_oid: Oid) -> TViewResult<bool> {
if !unlogged(table_oid) {
return Ok(false);
}
let sql = format!(
"INSERT INTO {} VALUES ($1) ON CONFLICT DO NOTHING RETURNING table_oid",
table()
);
let _owner = crate::owner::AsOwner::of_extension()?;
let inserted = Spi::connect_mut(|client| {
Ok::<_, pgrx::spi::Error>(
!client
.update(&sql, None, &[crate::utils::spi::oid(table_oid)])?
.is_empty(),
)
})
.map_err(|e| crate::utils::spi::error(&sql, &e))?;
Ok(inserted)
}
pub fn forget(table_oid: Oid) -> TViewResult<()> {
let sql = format!("DELETE FROM {} WHERE table_oid = $1", table());
let _owner = crate::owner::AsOwner::of_extension()?;
crate::utils::spi::run(&sql, &[crate::utils::spi::oid(table_oid)])
}
pub fn ensure(entity: &str) -> TViewResult<bool> {
let Some(meta) = TviewMeta::load_by_entity(entity)? else {
return Ok(false);
};
let table = meta.tview_oid.to_u32();
if CHECKED.with_borrow(|checked| checked.contains(&table)) {
return Ok(false);
}
let filled = fill_if_reset(entity)?;
CHECKED.with_borrow_mut(|checked| checked.insert(table));
Ok(filled)
}
pub fn forget_checks() {
CHECKED.with_borrow_mut(std::collections::HashSet::clear);
}
pub fn fill_if_reset(entity: &str) -> TViewResult<bool> {
let Some(meta) = TviewMeta::load_by_entity(entity)? else {
return Ok(false);
};
if !needs_fill(meta.tview_oid)? {
return Ok(false);
}
let graph = crate::flush::EntityDepGraph::load()?;
for dependency in graph.children.get(entity).into_iter().flatten() {
fill_if_reset(dependency)?;
}
if !mark(meta.tview_oid)? {
return Ok(false);
}
crate::admin::refill(entity)?;
Ok(true)
}
}
#[pg_guard]
pub extern "C-unwind" fn _PG_init() {
crate::config::register_gucs();
crate::cache::register_relcache_callback();
crate::rebuild_worker::register();
unsafe {
crate::hooks::ensure_hook_installed();
}
unsafe {
crate::flush::register_xact_callback();
crate::flush::register_subxact_callback();
}
}