use pgrx::pg_sys;
use pgrx::prelude::*;
use std::os::raw::c_void;
const RESET_AT_END: &[fn()] = &[
crate::queue::state::clear,
super::savepoint::clear,
crate::concurrency::clear,
crate::executor::reset,
super::Flushing::reset,
crate::cache::end_transaction,
crate::audit::clear_audit_buffer,
crate::metrics::metrics_api::reset_metrics,
crate::queue::affected::clear,
];
const RESET_ON_ABORT: &[fn()] = &[
crate::cache::invalidate_all,
crate::suspend::force_resume,
crate::revision::reset,
|| crate::internal_ddl::release_on_abort(true),
crate::lifecycle::validity::forget_checks,
];
const RESET_ON_SUBABORT: &[fn()] = &[
|| crate::internal_ddl::release_on_abort(false),
crate::cache::invalidate_all,
crate::lifecycle::validity::forget_checks,
];
fn run(resets: &[fn()]) {
for reset in resets {
reset();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum XactEvent {
Commit,
Abort,
PreCommit,
Prepare, }
pub unsafe fn register_xact_callback() {
unsafe {
pg_sys::RegisterXactCallback(Some(tview_xact_callback), std::ptr::null_mut());
}
}
pub unsafe fn register_subxact_callback() {
unsafe {
pg_sys::RegisterSubXactCallback(Some(tview_subxact_callback), std::ptr::null_mut());
}
let nest_level = unsafe { pg_sys::GetCurrentTransactionNestLevel() };
for _ in 0..usize::try_from(nest_level).unwrap_or(0).saturating_sub(1) {
super::savepoint::start();
}
}
#[pg_guard]
#[unsafe(no_mangle)]
unsafe extern "C-unwind" fn tview_xact_callback(event: u32, _arg: *mut c_void) {
#[allow(non_upper_case_globals)] let xact_event = match event {
pg_sys::XactEvent::XACT_EVENT_COMMIT => XactEvent::Commit,
pg_sys::XactEvent::XACT_EVENT_PRE_COMMIT => XactEvent::PreCommit,
pg_sys::XactEvent::XACT_EVENT_ABORT => XactEvent::Abort,
pg_sys::XactEvent::XACT_EVENT_PREPARE => XactEvent::Prepare,
_ => return, };
match xact_event {
XactEvent::PreCommit | XactEvent::Commit => {
if xact_event == XactEvent::PreCommit {
fail_unflushed();
}
if crate::suspend::is_suspended() {
let stale = crate::suspend::get_changed_entities();
if !stale.is_empty() {
warning!(
"pg_tviews: transaction committed with refresh suspended; TVIEWs {:?} \
are stale until pg_tviews_refresh() is run for each",
stale
);
}
crate::suspend::clear_changed_entities();
}
crate::suspend::force_resume();
run(RESET_AT_END);
}
XactEvent::Prepare => {
crate::suspend::force_resume();
crate::lifecycle::validity::forget_checks();
run(RESET_AT_END);
}
XactEvent::Abort => {
run(RESET_ON_ABORT);
run(RESET_AT_END);
}
}
}
fn fail_unflushed() {
let queued = crate::queue::state::get_queue_contents();
if queued.is_empty() {
return;
}
let entities: std::collections::BTreeSet<&str> =
queued.iter().map(|k| k.entity.as_str()).collect();
pg_sys::panic::ErrorReport::new(
PgSqlErrorCode::ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE,
format!(
"pg_tviews: cannot commit with {} queued refreshes for {:?}: they were never \
applied (missing flush trigger?)",
queued.len(),
entities
),
function_name!(),
)
.set_hint(
"Run tviews.pg_tviews_health_check() to find missing triggers, and \
tviews.pg_tviews_refresh(entity) to rebuild the TVIEWs named.",
)
.report(PgLogLevel::ERROR);
}
#[pg_guard]
#[unsafe(no_mangle)]
unsafe extern "C-unwind" fn tview_subxact_callback(
event: u32,
_subxid: pg_sys::SubTransactionId,
_parent_subid: pg_sys::SubTransactionId,
_arg: *mut c_void,
) {
match event {
pg_sys::SubXactEvent::SUBXACT_EVENT_START_SUB => super::savepoint::start(),
pg_sys::SubXactEvent::SUBXACT_EVENT_COMMIT_SUB => super::savepoint::commit(),
pg_sys::SubXactEvent::SUBXACT_EVENT_ABORT_SUB => {
super::savepoint::abort();
run(RESET_ON_SUBABORT);
}
_ => {}
}
}