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}