postjevsql.git / crates / postjevsql-pg / src / scan / statement.rs
1//! State shared by every jev scan of one statement: its spend guards
2//! count what the whole statement sends, not what each scan does
3//! (contract *Cost and safety*), and equal judgments in any of its scans
4//! share one request (contract *Execution*).
5//!
6//! A statement is one executor run, identified by its `EState`: every
7//! scan of a plan, subplans and init plans included, begins in the same
8//! one. The state lives in a backend-thread-local map until the
9//! `EState`'s query context is reset, which happens when the executor
10//! ends and when the statement aborts, so an entry never outlives its
11//! `EState` and a later `EState` at the same address starts fresh.
12
13use std::any::{Any, TypeId};
14use std::cell::RefCell;
15use std::collections::HashMap;
16use std::ffi::c_void;
17use std::rc::Rc;
18
19use pgrx::{pg_guard, pg_sys};
20
21/// One statement's values, one per type.
22type Values = HashMap<TypeId, Rc<dyn Any>>;
23
24thread_local! {
25    /// By `EState` address.
26    static STATEMENTS: RefCell<HashMap<usize, Values>> = RefCell::default();
27}
28
29/// The statement a scan belongs to, passed to [`super::Judge::begin`].
30pub struct Statement {
31    estate: *mut pg_sys::EState,
32    rechecking: bool,
33}
34
35impl Statement {
36    /// An EvalPlanQual recheck runs in an executor of its own, whose
37    /// scans belong to the statement being rechecked: the `EState` it
38    /// was started from.
39    ///
40    /// # Safety
41    /// `estate` is the running executor's, and outlives this value.
42    pub(super) unsafe fn new(estate: *mut pg_sys::EState) -> Statement {
43        unsafe {
44            let epq = (*estate).es_epq_active;
45            if epq.is_null() {
46                Statement { estate, rechecking: false }
47            } else {
48                Statement { estate: (*epq).parentestate, rechecking: true }
49            }
50        }
51    }
52
53    /// Whether the scan is an EvalPlanQual recheck of a row the
54    /// statement has already judged (DML or a row lock meeting a
55    /// concurrent update). A recheck must not send: its answers come from
56    /// the statement's judgments or the cache.
57    pub fn rechecking(&self) -> bool {
58        self.rechecking
59    }
60
61    /// The statement's one `T`, made by `init` for its first scan.
62    pub fn shared<T: 'static>(&self, init: impl FnOnce() -> T) -> Rc<T> {
63        let key = self.estate as usize;
64        let fresh = STATEMENTS.with_borrow(|s| !s.contains_key(&key));
65        if fresh {
66            // SAFETY: the EState is live (`new`'s contract); the callback
67            // is allocated in the context it watches, so it lives exactly
68            // as long as the registration.
69            unsafe {
70                let cxt = (*self.estate).es_query_cxt;
71                let callback = pg_sys::MemoryContextAllocZero(cxt, size_of::<pg_sys::MemoryContextCallback>())
72                    .cast::<pg_sys::MemoryContextCallback>();
73                (*callback).func = Some(forget);
74                (*callback).arg = key as *mut c_void;
75                pg_sys::MemoryContextRegisterResetCallback(cxt, callback);
76            }
77            STATEMENTS.with_borrow_mut(|s| s.insert(key, HashMap::new()));
78        }
79        let entry = STATEMENTS.with_borrow(|s| s[&key].get(&TypeId::of::<T>()).cloned());
80        match entry {
81            Some(any) => any.downcast::<T>().unwrap_or_else(|_| unreachable!("keyed by its TypeId")),
82            None => {
83                let value = Rc::new(init());
84                STATEMENTS.with_borrow_mut(|s| {
85                    s.get_mut(&key).expect("registered above").insert(TypeId::of::<T>(), value.clone())
86                });
87                value
88            }
89        }
90    }
91}
92
93/// The statement's query context is going: drop its state. Never panics,
94/// since it may run while an abort unwinds.
95#[pg_guard]
96unsafe extern "C-unwind" fn forget(key: *mut c_void) {
97    let removed = STATEMENTS.with(|s| s.try_borrow_mut().ok().and_then(|mut s| s.remove(&(key as usize))));
98    drop(removed);
99}