tuple.rsannotatedtuple.rssource116 lines · 4.7 KB · raw

The scan tuple, and the only way the node evaluates an expression.

An expression initialized for this node reads its columns from the node's own scan slot, and Postgres compiles it for that slot's kind: the slot is virtual (scanopsfixed), so the compiled code does not deform. Pointing ecxt_scantuple at any other slot (the child's) then reads undeformed memory, and it crashed over a Sort's minimal tuples. So neither the slot nor the expression context is exposed: rows are loaded into [ScanTuple], and [ScanTuple::eval] is the one path to an [Expr], always against the tuple loaded here.

12use pgrx::pg_sys;
14use super::ffi::{eval_expr, exec_clear_tuple, reset_expr_context, slot_getallattrs};

An expression initialized with this node as its parent.

17pub(super) struct Expr(*mut pg_sys::ExprState);
19impl Expr {

Safety

expr is post-setrefs (reading the scan tuple as INDEX_VAR) and 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}

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}
37impl ScanTuple {

Safety

ss is the initialized scan node; its first prefix scan columns 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    }

Loads a row: the child's columns from from, of any slot kind (it is deformed here), then answers, one per call column. The values borrow from from, which must keep the row until the next load.

Safety

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    }

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    }

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    }

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    }

Per-tuple memory, where evaluation results live until 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}