ffi.rsannotatedffi.rssource197 lines · 6.7 KB · raw
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}