use super::ops::{
clear_queue, is_crash_recovery_checked, mark_crash_recovery_checked, take_queue_snapshot,
};
use crate::TViewResult;
use pgrx::datum::DatumWithOid;
use pgrx::pg_sys;
use pgrx::prelude::*;
use std::collections::HashSet;
use std::os::raw::c_void;
use std::panic::AssertUnwindSafe;
thread_local! {
static SAVEPOINT_DEPTH: std::cell::RefCell<usize> = const { std::cell::RefCell::new(0) };
static QUEUE_SNAPSHOTS: std::cell::RefCell<Vec<HashSet<super::key::RefreshKey>>> =
const { std::cell::RefCell::new(Vec::new()) };
static PATCH_SNAPSHOTS: std::cell::RefCell<
Vec<std::collections::HashMap<super::key::RefreshKey, super::patch::PatchState>>,
> = const { std::cell::RefCell::new(Vec::new()) };
}
#[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() };
SAVEPOINT_DEPTH.with(|d| {
*d.borrow_mut() = (nest_level as usize).saturating_sub(1);
});
QUEUE_SNAPSHOTS.with(|s| {
let mut snapshots = s.borrow_mut();
for _ in 0..(nest_level as usize).saturating_sub(1) {
snapshots.push(HashSet::new());
}
});
PATCH_SNAPSHOTS.with(|s| {
let mut snapshots = s.borrow_mut();
for _ in 0..(nest_level as usize).saturating_sub(1) {
snapshots.push(std::collections::HashMap::new());
}
});
}
#[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 => {
#[allow(clippy::collapsible_if)]
if crate::suspend::is_suspended() {
if let Err(e) = crate::suspend::enqueue_suspended_changes() {
warning!("Failed to enqueue suspended changes: {}", e);
}
}
crate::suspend::force_resume();
crate::audit::clear_audit_buffer();
super::ops::clear_crash_recovery_cache();
crate::metrics::metrics_api::reset_metrics();
}
XactEvent::Prepare => {
crate::audit::clear_audit_buffer();
crate::metrics::metrics_api::reset_metrics();
}
XactEvent::Abort => {
crate::suspend::force_resume();
clear_queue();
super::patch::clear_patch_map();
super::ops::clear_crash_recovery_cache();
super::cache::cascade_cache::clear_cache();
crate::audit::clear_audit_buffer();
crate::metrics::metrics_api::reset_metrics();
}
}
}
#[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,
) {
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
match event {
pg_sys::SubXactEvent::SUBXACT_EVENT_START_SUB => {
SAVEPOINT_DEPTH.with(|d| {
let mut depth = d.borrow_mut();
*depth += 1;
});
let snapshot = take_queue_snapshot();
QUEUE_SNAPSHOTS.with(|s| {
s.borrow_mut().push(snapshot);
});
let patch_snapshot = super::patch::take_patch_snapshot();
PATCH_SNAPSHOTS.with(|s| {
s.borrow_mut().push(patch_snapshot);
});
}
pg_sys::SubXactEvent::SUBXACT_EVENT_ABORT_SUB => {
decrement_savepoint_depth();
if let Some(snapshot) = QUEUE_SNAPSHOTS.with(|s| s.borrow_mut().pop()) {
super::state::replace_queue(snapshot);
}
if let Some(patch_snapshot) = PATCH_SNAPSHOTS.with(|s| s.borrow_mut().pop()) {
super::patch::replace_patch_map(patch_snapshot);
}
}
pg_sys::SubXactEvent::SUBXACT_EVENT_COMMIT_SUB => {
decrement_savepoint_depth();
QUEUE_SNAPSHOTS.with(|s| {
s.borrow_mut().pop();
});
PATCH_SNAPSHOTS.with(|s| {
s.borrow_mut().pop();
});
}
_ => {
}
}
}));
if result.is_err() {
warning!("PANIC in subtransaction callback - this is a bug!");
}
}
fn decrement_savepoint_depth() {
SAVEPOINT_DEPTH.with(|d| {
let mut depth = d.borrow_mut();
if *depth == 0 {
warning!("pg_tviews: subxact depth underflow — event ordering unexpected");
}
*depth = depth.saturating_sub(1);
});
}
pub fn flush_refresh_queue() -> TViewResult<()> {
let mut pending = take_queue_snapshot();
if pending.is_empty() {
return Ok(());
}
let mut patches = super::patch::take_patch_snapshot();
let refresh_timer = crate::metrics::metrics_api::record_refresh_start();
let graph = super::cache::graph_cache::load_cached()?;
let mut processed: std::collections::HashSet<super::key::RefreshKey> =
std::collections::HashSet::with_capacity(pending.len().max(16));
let mut parent_meta_cache: std::collections::HashMap<
String,
Option<crate::catalog::TviewMeta>,
> = std::collections::HashMap::new();
let mut iteration = 1;
loop {
while !pending.is_empty() {
let sorted_keys = graph.sort_keys(pending.drain().collect());
let mut keys_by_entity: std::collections::HashMap<String, Vec<super::key::RefreshKey>> =
std::collections::HashMap::with_capacity(8);
for key in sorted_keys {
if !processed.insert(key.clone()) {
continue;
}
keys_by_entity
.entry(key.entity.clone())
.or_default()
.push(key);
}
for (entity, entity_keys) in keys_by_entity {
if !is_crash_recovery_checked(&entity) {
mark_crash_recovery_checked(&entity);
if crate::lifecycle::detect_post_crash_truncation(&entity)? {
Spi::run_with_args(
"SELECT pg_tviews_refresh($1)",
&[unsafe {
DatumWithOid::new(
&entity,
PgOid::BuiltIn(PgBuiltInOids::TEXTOID).value(),
)
}],
)?;
}
}
let apply_enabled = crate::config::direct_patch_enabled();
let mut patched: Vec<(i64, Vec<super::patch::PatchEntry>)> = Vec::new();
let mut applied_pks: HashSet<i64> = HashSet::new();
let mut recompute_keys: Vec<super::key::RefreshKey> = Vec::new();
for key in entity_keys {
if apply_enabled
&& !key.is_dedup()
&& let Some(super::patch::PatchState::Direct(chain)) = patches.get(&key)
{
patched.push((key.pk, chain.clone()));
applied_pks.insert(key.pk);
continue;
}
recompute_keys.push(key);
}
if !patched.is_empty() {
let meta =
crate::catalog::TviewMeta::load_by_entity(&entity)?.ok_or_else(|| {
crate::TViewError::MetadataNotFound {
entity: entity.clone(),
}
})?;
let fallback = crate::refresh::direct::apply_entity_patches(&meta, patched)?;
for pk in fallback {
applied_pks.remove(&pk);
recompute_keys.push(super::key::RefreshKey::pk(&entity, pk));
}
}
if recompute_keys.len() == 1 {
let key = &recompute_keys[0];
let parents = refresh_and_get_parents(key, &graph)?;
for parent_key in parents {
super::patch::poison_into(&mut patches, parent_key.clone());
if !processed.contains(&parent_key) {
pending.insert(parent_key);
}
}
} else if recompute_keys.len() > 1 {
let mut pks =
Vec::with_capacity(recompute_keys.iter().filter(|k| !k.is_dedup()).count());
for key in &recompute_keys {
if !key.is_dedup() {
pks.push(key.pk);
}
}
crate::refresh::refresh_bulk(&entity, &pks)?;
let parent_map = crate::propagate::find_parents_batch(&recompute_keys, &graph)?;
for parent_keys in parent_map.values() {
for parent_key in parent_keys {
super::patch::poison_into(&mut patches, parent_key.clone());
if !processed.contains(parent_key) {
pending.insert(parent_key.clone());
}
}
}
}
if !applied_pks.is_empty() {
let applied_keys: Vec<super::key::RefreshKey> = applied_pks
.iter()
.map(|&pk| super::key::RefreshKey::pk(&entity, pk))
.collect();
let parent_map = crate::propagate::find_parents_batch(&applied_keys, &graph)?;
for (child_key, parent_keys) in &parent_map {
let child_chain = match patches.get(child_key) {
Some(super::patch::PatchState::Direct(chain)) => Some(chain.clone()),
_ => None,
};
for parent_key in parent_keys {
let derived = match &child_chain {
Some(chain) => {
load_meta_cached(&parent_key.entity, &mut parent_meta_cache)?
.and_then(|m| {
crate::refresh::direct::derive_parent_chain(
&m,
&child_key.entity,
chain,
)
})
}
None => None,
};
match derived {
Some(chain) => super::patch::merge_chain_into(
&mut patches,
parent_key.clone(),
chain,
),
None => {
super::patch::poison_into(&mut patches, parent_key.clone());
}
}
if !processed.contains(parent_key) {
pending.insert(parent_key.clone());
}
}
}
}
}
iteration += 1;
let max_depth = crate::config::max_propagation_depth();
if iteration > max_depth {
return Err(crate::TViewError::PropagationDepthExceeded {
max_depth,
processed: processed.len(),
});
}
}
let late = take_queue_snapshot();
if late.is_empty() {
break;
}
for (k, v) in super::patch::take_patch_snapshot() {
patches.insert(k, v);
}
pending = late;
}
{
let mut entity_counts: std::collections::HashMap<&str, i64> =
std::collections::HashMap::new();
for key in &processed {
*entity_counts.entry(&key.entity).or_insert(0) += 1;
}
for (entity, count) in entity_counts {
crate::audit::log_refresh(entity, count);
}
}
crate::metrics::metrics_api::record_refresh_complete(
processed.len(),
iteration - 1,
&refresh_timer,
);
Ok(())
}
fn load_meta_cached(
entity: &str,
cache: &mut std::collections::HashMap<String, Option<crate::catalog::TviewMeta>>,
) -> spi::Result<Option<crate::catalog::TviewMeta>> {
if let Some(meta) = cache.get(entity) {
return Ok(meta.clone());
}
let meta = crate::catalog::TviewMeta::load_by_entity(entity)?;
cache.insert(entity.to_string(), meta.clone());
Ok(meta)
}
fn refresh_and_get_parents(
key: &super::key::RefreshKey,
graph: &super::EntityDepGraph,
) -> TViewResult<Vec<super::key::RefreshKey>> {
use crate::catalog::TviewMeta;
let meta = TviewMeta::load_by_entity(&key.entity)?.ok_or_else(|| {
crate::TViewError::MetadataNotFound {
entity: key.entity.clone(),
}
})?;
if let Some(dedup) = &key.dedup_key {
crate::refresh::refresh_by_dedup_key(meta.view_oid, dedup)?;
} else {
crate::refresh::refresh_pk(meta.view_oid, key.pk)?;
}
let parent_keys = crate::propagate::find_parents_for(key, graph)?;
Ok(parent_keys)
}