use crate::catalog::plan::LocalPath;
use crate::catalog::{KeyType, TviewMeta};
use crate::flush::EntityDepGraph;
use pgrx::pg_sys::{self, Oid};
use pgrx::prelude::*;
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
pub struct Memo<K, V>(RefCell<HashMap<K, V>>);
impl<K: Eq + Hash, V: Clone> Memo<K, V> {
fn new() -> Self {
Self(RefCell::new(HashMap::new()))
}
pub fn get(&self, key: &K) -> Option<V> {
sync_generation();
self.0.borrow().get(key).cloned()
}
pub fn insert(&self, key: K, value: V) {
let mut map = self.0.borrow_mut();
if map.len() >= crate::config::cache_size() {
map.clear();
}
map.insert(key, value);
}
pub fn get_or_load(
&self,
key: K,
load: impl FnOnce() -> crate::TViewResult<V>,
) -> crate::TViewResult<V> {
if let Some(value) = self.get(&key) {
return Ok(value);
}
let value = load()?;
self.insert(key, value.clone());
Ok(value)
}
fn clear(&self) {
self.0.borrow_mut().clear();
}
}
thread_local! {
pub static GRAPH: Memo<(), EntityDepGraph> = Memo::new();
pub static CASCADE_PATHS: Memo<Oid, Vec<LocalPath>> = Memo::new();
pub static METAS: Memo<String, TviewMeta> = Memo::new();
pub static KEY_TYPES: Memo<Oid, KeyType> = Memo::new();
pub static COLUMNS: Memo<Oid, Vec<String>> = Memo::new();
pub static MAPPINGS: Memo<(String, u32), Option<String>> = Memo::new();
pub static READ_SETS: Memo<(String, u32, i16), Option<String>> = Memo::new();
pub static LOCK_VALUES: Memo<(u32, i16), Option<crate::concurrency::LockValue>> = Memo::new();
pub static PLANS: Memo<String, std::rc::Rc<crate::utils::spi::KeptPlan>> = Memo::new();
pub static DELTAS: Memo<(u32, crate::delta::Event, Vec<i16>), String> = Memo::new();
pub static COMPUTED: Memo<Oid, Option<String>> = Memo::new();
pub static PARTITION_ROOTS: Memo<Oid, Oid> = Memo::new();
pub static JSONB_DELTA_SCHEMA: Memo<(), Option<String>> = Memo::new();
}
pub fn graph() -> crate::TViewResult<EntityDepGraph> {
if !crate::config::graph_cache_enabled() {
return EntityDepGraph::load();
}
if let Some(graph) = GRAPH.with(|m| m.get(&())) {
crate::metrics::metrics_api::record_graph_cache_hit();
return Ok(graph);
}
crate::metrics::metrics_api::record_graph_cache_miss();
let graph = EntityDepGraph::load()?;
GRAPH.with(|m| m.insert((), graph.clone()));
Ok(graph)
}
pub fn cascade_paths(table_oid: Oid) -> crate::TViewResult<Vec<LocalPath>> {
CASCADE_PATHS.with(|m| {
m.get_or_load(table_oid, || {
Ok(TviewMeta::load_all()?
.into_iter()
.flat_map(|meta| meta.plan.paths)
.filter(|path| path.source_oid == table_oid)
.collect())
})
})
}
pub fn invalidate_all() {
GRAPH.with(Memo::clear);
CASCADE_PATHS.with(Memo::clear);
METAS.with(Memo::clear);
KEY_TYPES.with(Memo::clear);
COLUMNS.with(Memo::clear);
MAPPINGS.with(Memo::clear);
READ_SETS.with(Memo::clear);
LOCK_VALUES.with(Memo::clear);
PLANS.with(Memo::clear);
DELTAS.with(Memo::clear);
COMPUTED.with(Memo::clear);
PARTITION_ROOTS.with(Memo::clear);
JSONB_DELTA_SCHEMA.with(Memo::clear);
crate::utils::forget_logged(crate::jsonb_delta::JSONB_DELTA_MISSING);
CATALOG_WATCHED.with(|w| w.set(false));
}
pub fn end_transaction() {
CASCADE_PATHS.with(Memo::clear);
}
thread_local! {
static GENERATION: Cell<u64> = const { Cell::new(0) };
static SEEN_GENERATION: Cell<u64> = const { Cell::new(0) };
static WATCHED: RefCell<HashSet<Oid>> = RefCell::new(HashSet::new());
static CATALOG_WATCHED: Cell<bool> = const { Cell::new(false) };
}
fn watch_catalog() {
if CATALOG_WATCHED.with(Cell::get) {
return;
}
crate::metrics::metrics_api::record_catalog_lookup();
if let Ok(Some(oid)) = Spi::get_one::<Oid>(&format!(
"SELECT pg_catalog.to_regclass('{}')::pg_catalog.oid",
crate::utils::meta_table()
)) {
watch(&[oid]);
CATALOG_WATCHED.with(|w| w.set(true));
}
}
pub fn watch(oids: &[Oid]) {
WATCHED.with(|w| w.borrow_mut().extend(oids.iter().copied()));
}
pub fn sync_generation() {
watch_catalog();
let current = GENERATION.with(Cell::get);
if SEEN_GENERATION.with(Cell::get) != current {
SEEN_GENERATION.with(|s| s.set(current));
invalidate_all();
watch_catalog();
}
}
#[pg_guard]
unsafe extern "C-unwind" fn relcache_callback(_arg: pg_sys::Datum, relid: Oid) {
let relevant = relid == pg_sys::InvalidOid
|| WATCHED.with(|w| w.try_borrow().map_or(true, |w| w.contains(&relid)));
if relevant {
GENERATION.with(|g| g.set(g.get().wrapping_add(1)));
}
}
pub fn register_relcache_callback() {
unsafe {
pg_sys::CacheRegisterRelcacheCallback(Some(relcache_callback), pg_sys::Datum::from(0));
}
}
#[pg_extern]
fn pg_tviews_invalidate_caches(relid: Oid) {
unsafe { pg_sys::CacheInvalidateRelcacheByRelid(relid) };
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalidate_all_clears_every_memo_and_the_catalog_watch() {
COLUMNS.with(|m| m.0.borrow_mut().insert(Oid::from(1), vec!["a".into()]));
PARTITION_ROOTS.with(|m| m.0.borrow_mut().insert(Oid::from(2), Oid::from(3)));
CATALOG_WATCHED.with(|w| w.set(true));
invalidate_all();
assert!(COLUMNS.with(|m| m.0.borrow().is_empty()));
assert!(PARTITION_ROOTS.with(|m| m.0.borrow().is_empty()));
assert!(!CATALOG_WATCHED.with(Cell::get));
}
}