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.
The node's scan slot: the child's columns, then one per call.
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.
Frees what earlier evaluations allocated.
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 }