tuple.rsannotatedtuple.rssource116 lines · 4.7 KB · raw
1//! The scan tuple, and the only way the node evaluates an expression.
2//!
3//! An expression initialized for this node reads its columns from the
4//! node's own scan slot, and Postgres compiles it for that slot's kind:
5//! the slot is virtual (`scanopsfixed`), so the compiled code does not
6//! deform. Pointing `ecxt_scantuple` at any other slot (the child's) then
7//! reads undeformed memory, and it crashed over a Sort's minimal tuples.
8//! So neither the slot nor the expression context is exposed: rows are
9//! loaded into [`ScanTuple`], and [`ScanTuple::eval`] is the one path to
10//! an [`Expr`], always against the tuple loaded here.
11
12use pgrx::pg_sys;
13
14use super::ffi::{eval_expr, exec_clear_tuple, reset_expr_context, slot_getallattrs};
15
16/// An expression initialized with this node as its parent.
17pub(super) struct Expr(*mut pg_sys::ExprState);
18
19impl Expr {
20    /// # Safety
21    /// `expr` is post-setrefs (reading the scan tuple as INDEX_VAR) and
22    /// `node` is the scan node whose [`ScanTuple`] will evaluate it.
23    pub unsafe fn init(expr: *mut pg_sys::Expr, node: *mut pg_sys::PlanState) -> Self {
24        Expr(unsafe { pg_sys::ExecInitExpr(expr, node) })
25    }
26}
27
28/// The node's scan slot: the child's columns, then one per call.
29pub(super) struct ScanTuple {
30    slot: *mut pg_sys::TupleTableSlot,
31    econtext: *mut pg_sys::ExprContext,
32    prefix: usize,
33    width: usize,
34    loaded: bool,
35}
36
37impl ScanTuple {
38    /// # Safety
39    /// `ss` is the initialized scan node; its first `prefix` scan columns
40    /// are its child's output columns, and the rest are calls.
41    pub unsafe fn new(ss: *mut pg_sys::ScanState, prefix: usize) -> Self {
42        unsafe {
43            let slot = (*ss).ss_ScanTupleSlot;
44            let natts = (*(*slot).tts_tupleDescriptor).natts as usize;
45            assert!(natts >= prefix, "the scan tuple starts with the child's {prefix} columns");
46            assert!(
47                std::ptr::eq((*slot).tts_ops, &raw const pg_sys::TTSOpsVirtual),
48                "expressions are compiled for a virtual scan slot"
49            );
50            ScanTuple { slot, econtext: (*ss).ps.ps_ExprContext, prefix, width: natts - prefix, loaded: false }
51        }
52    }
53
54    /// Loads a row: the child's columns from `from`, of any slot kind (it
55    /// is deformed here), then `answers`, one per call column. The values
56    /// borrow from `from`, which must keep the row until the next load.
57    ///
58    /// # Safety
59    /// `from` holds a row of the child's type.
60    pub unsafe fn load(
61        &mut self,
62        from: *mut pg_sys::TupleTableSlot,
63        answers: impl ExactSizeIterator<Item = Option<pg_sys::Datum>>,
64    ) -> *mut pg_sys::TupleTableSlot {
65        assert_eq!(answers.len(), self.width, "one answer per call column");
66        unsafe {
67            assert!((*(*from).tts_tupleDescriptor).natts as usize >= self.prefix, "the child's row is too narrow");
68            slot_getallattrs(from);
69            exec_clear_tuple(self.slot);
70            let (values, nulls) = ((*self.slot).tts_values, (*self.slot).tts_isnull);
71            for i in 0..self.prefix {
72                *values.add(i) = *(*from).tts_values.add(i);
73                *nulls.add(i) = *(*from).tts_isnull.add(i);
74            }
75            for (i, answer) in answers.enumerate() {
76                *values.add(self.prefix + i) = answer.unwrap_or(pg_sys::Datum::from(0usize));
77                *nulls.add(self.prefix + i) = answer.is_none();
78            }
79            pg_sys::ExecStoreVirtualTuple(self.slot);
80        }
81        self.loaded = true;
82        self.slot
83    }
84
85    /// The end of the scan: an empty slot.
86    pub fn clear(&mut self) -> *mut pg_sys::TupleTableSlot {
87        self.loaded = false;
88        // SAFETY: the node's own slot, live for the executor's lifetime.
89        unsafe { exec_clear_tuple(self.slot) }
90    }
91
92    /// Frees what earlier evaluations allocated.
93    pub fn reset(&self) {
94        // SAFETY: the node's own expression context.
95        unsafe { reset_expr_context(self.econtext) };
96    }
97
98    /// `expr` on the loaded row, in per-tuple memory; `None` is NULL.
99    pub fn eval(&self, expr: &Expr) -> Option<pg_sys::Datum> {
100        assert!(self.loaded, "an expression evaluated with no row loaded");
101        // SAFETY: `expr` was initialized for this node (Expr::init), and
102        // the scan tuple is this node's slot holding a loaded row.
103        unsafe {
104            (*self.econtext).ecxt_scantuple = self.slot;
105            eval_expr(expr.0, self.econtext)
106        }
107    }
108
109    /// Per-tuple memory, where evaluation results live until [`reset`].
110    ///
111    /// [`reset`]: ScanTuple::reset
112    pub fn memory(&self) -> pg_sys::MemoryContext {
113        // SAFETY: the node's own expression context.
114        unsafe { (*self.econtext).ecxt_per_tuple_memory }
115    }
116}