1//! Postgres helpers the scan needs that are `static inline` or macros in 2//! the headers, so bindgen does not carry them, and thin iterators over 3//! `List`. Each mirrors the PG18 definition named in its comment. 4 5use std::ffi::c_void; 6 7use pgrx::{pg_guard, pg_sys}; 8 9/// `ExecProcNode` (executor.h). 10/// 11/// # Safety 12/// `node` is an initialized plan state of the running executor. 13pub unsafe fn exec_proc_node(node: *mut pg_sys::PlanState) -> *mut pg_sys::TupleTableSlot { 14 unsafe { 15 if !(*node).chgParam.is_null() { 16 pg_sys::ExecReScan(node); 17 } 18 (*node).ExecProcNode.expect("an initialized plan state has ExecProcNode")(node) 19 } 20} 21 22/// `TupIsNull` (tuptable.h). 23/// 24/// # Safety 25/// `slot` is NULL or a valid slot. 26pub unsafe fn tup_is_null(slot: *mut pg_sys::TupleTableSlot) -> bool { 27 slot.is_null() || unsafe { (*slot).tts_flags as u32 & pg_sys::TTS_FLAG_EMPTY != 0 } 28} 29 30/// `ExecClearTuple` (tuptable.h). 31/// 32/// # Safety 33/// `slot` is a valid slot. 34pub unsafe fn exec_clear_tuple(slot: *mut pg_sys::TupleTableSlot) -> *mut pg_sys::TupleTableSlot { 35 unsafe { (*(*slot).tts_ops).clear.expect("slot ops have clear")(slot) }; 36 slot 37} 38 39/// `slot_getallattrs` (tuptable.h). 40/// 41/// # Safety 42/// `slot` is a valid, non-empty slot. 43pub unsafe fn slot_getallattrs(slot: *mut pg_sys::TupleTableSlot) { 44 unsafe { 45 let natts = (*(*slot).tts_tupleDescriptor).natts as i16; 46 if (*slot).tts_nvalid < natts { 47 pg_sys::slot_getsomeattrs_int(slot, natts as i32); 48 } 49 } 50} 51 52/// `ExecCopySlotMinimalTuple` (tuptable.h): a copy in the current memory 53/// context. 54/// 55/// # Safety 56/// `slot` is a valid, non-empty slot. 57pub unsafe fn copy_minimal_tuple(slot: *mut pg_sys::TupleTableSlot) -> pg_sys::MinimalTuple { 58 let copy = unsafe { (*(*slot).tts_ops).copy_minimal_tuple.expect("slot ops copy") }; 59 // PG18 added `extra`, bytes reserved before the tuple; none here. 60 #[cfg(feature = "pg17")] 61 return unsafe { copy(slot) }; 62 #[cfg(not(feature = "pg17"))] 63 return unsafe { copy(slot, 0) }; 64} 65 66/// `ExecEvalExprSwitchContext` (executor.h). 67/// 68/// # Safety 69/// `state` was initialized for a plan node of the running executor, and 70/// `econtext` is that node's expression context. 71pub unsafe fn eval_expr(state: *mut pg_sys::ExprState, econtext: *mut pg_sys::ExprContext) -> Option<pg_sys::Datum> { 72 unsafe { 73 let old = pg_sys::CurrentMemoryContext; 74 pg_sys::CurrentMemoryContext = (*econtext).ecxt_per_tuple_memory; 75 let mut isnull = false; 76 let datum = (*state).evalfunc.expect("an initialized ExprState has evalfunc")(state, econtext, &mut isnull); 77 pg_sys::CurrentMemoryContext = old; 78 (!isnull).then_some(datum) 79 } 80} 81 82/// `ResetExprContext` (executor.h). 83/// 84/// # Safety 85/// `econtext` is a live expression context. 86pub unsafe fn reset_expr_context(econtext: *mut pg_sys::ExprContext) { 87 unsafe { pg_sys::MemoryContextReset((*econtext).ecxt_per_tuple_memory) }; 88} 89 90/// `InvokeFunctionExecuteHook` (objectaccess.h). 91/// 92/// # Safety 93/// Called inside a transaction. 94pub unsafe fn invoke_function_execute_hook(funcid: pg_sys::Oid) { 95 unsafe { 96 let hook = pg_sys::object_access_hook; 97 if hook.is_some() { 98 pg_sys::RunFunctionExecuteHook(funcid); 99 } 100 } 101} 102 103/// `IsA(node, tag)` (nodes.h). 104/// 105/// # Safety 106/// `node` is NULL or points to a Node. 107pub unsafe fn is_a(node: *const c_void, tag: pg_sys::NodeTag) -> bool { 108 !node.is_null() && unsafe { (*(node as *const pg_sys::Node)).type_ } == tag 109} 110 111/// `list_length` (pg_list.h). 112/// 113/// # Safety 114/// `list` is NIL or a List. 115pub unsafe fn len(list: *mut pg_sys::List) -> usize { 116 if list.is_null() { 0 } else { unsafe { (*list).length as usize } } 117} 118 119/// The pointer elements of `list` (NIL is empty). 120/// 121/// # Safety 122/// `list` is NIL or a pointer List whose elements are `*mut T`, and it is 123/// not modified while the iterator lives. 124pub unsafe fn ptrs<T>(list: *mut pg_sys::List) -> impl Iterator<Item = *mut T> { 125 (0..unsafe { len(list) }).map(move |i| unsafe { (*(*list).elements.add(i)).ptr_value as *mut T }) 126} 127 128/// The integer elements of an IntList (NIL is empty). 129/// 130/// # Safety 131/// As [`ptrs`], for an IntList. 132pub unsafe fn ints(list: *mut pg_sys::List) -> impl Iterator<Item = i32> { 133 (0..unsafe { len(list) }).map(move |i| unsafe { (*(*list).elements.add(i)).int_value }) 134} 135 136/// `list` with `T`s appended. 137/// 138/// # Safety 139/// `list` is NIL or a pointer List; the pointers stay valid as long as it. 140pub unsafe fn append<T>(mut list: *mut pg_sys::List, items: impl IntoIterator<Item = *mut T>) -> *mut pg_sys::List { 141 for item in items { 142 list = unsafe { pg_sys::lappend(list, item.cast()) }; 143 } 144 list 145} 146 147/// Visits every node of an expression tree, depth first, as 148/// `expression_tree_walker` does; `visit` returns false to skip a node's 149/// children. `visit` must not raise an ERROR. 150/// 151/// # Safety 152/// `node` is NULL or an expression tree. 153pub unsafe fn walk(node: *mut pg_sys::Node, visit: &mut dyn FnMut(*mut pg_sys::Node) -> bool) { 154 #[pg_guard] 155 unsafe extern "C-unwind" fn walker(node: *mut pg_sys::Node, context: *mut c_void) -> bool { 156 if node.is_null() { 157 return false; 158 } 159 // SAFETY: `context` is the `&mut dyn FnMut` passed below, alive 160 // for the whole walk. 161 let visit = unsafe { &mut *(context as *mut &mut dyn FnMut(*mut pg_sys::Node) -> bool) }; 162 if visit(node) { 163 unsafe { pg_sys::expression_tree_walker_impl(node, Some(walker), context) }; 164 } 165 false 166 } 167 let mut visit = visit; 168 unsafe { walker(node, (&raw mut visit).cast()) }; 169} 170 171/// Rebuilds an expression tree, as `expression_tree_mutator` does: 172/// `replace` returns a node to put in place of one (its children are not 173/// visited), or `None` to copy it and visit its children. `replace` must 174/// not raise an ERROR. 175/// 176/// # Safety 177/// `node` is NULL or an expression tree. 178pub unsafe fn mutate( 179 node: *mut pg_sys::Node, 180 replace: &mut dyn FnMut(*mut pg_sys::Node) -> Option<*mut pg_sys::Node>, 181) -> *mut pg_sys::Node { 182 #[pg_guard] 183 unsafe extern "C-unwind" fn mutator(node: *mut pg_sys::Node, context: *mut c_void) -> *mut pg_sys::Node { 184 if node.is_null() { 185 return node; 186 } 187 // SAFETY: `context` is the `&mut dyn FnMut` passed below, alive 188 // for the whole walk. 189 let replace = unsafe { &mut *(context as *mut &mut dyn FnMut(*mut pg_sys::Node) -> Option<*mut pg_sys::Node>) }; 190 match replace(node) { 191 Some(new) => new, 192 None => unsafe { pg_sys::expression_tree_mutator_impl(node, Some(mutator), context) }, 193 } 194 } 195 let mut replace = replace; 196 unsafe { mutator(node, (&raw mut replace).cast()) } 197}