exec.rsannotatedexec.rssource569 lines · 24.2 KB · raw
1//! Execution: pull rows ahead while the window has room, judge each in a
2//! scoped task, emit in the child's order.
3
4use std::cell::{Cell, RefCell};
5use std::collections::VecDeque;
6use std::ffi::{CStr, c_int};
7use std::rc::Rc;
8use std::task::{Poll, Waker};
9
10use pgrx::pg_sys::panic::ErrorReport;
11use pgrx::{FromDatum, IntoDatum, PgLogLevel, PgMemoryContexts, PgSqlErrorCode, pg_guard, pg_sys};
12
13use super::ffi::{
14    append, copy_minimal_tuple, exec_clear_tuple, exec_proc_node, ints, invoke_function_execute_hook, is_a, ptrs,
15    tup_is_null, walk,
16};
17use super::plan::tables;
18use super::tuple::{Expr, ScanTuple};
19use super::backend;
20use super::reach;
21use super::guard::scan_estimate;
22use super::{Arg, Call, Field, Judge, Out, Refusal, Returns, Shown, Statement, Value};
23use crate::executor::{Scope, block_on};
24use crate::row::{CanonicalOutput, RowJson};
25
26/// Postgres holds this as its `CustomScanState`: the base must come
27/// first. It is Rust-heap memory that Postgres never frees; the query's
28/// memory context drops it (pgrx's `leak_and_drop_on_delete`), which
29/// also covers the queries that error and never reach `EndCustomScan`.
30#[repr(C)]
31pub(super) struct State<J: Judge> {
32    base: pg_sys::CustomScanState,
33    run: Option<Run<J>>,
34}
35
36/// Everything a running scan owns. Absent under EXPLAIN without ANALYZE.
37struct Run<J: Judge> {
38    judge: J,
39    child: *mut pg_sys::PlanState,
40    tuple: ScanTuple,
41    /// One per call column, in scan-tuple order.
42    calls: Vec<Planned>,
43    /// Writes row arguments as JSON.
44    json: RefCell<RowJson>,
45    /// No call is referenced: rows pass straight through.
46    passthrough: bool,
47    /// Receives a row's copy when it is emitted, and frees it at the next.
48    holder: *mut pg_sys::TupleTableSlot,
49    /// Where waiting rows are copied; reset on rescan.
50    rows: pg_sys::MemoryContext,
51    /// Where storing answers allocates (SPI's argument datums); reset
52    /// after each store.
53    settling: pg_sys::MemoryContext,
54    waiting: VecDeque<Waiting>,
55    child_done: bool,
56    board: Rc<Board>,
57    /// Last, so the tasks go before the state they write into.
58    scope: Scope,
59}
60
61struct Planned {
62    function: usize,
63    returns: Returns,
64    /// The blessed descriptor of a [`Returns::Record`] function's result
65    /// type, in the query's context; null otherwise.
66    record: pg_sys::TupleDesc,
67    /// Each argument, and the type it evaluates to.
68    args: Vec<(Arg, Expr, pg_sys::Oid, i32)>,
69    /// Referenced by the quals or the projection; others are left NULL.
70    used: bool,
71    /// When the row does not satisfy it, Postgres never reads the call,
72    /// which is left NULL and not sent (`reach.rs`); `None` is always.
73    reach: Option<Expr>,
74}
75
76struct Waiting {
77    tuple: pg_sys::MinimalTuple,
78    /// Per call column: `None` is NULL.
79    answer: Rc<RefCell<Option<Vec<Option<Out>>>>>,
80}
81
82/// Where tasks report to the waiting scan.
83#[derive(Default)]
84struct Board {
85    waker: Cell<Option<Waker>>,
86    failure: RefCell<Option<Refusal>>,
87}
88
89impl Board {
90    fn wake(&self) {
91        if let Some(waker) = self.waker.take() {
92            waker.wake();
93        }
94    }
95}
96
97pub(super) fn methods<J: Judge>() -> pg_sys::CustomExecMethods {
98    pg_sys::CustomExecMethods {
99        CustomName: J::NAME.as_ptr(),
100        BeginCustomScan: Some(begin::<J>),
101        ExecCustomScan: Some(exec::<J>),
102        EndCustomScan: Some(end::<J>),
103        ReScanCustomScan: Some(rescan::<J>),
104        ExplainCustomScan: Some(explain::<J>),
105        ..Default::default()
106    }
107}
108
109#[pg_guard]
110pub(super) unsafe extern "C-unwind" fn create_state<J: Judge>(_plan: *mut pg_sys::CustomScan) -> *mut pg_sys::Node {
111    let state = State::<J> {
112        base: pg_sys::CustomScanState {
113            ss: pg_sys::ScanState {
114                ps: pg_sys::PlanState { type_: pg_sys::NodeTag::T_CustomScanState, ..Default::default() },
115                ..Default::default()
116            },
117            methods: &tables().exec,
118            ..Default::default()
119        },
120        run: None,
121    };
122    PgMemoryContexts::CurrentMemoryContext.leak_and_drop_on_delete(state).cast()
123}
124
125/// # Safety
126/// `node` came from [`create_state::<J>`].
127unsafe fn state<'a, J: Judge>(node: *mut pg_sys::CustomScanState) -> &'a mut State<J> {
128    unsafe { &mut *node.cast::<State<J>>() }
129}
130
131#[pg_guard]
132unsafe extern "C-unwind" fn begin<J: Judge>(node: *mut pg_sys::CustomScanState, estate: *mut pg_sys::EState, eflags: c_int) {
133    // SAFETY: ExecInitCustomScan has set up the node for this plan.
134    unsafe {
135        let plan = (*node).ss.ps.plan.cast::<pg_sys::CustomScan>();
136        let child_plan = ptrs::<pg_sys::Plan>((*plan).custom_plans).next().expect("one child plan");
137        let child = pg_sys::ExecInitNode(child_plan, estate, eflags);
138        (*node).custom_ps = append(std::ptr::null_mut(), [child]);
139
140        let mut private = ints((*plan).custom_private);
141        let prefix = private.next().expect("the child's column count") as usize;
142        let functions: Vec<usize> = private.map(|i| i as usize).collect();
143        let funcids: Vec<pg_sys::Oid> = ptrs::<pg_sys::TargetEntry>((*plan).custom_scan_tlist)
144            .skip(prefix)
145            .map(|entry| (*(*entry).expr.cast::<pg_sys::FuncExpr>()).funcid)
146            .collect();
147
148        // After setrefs, a call the node computes is an INDEX_VAR column
149        // past the prefix wherever the quals or projection use it.
150        let mut used = vec![false; functions.len()];
151        for list in [(*plan).scan.plan.targetlist, (*plan).scan.plan.qual] {
152            walk(list.cast(), &mut |n| {
153                if is_a(n.cast(), pg_sys::NodeTag::T_Var) {
154                    let var = n.cast::<pg_sys::Var>();
155                    let column = (*var).varattno as usize;
156                    if (*var).varno == pg_sys::INDEX_VAR && column > prefix {
157                        used[column - prefix - 1] = true;
158                    }
159                }
160                true
161            });
162        }
163
164        // The calls are never initialized as expressions, which is where
165        // Postgres checks EXECUTE; so check it here, as ExecInitFunc does.
166        for (&funcid, _) in funcids.iter().zip(&used).filter(|&(_, &u)| u) {
167            let acl = pg_sys::object_aclcheck(pg_sys::ProcedureRelationId, funcid, pg_sys::GetUserId(), pg_sys::ACL_EXECUTE as _);
168            if acl != pg_sys::AclResult::ACLCHECK_OK {
169                pg_sys::aclcheck_error(acl, pg_sys::ObjectType::OBJECT_FUNCTION, pg_sys::get_func_name(funcid));
170            }
171            invoke_function_execute_hook(funcid);
172        }
173
174        if eflags as u32 & pg_sys::EXEC_FLAG_EXPLAIN_ONLY != 0 {
175            return;
176        }
177
178        let parent = &raw mut (*node).ss.ps;
179        let mut exprs = ptrs::<pg_sys::Expr>((*plan).custom_exprs);
180        let calls: Vec<Planned> = functions
181            .iter()
182            .zip(used)
183            .zip(&funcids)
184            .map(|((&function, used), &funcid)| {
185                let spec = &J::FUNCTIONS[function];
186                let record = match spec.returns {
187                    Returns::Record => {
188                        pg_sys::BlessTupleDesc(pg_sys::lookup_rowtype_tupdesc_copy(pg_sys::get_func_rettype(funcid), -1))
189                    }
190                    _ => std::ptr::null_mut(),
191                };
192                let args = spec
193                    .args
194                    .iter()
195                    .map(|&kind| {
196                        let expr = exprs.next().expect("an argument per parameter");
197                        let (typid, typmod) = (pg_sys::exprType(expr.cast()), pg_sys::exprTypmod(expr.cast()));
198                        (kind, Expr::init(expr, parent), typid, typmod)
199                    })
200                    .collect();
201                Planned { function, returns: spec.returns, record, args, used, reach: None }
202            })
203            .collect();
204        let mut calls = calls;
205        for planned in &mut calls {
206            let reach = exprs.next().expect("a reach per call");
207            if !reach::is_always(reach.cast()) {
208                planned.reach = Some(Expr::init(reach, parent));
209            }
210        }
211        assert!(exprs.next().is_none(), "custom_exprs is each call's arguments, then each call's reach");
212
213        let judge = J::begin(&Statement::new(estate)).unwrap_or_else(|e| raise(*e));
214        let rows = pg_sys::AllocSetContextCreateInternal(
215            pg_sys::CurrentMemoryContext,
216            c"jev scan rows".as_ptr(),
217            pg_sys::ALLOCSET_DEFAULT_MINSIZE as _,
218            pg_sys::ALLOCSET_DEFAULT_INITSIZE as _,
219            pg_sys::ALLOCSET_DEFAULT_MAXSIZE as _,
220        );
221        let settling = pg_sys::AllocSetContextCreateInternal(
222            pg_sys::CurrentMemoryContext,
223            c"jev scan settle".as_ptr(),
224            pg_sys::ALLOCSET_DEFAULT_MINSIZE as _,
225            pg_sys::ALLOCSET_DEFAULT_INITSIZE as _,
226            pg_sys::ALLOCSET_DEFAULT_MAXSIZE as _,
227        );
228        let passthrough = !calls.iter().any(|c| c.used);
229        state::<J>(node).run = Some(Run {
230            judge,
231            child,
232            tuple: ScanTuple::new(&raw mut (*node).ss, prefix),
233            calls,
234            json: RefCell::new(RowJson::new()),
235            passthrough,
236            holder: pg_sys::ExecInitExtraTupleSlot(estate, pg_sys::ExecGetResultType(child), &pg_sys::TTSOpsMinimalTuple),
237            rows,
238            settling,
239            waiting: VecDeque::new(),
240            child_done: false,
241            board: Rc::default(),
242            scope: Scope::new(),
243        });
244    }
245}
246
247#[pg_guard]
248unsafe extern "C-unwind" fn exec<J: Judge>(node: *mut pg_sys::CustomScanState) -> *mut pg_sys::TupleTableSlot {
249    // SAFETY: ExecScan applies the quals and projection to what `next`
250    // returns, as for any scan.
251    unsafe { pg_sys::ExecScan(&raw mut (*node).ss, Some(next::<J>), Some(recheck::<J>)) }
252}
253
254#[pg_guard]
255unsafe extern "C-unwind" fn next<J: Judge>(ss: *mut pg_sys::ScanState) -> *mut pg_sys::TupleTableSlot {
256    // SAFETY: `ss` is this node's (ExecScan passes it back); begin ran.
257    unsafe { state::<J>(ss.cast()).run.as_mut().expect("the scan began").next() }
258}
259
260/// An EvalPlanQual recheck (DML or a row lock meeting a concurrent
261/// update). The scan's `scanrelid` is 0, so ExecScan asks this to fill the
262/// scan slot: the child, a scan of the relation in the recheck's executor,
263/// returns the row's current version once, and it is judged as any row.
264/// That executor's scans begin with [`Statement::rechecking`], so the
265/// judge answers from the statement's judgments or the cache and never
266/// sends.
267#[pg_guard]
268unsafe extern "C-unwind" fn recheck<J: Judge>(ss: *mut pg_sys::ScanState, _slot: *mut pg_sys::TupleTableSlot) -> bool {
269    // SAFETY: `ss` is this node's (ExecScan passes it back); begin ran,
270    // and `next` loads the scan slot, which is `_slot`.
271    unsafe { !tup_is_null(state::<J>(ss.cast()).run.as_mut().expect("the scan began").next()) }
272}
273
274#[pg_guard]
275unsafe extern "C-unwind" fn rescan<J: Judge>(node: *mut pg_sys::CustomScanState) {
276    // SAFETY: an initialized node; the child is the one begin created.
277    unsafe {
278        if let Some(run) = state::<J>(node).run.as_mut() {
279            run.restart();
280        }
281        pg_sys::ExecScanReScan(&raw mut (*node).ss);
282        let child = ptrs::<pg_sys::PlanState>((*node).custom_ps).next().expect("one child");
283        // A changed parameter makes the next ExecProcNode rescan it.
284        if (*child).chgParam.is_null() {
285            pg_sys::ExecReScan(child);
286        }
287    }
288}
289
290#[pg_guard]
291unsafe extern "C-unwind" fn explain<J: Judge>(
292    node: *mut pg_sys::CustomScanState,
293    _ancestors: *mut pg_sys::List,
294    es: *mut pg_sys::ExplainState,
295) {
296    // SAFETY: an initialized node of the plan being explained. Under ANALYZE this runs after
297    // the executor finished and before it ends, so `run` is still there.
298    unsafe {
299        let estimate = scan_estimate((*node).ss.ps.plan.cast());
300        let run = state::<J>(node).run.as_ref().filter(|_| (*es).analyze);
301        for property in J::explain((*es).costs.then_some(&estimate), run.map(|r| &r.judge)) {
302            let unit = property.unit.map_or(std::ptr::null(), CStr::as_ptr);
303            match property.value {
304                Shown::Integer(v) => pg_sys::ExplainPropertyInteger(property.label.as_ptr(), unit, v, es),
305                Shown::Float(v, digits) => {
306                    pg_sys::ExplainPropertyFloat(property.label.as_ptr(), unit, v, c_int::from(digits), es)
307                }
308            }
309        }
310    }
311}
312
313#[pg_guard]
314unsafe extern "C-unwind" fn end<J: Judge>(node: *mut pg_sys::CustomScanState) {
315    // SAFETY: an initialized node, ended once.
316    unsafe {
317        // Keeps what was answered before the scope is dropped.
318        if let Some(run) = state::<J>(node).run.as_ref() {
319            run.judge.settle().unwrap_or_else(|e| raise(*e));
320        }
321        // Drops the scope first: unanswered requests are reset now, not
322        // when the query's memory goes.
323        state::<J>(node).run = None;
324        for child in ptrs::<pg_sys::PlanState>((*node).custom_ps) {
325            pg_sys::ExecEndNode(child);
326        }
327    }
328}
329
330impl<J: Judge> Run<J> {
331    /// The next row, answered, in the scan slot; or the slot cleared at
332    /// the end.
333    unsafe fn next(&mut self) -> *mut pg_sys::TupleTableSlot {
334        unsafe {
335            if self.passthrough {
336                let row = exec_proc_node(self.child);
337                if tup_is_null(row) {
338                    return self.tuple.clear();
339                }
340                return self.tuple.load(row, std::iter::repeat_n(None, self.calls.len()));
341            }
342            loop {
343                self.fill();
344                self.settle();
345                if let Some(failure) = self.board.failure.take() {
346                    raise(*failure);
347                }
348                let Some(head) = self.waiting.front() else {
349                    return self.tuple.clear();
350                };
351                if head.answer.borrow().is_some() {
352                    let row = self.waiting.pop_front().expect("the head");
353                    return self.emit(row);
354                }
355                let (board, answer) = (self.board.clone(), head.answer.clone());
356                block_on(std::future::poll_fn(|cx| {
357                    if board.failure.borrow().is_some() || answer.borrow().is_some() || backend::pending(cx.waker()) {
358                        return Poll::Ready(());
359                    }
360                    board.waker.set(Some(cx.waker().clone()));
361                    Poll::Pending
362                }));
363            }
364        }
365    }
366
367    /// Pulls rows until the window is full, starting each one's judgement.
368    unsafe fn fill(&mut self) {
369        unsafe {
370            while self.waiting.len() < self.judge.window().max(1) && !self.child_done {
371                let slot = exec_proc_node(self.child);
372                if tup_is_null(slot) {
373                    self.child_done = true;
374                    break;
375                }
376                let tuple = PgMemoryContexts::For(self.rows).switch_to(|_| copy_minimal_tuple(slot));
377                self.tuple.load(slot, std::iter::repeat_n(None, self.calls.len()));
378                let (columns, calls) = self.evaluate();
379                let answer = Rc::new(RefCell::new(None));
380                if calls.is_empty() {
381                    *answer.borrow_mut() = Some(vec![None; self.calls.len()]);
382                } else {
383                    let judgement = self.judge.judge(calls);
384                    let (board, into, width) = (self.board.clone(), answer.clone(), self.calls.len());
385                    self.scope.spawn(async move {
386                        match judgement.await {
387                            Ok(outs) if outs.len() == columns.len() => {
388                                let mut row = vec![None; width];
389                                for (column, out) in columns.into_iter().zip(outs) {
390                                    row[column] = out;
391                                }
392                                *into.borrow_mut() = Some(row);
393                            }
394                            Ok(outs) => {
395                                let message = format!("{} answers for {} calls", outs.len(), columns.len());
396                                board.failure.borrow_mut().get_or_insert_with(|| Box::new(internal(message)));
397                            }
398                            Err(e) => {
399                                board.failure.borrow_mut().get_or_insert(e);
400                            }
401                        }
402                        board.wake();
403                    });
404                }
405                self.waiting.push_back(Waiting { tuple, answer });
406            }
407        }
408    }
409
410    /// The row's calls that Postgres reaches and that have no NULL
411    /// argument (STRICT: those are NULL without asking), and the call
412    /// column each answers.
413    unsafe fn evaluate(&self) -> (Vec<usize>, Vec<Call>) {
414        unsafe {
415            self.tuple.reset();
416            // The JSON of a row's leaves depends on these; see crate::row.
417            let _output = CanonicalOutput::enter().unwrap_or_else(|e| raise(internal(e.to_string())));
418            PgMemoryContexts::For(self.tuple.memory()).switch_to(|_| {
419                let mut columns = Vec::new();
420                let mut calls = Vec::new();
421                for (column, planned) in self.calls.iter().enumerate().filter(|(_, p)| p.used) {
422                    if let Some(reach) = &planned.reach
423                        && !self.tuple.eval(reach).is_some_and(|d| d.value() != 0)
424                    {
425                        continue;
426                    }
427                    let args: Option<Vec<Value>> = planned
428                        .args
429                        .iter()
430                        .map(|&(kind, ref expr, typid, typmod)| {
431                            let datum = self.tuple.eval(expr)?;
432                            Some(match kind {
433                                Arg::Row => {
434                                    let mut json = String::new();
435                                    self.json.borrow_mut().write(&mut json, datum, typid, typmod);
436                                    Value::Json(json)
437                                }
438                                Arg::Text => Value::Text(
439                                    CStr::from_ptr(pg_sys::text_to_cstring(datum.cast_mut_ptr::<pg_sys::text>())).to_string_lossy().into_owned(),
440                                ),
441                                // SAFETY: the planner matched this argument to a float8
442                                // parameter, and eval returned it non-NULL.
443                                Arg::Float8 => Value::Float8(f64::from_datum(datum, false).expect("a non-NULL float8")),
444                                // SAFETY: as above, a non-NULL text[]. The Array
445                                // borrows the per-tuple context, and is copied out.
446                                Arg::TextArray => Value::TextArray(
447                                    pgrx::Array::<String>::from_datum(datum, false)
448                                        .expect("a non-NULL text[]")
449                                        .iter()
450                                        .collect(),
451                                ),
452                            })
453                        })
454                        .collect();
455                    if let Some(args) = args {
456                        columns.push(column);
457                        calls.push(Call { function: planned.function, args });
458                    }
459                }
460                (columns, calls)
461            })
462        }
463    }
464
465    /// Loads `row` into the scan tuple: the child's columns, then the
466    /// answers.
467    unsafe fn emit(&mut self, row: Waiting) -> *mut pg_sys::TupleTableSlot {
468        let answers = row.answer.take().expect("an answered row");
469        // By-reference answers (text) are palloc'd in per-tuple memory, not
470        // the current context: that is the query's (ExecutorRun's), which
471        // would keep every row's until the statement ends. Per-tuple memory
472        // is reset only when the parent pulls again (ExecScan, then `fill`),
473        // so the row stays valid while the parent reads it, as a
474        // projection's results do.
475        unsafe {
476            // SAFETY: the node's own per-tuple context, live for the executor.
477            let datums: Vec<Option<pg_sys::Datum>> = PgMemoryContexts::For(self.tuple.memory()).switch_to(|_| {
478                answers
479                    .into_iter()
480                    .zip(&self.calls)
481                    .map(|(answer, planned)| match (answer, planned.returns) {
482                        (None, _) => None,
483                        (Some(Out::Float8(p)), Returns::Float8) => p.into_datum(),
484                        (Some(Out::Bool(b)), Returns::Bool) => b.into_datum(),
485                        (Some(Out::Text(t)), Returns::Text) => t.into_datum(),
486                        (Some(Out::Record(fields)), Returns::Record) => Some(record(planned.record, fields)),
487                        (Some(out), returns) => raise(internal(format!("{out:?} answered a call returning {returns:?}"))),
488                    })
489                    .collect()
490            });
491            // The holder frees the previous row's copy; this one stays
492            // until the next emit, as long as the scan tuple borrows it.
493            pg_sys::ExecStoreMinimalTuple(row.tuple, self.holder, true);
494            self.tuple.load(self.holder, datums.into_iter())
495        }
496    }
497
498    /// Runs the work judgements queued for the backend (see
499    /// [`backend`]), then stores what was answered since the last call.
500    /// Its SPI arguments
501    /// (each question with its labels, each answer) are palloc'd in the
502    /// current context, the query's, which would keep every row's until
503    /// the statement ends; a context of its own is reset once they are
504    /// stored.
505    unsafe fn settle(&self) {
506        unsafe {
507            // SAFETY: the node's own context, live for the executor.
508            PgMemoryContexts::For(self.settling)
509                .switch_to(|_| {
510                    backend::run();
511                    self.judge.settle()
512                })
513                .unwrap_or_else(|e| raise(*e));
514            pg_sys::MemoryContextReset(self.settling);
515        }
516    }
517
518    /// Back to the first row: drop every judgement and waiting copy.
519    unsafe fn restart(&mut self) {
520        unsafe {
521            self.scope.cancel();
522            self.waiting.clear();
523            exec_clear_tuple(self.holder);
524            pg_sys::MemoryContextReset(self.rows);
525            self.board.failure.take();
526            self.child_done = false;
527        }
528    }
529}
530
531fn internal(message: String) -> ErrorReport {
532    ErrorReport::new(PgSqlErrorCode::ERRCODE_INTERNAL_ERROR, message, "jev scan")
533}
534
535fn raise(report: ErrorReport) -> ! {
536    report.report(PgLogLevel::ERROR);
537    unreachable!("an ERROR report does not return")
538}
539
540/// A composite datum of `desc` from `fields`, in the current context.
541///
542/// # Safety
543/// `desc` is a blessed descriptor, live for the call.
544unsafe fn record(desc: pg_sys::TupleDesc, fields: Vec<Option<Field>>) -> pg_sys::Datum {
545    unsafe {
546        if (*desc).natts as usize != fields.len() {
547            raise(internal(format!("{} fields for a record of {} columns", fields.len(), (*desc).natts)));
548        }
549        let (mut values, mut nulls): (Vec<pg_sys::Datum>, Vec<bool>) = fields
550            .into_iter()
551            .map(|field| match field {
552                None => (pg_sys::Datum::from(0), true),
553                Some(Field::Float8(v)) => (v.into_datum().expect("a float8"), false),
554                Some(Field::Int4(v)) => (v.into_datum().expect("an int4"), false),
555                Some(Field::Bool(v)) => (v.into_datum().expect("a bool"), false),
556                Some(Field::Text(v)) => (v.into_datum().expect("a text"), false),
557                Some(Field::Jsonb(v)) => {
558                    let text = std::ffi::CString::new(v).expect("JSON text has no NUL");
559                    // SAFETY: jsonb_in reads a NUL-terminated cstring.
560                    let datum = pgrx::fcinfo::direct_function_call_as_datum(pg_sys::jsonb_in, &[text.as_c_str().into_datum()]);
561                    (datum.expect("jsonb_in returns a value"), false)
562                }
563            })
564            .unzip();
565        // SAFETY: one value and one null flag per column of `desc`.
566        let tuple = pg_sys::heap_form_tuple(desc, values.as_mut_ptr(), nulls.as_mut_ptr());
567        pg_sys::HeapTupleHeaderGetDatum((*tuple).t_data)
568    }
569}